diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 495b80a3427..fce02bcb7bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -219,7 +219,7 @@ Calcite follows [Conventional Commits](https://www.conventionalcommits.org/en/v1 Contributions should adhere to the `(): ` format and include the following: - [Commit type](#commit-type) -- [Scope of change](#scope-of-change), _optional_ +- [Scope of change](#scope-of-change), *optional* - [Descriptive commit subject](#descriptive-commit-subject) Check out the [contribution example](#contribution-example) for a formatted example, and explore [breaking change formatting](#breaking-changes) for consideration during Calcite's breaking change releases. @@ -240,7 +240,7 @@ Contributions must adhere to **one** of the following types: ### Scope of change -_Optional_. Most contributions will include a scope, such as a component, multiple components, test(s), or utilities. For example: +*Optional*. Most contributions will include a scope, such as a component, multiple components, test(s), or utilities. For example: - `text-area` - `dropdown, dropdown-group, dropdown-item` diff --git a/packages/calcite-components/src/assets/styles/includes.scss b/packages/calcite-components/src/assets/styles/includes.scss index d10a18d74d0..fa93acedd95 100644 --- a/packages/calcite-components/src/assets/styles/includes.scss +++ b/packages/calcite-components/src/assets/styles/includes.scss @@ -211,6 +211,12 @@ $common-animatable-props: "background-color, block-size, border-color, box-shado transition-timing-function: ease-in-out; } +@mixin hidden-item() { + :host([hidden-item]) { + @apply hidden; + } +} + // Mixin for text highlighting styles. // - this should be used in conjunction with the `text.tsx` util. @mixin text-highlight-item() { diff --git a/packages/calcite-components/src/components/combobox-item-group/combobox-item-group.scss b/packages/calcite-components/src/components/combobox-item-group/combobox-item-group.scss index 03270197cb7..a01a4879f84 100644 --- a/packages/calcite-components/src/components/combobox-item-group/combobox-item-group.scss +++ b/packages/calcite-components/src/components/combobox-item-group/combobox-item-group.scss @@ -48,3 +48,4 @@ } @include base-component(); +@include hidden-item(); diff --git a/packages/calcite-components/src/components/combobox-item-group/combobox-item-group.tsx b/packages/calcite-components/src/components/combobox-item-group/combobox-item-group.tsx index 36c13c09a84..8b441259dd3 100644 --- a/packages/calcite-components/src/components/combobox-item-group/combobox-item-group.tsx +++ b/packages/calcite-components/src/components/combobox-item-group/combobox-item-group.tsx @@ -53,6 +53,13 @@ export class ComboboxItemGroup extends LitElement { */ @property() scale: Scale = "m"; + /** + * Specifies whether the user set the hidden attribute in the HTML + * + * @private + */ + @property({ reflect: true }) hiddenItem = false; + // #endregion // #region Lifecycle diff --git a/packages/calcite-components/src/components/combobox-item/combobox-item.scss b/packages/calcite-components/src/components/combobox-item/combobox-item.scss index a1bff5b15d7..e29927d8d96 100644 --- a/packages/calcite-components/src/components/combobox-item/combobox-item.scss +++ b/packages/calcite-components/src/components/combobox-item/combobox-item.scss @@ -150,4 +150,5 @@ ul:focus { line-height: var(--calcite-font-line-height-relative-snug); } +@include hidden-item(); @include text-highlight-item(); diff --git a/packages/calcite-components/src/components/combobox-item/combobox-item.tsx b/packages/calcite-components/src/components/combobox-item/combobox-item.tsx index 4c44e0ff5ad..5148c9025fb 100644 --- a/packages/calcite-components/src/components/combobox-item/combobox-item.tsx +++ b/packages/calcite-components/src/components/combobox-item/combobox-item.tsx @@ -134,6 +134,13 @@ export class ComboboxItem extends LitElement implements InteractiveComponent { */ @property() value: any; + /** + * Specifies whether the user set the hidden attribute in the HTML + * + * @private + */ + @property({ reflect: true }) hiddenItem = false; + // #endregion // #region Events diff --git a/packages/calcite-components/src/components/combobox/combobox.e2e.ts b/packages/calcite-components/src/components/combobox/combobox.e2e.ts index d1c7495cc06..22f79e933a3 100644 --- a/packages/calcite-components/src/components/combobox/combobox.e2e.ts +++ b/packages/calcite-components/src/components/combobox/combobox.e2e.ts @@ -469,7 +469,7 @@ describe("calcite-combobox", () => { await page.waitForTimeout(DEBOUNCE.filter); const visibleItemsAndGroups = await page.findAll( - "calcite-combobox-item:not([hidden]), calcite-combobox-item-group:not([hidden])", + "calcite-combobox-item:not([hidden-item]), calcite-combobox-item-group:not([hidden-item])", ); const visibleItemAndGroupIds = await Promise.all(visibleItemsAndGroups.map((item) => item.getProperty("id"))); @@ -497,7 +497,7 @@ describe("calcite-combobox", () => { await page.waitForTimeout(DEBOUNCE.filter); const filteredItemsAndGroups = await page.findAll( - "calcite-combobox-item:not([hidden]), calcite-combobox-item-group:not([hidden])", + "calcite-combobox-item:not([hidden-item]), calcite-combobox-item-group:not([hidden-item])", ); const filteredItemAndGroupIds = await Promise.all(filteredItemsAndGroups.map((item) => item.getProperty("id"))); @@ -518,7 +518,7 @@ describe("calcite-combobox", () => { await page.waitForTimeout(DEBOUNCE.filter); const allVisibleItemAndGroups = await page.findAll( - "calcite-combobox-item:not([hidden]), calcite-combobox-item-group:not([hidden])", + "calcite-combobox-item:not([hidden]):not([hidden-item]), calcite-combobox-item-group:not([hidden]):not([hidden-item])", ); const allVisibleItemAndGroupIds = await Promise.all( allVisibleItemAndGroups.map((item) => item.getProperty("id")), @@ -570,7 +570,7 @@ describe("calcite-combobox", () => { await page.waitForChanges(); await page.waitForTimeout(DEBOUNCE.filter); - const visibleItems = await page.findAll("calcite-combobox-item:not([hidden])"); + const visibleItems = await page.findAll("calcite-combobox-item:not([hidden-item])"); expect(visibleItems.length).toBe(1); expect(await visibleItems[0].getProperty("value")).toBe("1"); @@ -725,7 +725,7 @@ describe("calcite-combobox", () => { expect(await combobox.getProperty("filteredItems")).toHaveLength(2); - const visibleItems = await page.findAll("calcite-combobox-item:not([hidden])"); + const visibleItems = await page.findAll("calcite-combobox-item:not([hidden]):not([hidden-item])"); expect(visibleItems.map((item) => item.id)).toEqual(["text-label-match", "description-match"]); }); @@ -919,6 +919,56 @@ describe("calcite-combobox", () => { } }); + it("should show correct number of items when child hidden", async () => { + const page = await newE2EPage(); + await page.setContent(html` + + + + + + + + + + + `); + await page.waitForChanges(); + + const element = await page.find("calcite-combobox"); + await element.click(); + await page.waitForChanges(); + const items = await page.findAll("calcite-combobox-item, calcite-combobox-item-group"); + for (let i = 0; i < items.length; i++) { + expect(await items[i].isIntersectingViewport()).toBe(i !== 3); + } + }); + + it("should show correct number of items when parent hidden", async () => { + const page = await newE2EPage(); + await page.setContent(html` + + + + + `); + await page.waitForChanges(); + + const element = await page.find("calcite-combobox"); + await element.click(); + await page.waitForChanges(); + const items = await page.findAll("calcite-combobox-item, calcite-combobox-item-group"); + for (let i = 0; i < items.length; i++) { + expect(await items[i].isIntersectingViewport()).toBe(i === 5); + } + }); + it("should show correct max items after selection", async () => { const page = await newE2EPage(); const maxItems = 6; diff --git a/packages/calcite-components/src/components/combobox/combobox.tsx b/packages/calcite-components/src/components/combobox/combobox.tsx index fa4d5740bb8..8e1471442e6 100644 --- a/packages/calcite-components/src/components/combobox/combobox.tsx +++ b/packages/calcite-components/src/components/combobox/combobox.tsx @@ -64,6 +64,7 @@ import type { Chip } from "../chip/chip"; import type { ComboboxItemGroup as HTMLCalciteComboboxItemGroupElement } from "../combobox-item-group/combobox-item-group"; import type { ComboboxItem as HTMLCalciteComboboxItemElement } from "../combobox-item/combobox-item"; import type { Label } from "../label/label"; +import { isHidden } from "../../utils/component"; import T9nStrings from "./assets/t9n/messages.en.json"; import { ComboboxChildElement, GroupData, ItemData, SelectionDisplay } from "./interfaces"; import { ComboboxItemGroupSelector, ComboboxItemSelector, CSS, IDS } from "./resources"; @@ -145,20 +146,20 @@ export class Combobox itemsAndGroups.forEach((item) => { if (matchAll) { - item.hidden = false; + item.hiddenItem = false; return; } const hidden = !find(item, filteredData); - item.hidden = hidden; + item.hiddenItem = hidden; const [parent, grandparent] = item.ancestors; if (find(parent, filteredData) || find(grandparent, filteredData)) { - item.hidden = false; + item.hiddenItem = false; } if (!hidden) { - item.ancestors.forEach((ancestor) => (ancestor.hidden = false)); + item.ancestors.forEach((ancestor) => (ancestor.hiddenItem = false)); } }); @@ -1126,7 +1127,7 @@ export class Combobox private getMaxScrollerHeight(): number { const allItemsAndGroups = [...this.groupItems, ...this.getItems(true)]; - const items = allItemsAndGroups.filter((item) => !item.hidden); + const items = allItemsAndGroups.filter((item) => !isHidden(item)); const { maxItems } = this; @@ -1226,7 +1227,7 @@ export class Combobox } private getFilteredItems(): HTMLCalciteComboboxItemElement["el"][] { - return this.filterText === "" ? this.items : this.items.filter((item) => !item.hidden); + return this.filterText === "" ? this.items : this.items.filter((item) => !isHidden(item)); } private updateItems = debounce((): void => { diff --git a/packages/calcite-components/src/components/input-time-zone/input-time-zone.e2e.ts b/packages/calcite-components/src/components/input-time-zone/input-time-zone.e2e.ts index 58b251dc993..fa1b6d787e9 100644 --- a/packages/calcite-components/src/components/input-time-zone/input-time-zone.e2e.ts +++ b/packages/calcite-components/src/components/input-time-zone/input-time-zone.e2e.ts @@ -225,7 +225,7 @@ describe("calcite-input-time-zone", () => { } let matchedTimeZoneItems = await page.findAll( - "calcite-input-time-zone >>> calcite-combobox-item:not([hidden])", + "calcite-input-time-zone >>> calcite-combobox-item:not([hidden]):not([hidden-item])", ); expect(matchedTimeZoneItems.length).toBeGreaterThan(1); @@ -234,7 +234,9 @@ describe("calcite-input-time-zone", () => { await page.waitForChanges(); await page.waitForTimeout(DEBOUNCE.filter); - matchedTimeZoneItems = await page.findAll("calcite-input-time-zone >>> calcite-combobox-item:not([hidden])"); + matchedTimeZoneItems = await page.findAll( + "calcite-input-time-zone >>> calcite-combobox-item:not([hidden]):not([hidden-item])", + ); expect(matchedTimeZoneItems).toHaveLength(1); @@ -243,7 +245,9 @@ describe("calcite-input-time-zone", () => { await page.waitForChanges(); await page.waitForTimeout(DEBOUNCE.filter); - matchedTimeZoneItems = await page.findAll("calcite-input-time-zone >>> calcite-combobox-item:not([hidden])"); + matchedTimeZoneItems = await page.findAll( + "calcite-input-time-zone >>> calcite-combobox-item:not([hidden]):not([hidden-item])", + ); expect(matchedTimeZoneItems).toHaveLength(1); @@ -252,7 +256,9 @@ describe("calcite-input-time-zone", () => { await page.waitForChanges(); await page.waitForTimeout(DEBOUNCE.filter); - matchedTimeZoneItems = await page.findAll("calcite-input-time-zone >>> calcite-combobox-item:not([hidden])"); + matchedTimeZoneItems = await page.findAll( + "calcite-input-time-zone >>> calcite-combobox-item:not([hidden]):not([hidden-item])", + ); expect(matchedTimeZoneItems).toHaveLength(2); @@ -260,7 +266,9 @@ describe("calcite-input-time-zone", () => { await page.waitForChanges(); await page.waitForTimeout(DEBOUNCE.filter); - matchedTimeZoneItems = await page.findAll("calcite-input-time-zone >>> calcite-combobox-item:not([hidden])"); + matchedTimeZoneItems = await page.findAll( + "calcite-input-time-zone >>> calcite-combobox-item:not([hidden]):not([hidden-item])", + ); expect(matchedTimeZoneItems.length).toBeGreaterThan(1); }); @@ -545,7 +553,7 @@ describe("calcite-input-time-zone", () => { await page.waitForTimeout(DEBOUNCE.filter); const sharedOffsetTimeZoneItems = await page.findAll( - "calcite-input-time-zone >>> calcite-combobox-item:not([hidden])", + "calcite-input-time-zone >>> calcite-combobox-item:not([hidden]):not([hidden-item])", ); expect(sharedOffsetTimeZoneItems).toHaveLength(2); diff --git a/packages/calcite-components/src/components/stepper-item/stepper-item.scss b/packages/calcite-components/src/components/stepper-item/stepper-item.scss index 9aa31de21a4..c18ccc431c1 100644 --- a/packages/calcite-components/src/components/stepper-item/stepper-item.scss +++ b/packages/calcite-components/src/components/stepper-item/stepper-item.scss @@ -407,3 +407,4 @@ } @include base-component(); +@include hidden-item(); diff --git a/packages/calcite-components/src/components/stepper-item/stepper-item.tsx b/packages/calcite-components/src/components/stepper-item/stepper-item.tsx index baf4d1c10c1..cfa60116345 100644 --- a/packages/calcite-components/src/components/stepper-item/stepper-item.tsx +++ b/packages/calcite-components/src/components/stepper-item/stepper-item.tsx @@ -32,6 +32,7 @@ import { import { IconNameOrString } from "../icon/interfaces"; import { useT9n } from "../../controllers/useT9n"; import type { Stepper } from "../stepper/stepper"; +import { isHidden } from "../../utils/component"; import { CSS } from "./resources"; import T9nStrings from "./assets/t9n/messages.en.json"; import { styles } from "./stepper-item.scss"; @@ -92,6 +93,13 @@ export class StepperItem extends LitElement implements InteractiveComponent, Loa /** When `true`, the icon will be flipped when the element direction is right-to-left (`"rtl"`). */ @property({ reflect: true }) iconFlipRtl = false; + /** + * When `true`, the item will be hidden + * + * @private + * */ + @property({ reflect: true }) hiddenItem = false; + /** * Specifies the layout of the `calcite-stepper-item` inherited from parent `calcite-stepper`, defaults to `horizontal`. * @@ -274,6 +282,7 @@ export class StepperItem extends LitElement implements InteractiveComponent, Loa private handleItemClick(event: MouseEvent): void { if ( this.disabled || + isHidden(this.el) || (this.layout === "horizontal" && event .composedPath() @@ -303,9 +312,11 @@ export class StepperItem extends LitElement implements InteractiveComponent, Loa } private getItemPosition(): number { - return Array.from(this.parentStepperEl?.querySelectorAll("calcite-stepper-item")).indexOf( - this.el, - ); + return Array.from( + this.parentStepperEl?.querySelectorAll( + "calcite-stepper-item:not([hidden]):not([hidden-item])", + ), + ).indexOf(this.el); } // #endregion diff --git a/packages/calcite-components/src/components/stepper/stepper.e2e.ts b/packages/calcite-components/src/components/stepper/stepper.e2e.ts index 7ddf7907142..42ec8eb878b 100644 --- a/packages/calcite-components/src/components/stepper/stepper.e2e.ts +++ b/packages/calcite-components/src/components/stepper/stepper.e2e.ts @@ -249,7 +249,7 @@ describe("calcite-stepper", () => { expect(await step4Content.isVisible()).toBe(false); }); - it("navigates disabled items correctly with nextStep and prevStep methods", async () => { + it("navigates disabled and hidden items correctly with nextStep and prevStep methods", async () => { const page = await newE2EPage(); await page.setContent( html` @@ -259,40 +259,51 @@ describe("calcite-stepper", () => {
Step 2 content
- + + +
Step 4 content
+
`, ); const element = await page.find("calcite-stepper"); const step1 = await page.find("#step-1"); const step2 = await page.find("#step-2"); const step3 = await page.find("#step-3"); + const step4 = await page.find("#step-4"); const step1Content = await page.find("#step-1 >>> .stepper-item-content"); const step2Content = await page.find("#step-2 >>> .stepper-item-content"); const step3Content = await page.find("#step-3 >>> .stepper-item-content"); + const step4Content = await page.find("#step-4 >>> .stepper-item-content"); expect(step1).toHaveAttribute("selected"); expect(step2).not.toHaveAttribute("selected"); expect(step3).not.toHaveAttribute("selected"); + expect(step4).not.toHaveAttribute("selected"); expect(await step1Content.isVisible()).toBe(true); expect(await step2Content.isVisible()).toBe(false); expect(await step3Content.isVisible()).toBe(false); + expect(await step4Content.isVisible()).toBe(false); await element.callMethod("nextStep"); await page.waitForChanges(); expect(step1).not.toHaveAttribute("selected"); expect(step2).not.toHaveAttribute("selected"); - expect(step3).toHaveAttribute("selected"); + expect(step3).not.toHaveAttribute("selected"); + expect(step4).toHaveAttribute("selected"); expect(await step1Content.isVisible()).toBe(false); expect(await step2Content.isVisible()).toBe(false); - expect(await step3Content.isVisible()).toBe(true); + expect(await step3Content.isVisible()).toBe(false); + expect(await step4Content.isVisible()).toBe(true); await element.callMethod("prevStep"); await page.waitForChanges(); expect(step1).toHaveAttribute("selected"); expect(step2).not.toHaveAttribute("selected"); expect(step3).not.toHaveAttribute("selected"); + expect(step4).not.toHaveAttribute("selected"); expect(await step1Content.isVisible()).toBe(true); expect(await step2Content.isVisible()).toBe(false); expect(await step3Content.isVisible()).toBe(false); + expect(await step4Content.isVisible()).toBe(false); }); it("navigates correctly with startStep and endStep methods", async () => { @@ -344,7 +355,7 @@ describe("calcite-stepper", () => { expect(await step4Content.isVisible()).toBe(true); }); - it("navigates disabled items correctly with startStep and endStep methods", async () => { + it("navigates disabled and hidden items correctly with startStep and endStep methods", async () => { const page = await newE2EPage(); await page.setContent( html` @@ -360,6 +371,9 @@ describe("calcite-stepper", () => {
Step 4 content
+
`, ); const element = await page.find("calcite-stepper"); @@ -367,30 +381,36 @@ describe("calcite-stepper", () => { const step2 = await page.find("#step-2"); const step3 = await page.find("#step-3"); const step4 = await page.find("#step-4"); + const step5 = await page.find("#step-5"); const step1Content = await page.find("#step-1 >>> .stepper-item-content"); const step2Content = await page.find("#step-2 >>> .stepper-item-content"); const step3Content = await page.find("#step-3 >>> .stepper-item-content"); const step4Content = await page.find("#step-4 >>> .stepper-item-content"); + const step5Content = await page.find("#step-5 >>> .stepper-item-content"); await element.callMethod("endStep"); await page.waitForChanges(); expect(step1).not.toHaveAttribute("selected"); expect(step2).not.toHaveAttribute("selected"); expect(step3).toHaveAttribute("selected"); expect(step4).not.toHaveAttribute("selected"); + expect(step5).not.toHaveAttribute("selected"); expect(await step1Content.isVisible()).toBe(false); expect(await step2Content.isVisible()).toBe(false); expect(await step3Content.isVisible()).toBe(true); expect(await step4Content.isVisible()).toBe(false); + expect(await step5Content.isVisible()).toBe(false); await element.callMethod("startStep"); await page.waitForChanges(); expect(step1).not.toHaveAttribute("selected"); expect(step2).toHaveAttribute("selected"); expect(step3).not.toHaveAttribute("selected"); expect(step4).not.toHaveAttribute("selected"); + expect(step5).not.toHaveAttribute("selected"); expect(await step1Content.isVisible()).toBe(false); expect(await step2Content.isVisible()).toBe(true); expect(await step3Content.isVisible()).toBe(false); expect(await step4Content.isVisible()).toBe(false); + expect(await step5Content.isVisible()).toBe(false); }); it("navigates to requested step with goToStep method", async () => { @@ -560,6 +580,10 @@ describe("calcite-stepper", () => { expect(changeSpy).toHaveReceivedEventTimes(expectedEvents); expect(await getSelectedItemId()).toBe("step-4"); + await page.$eval("#step-5", itemClicker); + expect(itemChangeSpy).toHaveReceivedEventTimes(expectedEvents); + expect(changeSpy).toHaveReceivedEventTimes(expectedEvents); + await element.callMethod("prevStep"); await page.waitForChanges(); expect(itemChangeSpy).toHaveReceivedEventTimes(expectedEvents); @@ -592,6 +616,9 @@ describe("calcite-stepper", () => {
Step 4 content
+ `, ); @@ -606,6 +633,7 @@ describe("calcite-stepper", () => { + `, ); @@ -634,6 +662,9 @@ describe("calcite-stepper", () => {
Step 4 content
+ `, ); @@ -648,6 +679,7 @@ describe("calcite-stepper", () => { + `, ); @@ -739,23 +771,26 @@ describe("calcite-stepper", () => { expect(await stepperItem3.getProperty("selected")).not.toBe(true); }); - it("should select the next enabled stepper-item if first stepper-item is disabled", async () => { - const page = await newE2EPage(); - await page.setContent( - html` - -
Step 1 content
-
- -
Step 2 content
-
-
`, - ); + it.each(["disabled", "hidden"])( + "should select the next enabled stepper-item if first stepper-item is %s", + async (accessibility) => { + const page = await newE2EPage(); + await page.setContent( + html` + +
Step 1 content
+
+ +
Step 2 content
+
+
`, + ); - const [stepperItem1, stepperItem2] = await page.findAll("calcite-stepper-item"); - expect(await stepperItem1.getProperty("selected")).toBe(false); - expect(await stepperItem2.getProperty("selected")).toBe(true); - }); + const [stepperItem1, stepperItem2] = await page.findAll("calcite-stepper-item"); + expect(await stepperItem1.getProperty("selected")).toBe(false); + expect(await stepperItem2.getProperty("selected")).toBe(true); + }, + ); describe("horizontal-single layout", () => { it("should display action buttons when layout is horizontal-single.", async () => { @@ -843,9 +878,12 @@ describe("calcite-stepper", () => {
Step 2 content
- + + +
Step 4 content
+
`, ); @@ -878,29 +916,27 @@ describe("calcite-stepper", () => { it(`switching to layout="horizontal-single" dynamically from another option should display a single item (#8931)`, async () => { const page = await newE2EPage(); await page.setContent( - html` - - -
Step 1 content
-
- -
Step 2 content
-
- -
Step 3 content
-
- -
Step 4 content
-
-
- `, + html` + +
Step 1 content
+
+ +
Step 2 content
+
+ +
Step 3 content
+
+ +
Step 4 content
+
+
`, ); const stepper = await page.find("calcite-stepper"); await stepper.setProperty("layout", "horizontal-single"); await page.waitForChanges(); - const displayedItems = await page.findAll("calcite-stepper-item:not([hidden])"); + const displayedItems = await page.findAll("calcite-stepper-item:not([hidden]):not([hidden-item])"); expect(displayedItems.length).toBe(1); }); }); diff --git a/packages/calcite-components/src/components/stepper/stepper.tsx b/packages/calcite-components/src/components/stepper/stepper.tsx index 55f84896863..b886c9bf513 100644 --- a/packages/calcite-components/src/components/stepper/stepper.tsx +++ b/packages/calcite-components/src/components/stepper/stepper.tsx @@ -9,6 +9,7 @@ import { NumberingSystem } from "../../utils/locale"; import { useT9n } from "../../controllers/useT9n"; import type { StepperItem } from "../stepper-item/stepper-item"; import type { Action } from "../action/action"; +import { isHidden } from "../../utils/component"; import { CSS } from "./resources"; import { StepBar } from "./functional/step-bar"; import { @@ -319,7 +320,7 @@ export class Stepper extends LitElement { this.multipleViewMode = layout !== "horizontal-single"; items.forEach((item, index) => { - item.hidden = layout === "horizontal-single" && index !== (currentActivePosition || 0); + item.hiddenItem = layout === "horizontal-single" && index !== (currentActivePosition || 0); }); } @@ -348,7 +349,7 @@ export class Stepper extends LitElement { } private filterItems(): StepperItem["el"][] { - return this.items.filter((item) => !item.disabled); + return this.items.filter((item) => !item.disabled && !isHidden(item)); } private setStepperItemNumberingSystem(): void { @@ -390,7 +391,8 @@ export class Stepper extends LitElement { private handleDefaultSlotChange(event: Event): void { const items = slotChangeGetAssignedElements(event).filter( - (el): el is StepperItem["el"] => el?.tagName === "CALCITE-STEPPER-ITEM", + (el): el is StepperItem["el"] => + el?.tagName === "CALCITE-STEPPER-ITEM" && !isHidden(el as StepperItem["el"]), ); this.items = items; const spacing = Array(items.length).fill("1fr").join(" "); diff --git a/packages/calcite-components/src/components/table-row/table-row.scss b/packages/calcite-components/src/components/table-row/table-row.scss index 9d1b599043d..9940eda5044 100644 --- a/packages/calcite-components/src/components/table-row/table-row.scss +++ b/packages/calcite-components/src/components/table-row/table-row.scss @@ -29,3 +29,5 @@ tr { tr.last-visible-row { border-block-end: 0; } + +@include hidden-item(); diff --git a/packages/calcite-components/src/components/table-row/table-row.tsx b/packages/calcite-components/src/components/table-row/table-row.tsx index 2eadc8ff9c5..0b706b290d3 100644 --- a/packages/calcite-components/src/components/table-row/table-row.tsx +++ b/packages/calcite-components/src/components/table-row/table-row.tsx @@ -49,6 +49,9 @@ export class TableRow extends LitElement implements InteractiveComponent { /** Specifies the alignment of the component. */ @property({ reflect: true }) alignment: Alignment; + /** Specifies whether this row should be hidden */ + @property({ reflect: true }) hiddenItem = false; + /** @private */ @property() bodyRowCount: number; diff --git a/packages/calcite-components/src/components/table/table.e2e.ts b/packages/calcite-components/src/components/table/table.e2e.ts index 66d262ecafa..0208657863b 100644 --- a/packages/calcite-components/src/components/table/table.e2e.ts +++ b/packages/calcite-components/src/components/table/table.e2e.ts @@ -1614,7 +1614,7 @@ describe("keyboard navigation", () => { expect(await getFocusedElementProp(page, "id")).toBe("cell-5b"); }); - it("navigates correctly skipping disabled rows", async () => { + it("navigates correctly skipping disabled and hidden rows", async () => { const page = await newE2EPage(); await page.setContent( html` @@ -1638,6 +1638,10 @@ describe("keyboard navigation", () => { cell cell + `, ); @@ -1667,7 +1671,7 @@ describe("keyboard navigation", () => { expect(await getFocusedElementProp(page, "id")).toBe("cell-1b"); }); - it("navigates correctly skipping disabled rows when disabled rows in last body position", async () => { + it("navigates correctly skipping disabled/hidden rows when disabled/hidden rows in last body position", async () => { const page = await newE2EPage(); await page.setContent( html` @@ -1691,6 +1695,10 @@ describe("keyboard navigation", () => { cell cell + `, ); diff --git a/packages/calcite-components/src/components/table/table.tsx b/packages/calcite-components/src/components/table/table.tsx index 188893e36b5..2ec615dfc9e 100644 --- a/packages/calcite-components/src/components/table/table.tsx +++ b/packages/calcite-components/src/components/table/table.tsx @@ -14,6 +14,7 @@ import { getUserAgentString } from "../../utils/browser"; import { useT9n } from "../../controllers/useT9n"; import type { TableRow } from "../table-row/table-row"; import type { Pagination } from "../pagination/pagination"; +import { isHidden } from "../../utils/component"; import { TableInteractionMode, TableLayout, @@ -231,8 +232,8 @@ export class Table extends LitElement implements LoadableComponent { const destination = event.detail.destination; const lastCell = event.detail.lastCell; - const visibleBody = this.bodyRows?.filter((row) => !row.hidden); - const visibleAll = this.allRows?.filter((row) => !row.hidden); + const visibleBody = this.bodyRows?.filter((row) => !isHidden(row)); + const visibleAll = this.allRows?.filter((row) => !isHidden(row)); const lastHeadRow = this.headRows[this.headRows.length - 1]?.positionAll; const firstBodyRow = visibleBody[0]?.positionAll; @@ -346,7 +347,7 @@ export class Table extends LitElement implements LoadableComponent { this.bodyRows?.forEach((row) => { const rowPos = row.positionSection + 1; const inView = rowPos >= this.pageStartRow && rowPos < this.pageStartRow + this.pageSize; - row.hidden = this.pageSize > 0 && !inView && !this.footRows.includes(row); + row.hiddenItem = this.pageSize > 0 && !inView && !this.footRows.includes(row); row.lastVisibleRow = rowPos === this.pageStartRow + this.pageSize - 1 || rowPos === this.bodyRows.length; }); @@ -399,7 +400,7 @@ export class Table extends LitElement implements LoadableComponent { // #region Rendering private renderSelectionArea(): JsxNode { - const outOfViewCount = this._selectedItems?.filter((el) => el.hidden)?.length; + const outOfViewCount = this._selectedItems?.filter((el) => isHidden(el))?.length; const localizedOutOfView = this.localizeNumber(outOfViewCount?.toString()); const localizedSelectedCount = this.localizeNumber(this.selectedCount?.toString()); const selectionText = `${localizedSelectedCount} ${this.messages.selected}`; diff --git a/packages/calcite-components/src/utils/component.ts b/packages/calcite-components/src/utils/component.ts index a68bb835c45..7f662f56653 100644 --- a/packages/calcite-components/src/utils/component.ts +++ b/packages/calcite-components/src/utils/component.ts @@ -1,5 +1,8 @@ import type { LitElement } from "@arcgis/lumina"; import { Scale } from "../components/interfaces"; +import { ComboboxChildElement } from "../components/combobox/interfaces"; +import { StepperItem } from "../components/stepper-item/stepper-item"; +import { TableRow } from "../components/table-row/table-row"; import { logger } from "./logger"; export function getIconScale(componentScale: Scale): Extract { @@ -26,3 +29,7 @@ export function warnIfMissingRequiredProp( logger.warn(`[${component.el.localName}] "${newProp.toString()}" or "${deprecatedProp.toString()}" is required.`); } } + +export function isHidden(el: C): boolean { + return el.hidden || el.hiddenItem; +}