diff --git a/packages/calcite-components/src/components.d.ts b/packages/calcite-components/src/components.d.ts index ec69dc49efc..044f72cfb92 100644 --- a/packages/calcite-components/src/components.d.ts +++ b/packages/calcite-components/src/components.d.ts @@ -5751,6 +5751,10 @@ export namespace Components { * In ancestor selection mode, show as indeterminate when only some children are selected. */ "indeterminate": boolean; + /** + * Accessible name for the component. + */ + "label": string; "lines": boolean; "parentExpanded": boolean; "scale": Scale; @@ -13916,6 +13920,10 @@ declare namespace LocalJSX { * In ancestor selection mode, show as indeterminate when only some children are selected. */ "indeterminate"?: boolean; + /** + * Accessible name for the component. + */ + "label"?: string; "lines"?: boolean; "onCalciteInternalTreeItemSelect"?: (event: CalciteTreeItemCustomEvent) => void; "parentExpanded"?: boolean; diff --git a/packages/calcite-components/src/components/tree-item/resources.ts b/packages/calcite-components/src/components/tree-item/resources.ts index 31b0d0fb231..9533b3b7d52 100644 --- a/packages/calcite-components/src/components/tree-item/resources.ts +++ b/packages/calcite-components/src/components/tree-item/resources.ts @@ -1,15 +1,16 @@ export const CSS = { actionsEnd: "actions-end", - checkboxLabel: "checkbox-label", + bulletPointIcon: "bullet-point", checkbox: "checkbox", + checkboxContainer: "checkbox-container", + checkboxLabel: "checkbox-label", + checkmarkIcon: "checkmark", chevron: "chevron", - nodeContainer: "node-container", childrenContainer: "children-container", - bulletPointIcon: "bullet-point", - checkmarkIcon: "checkmark", - itemExpanded: "item--expanded", iconStart: "icon-start", + itemExpanded: "item--expanded", nodeAndActionsContainer: "node-actions-container", + nodeContainer: "node-container", }; export const SLOTS = { @@ -18,8 +19,11 @@ export const SLOTS = { }; export const ICONS = { + blank: "blank", bulletPoint: "bullet-point", checkmark: "check", + checkSquareF: "check-square-f", chevronRight: "chevron-right", - blank: "blank", + minusSquareF: "minus-square-f", + square: "square", } as const; diff --git a/packages/calcite-components/src/components/tree-item/tree-item.e2e.ts b/packages/calcite-components/src/components/tree-item/tree-item.e2e.ts index 9257f3c42aa..95f888fcb23 100644 --- a/packages/calcite-components/src/components/tree-item/tree-item.e2e.ts +++ b/packages/calcite-components/src/components/tree-item/tree-item.e2e.ts @@ -1,7 +1,7 @@ import { E2EPage, newE2EPage } from "@stencil/core/testing"; import { accessible, defaults, disabled, hidden, renders, slots } from "../../tests/commonTests"; import { html } from "../../../support/formatting"; -import { SLOTS } from "./resources"; +import { CSS, SLOTS } from "./resources"; describe("calcite-tree-item", () => { describe("renders", () => { @@ -331,8 +331,9 @@ describe("calcite-tree-item", () => { `); const container = await page.find("calcite-tree-item >>> .node-container"); - const label = await container.find("label"); - const checkbox = await label.find("calcite-checkbox"); + const checkbox = await container.find(`.${CSS.checkbox}`); + const host = await page.find("calcite-tree-item[role='treeitem']"); + const label = await container.find(`label`); const icon = await container.find(`[data-test-id="icon"]`); await icon.click(); @@ -342,27 +343,63 @@ describe("calcite-tree-item", () => { expect(isVisible).toBe(true); await container.click(); - expect(checkbox).toHaveAttribute("checked"); + expect(host).toEqualAttribute("aria-checked", true); expect(isVisible).toBe(true); await container.click(); - expect(checkbox).not.toHaveAttribute("checked"); + expect(host).toEqualAttribute("aria-checked", false); expect(isVisible).toBe(true); await label.click(); - expect(checkbox).toHaveAttribute("checked"); + expect(host).toEqualAttribute("aria-checked", true); expect(isVisible).toBe(true); await label.click(); - expect(checkbox).not.toHaveAttribute("checked"); + expect(host).toEqualAttribute("aria-checked", false); expect(isVisible).toBe(true); await checkbox.click(); - expect(checkbox).toHaveAttribute("checked"); + expect(host).toEqualAttribute("aria-checked", true); expect(isVisible).toBe(true); await checkbox.click(); - expect(checkbox).not.toHaveAttribute("checked"); + expect(host).toEqualAttribute("aria-checked", false); expect(isVisible).toBe(true); }); + it('should contain aria-selected attribute when selectionMode is "single". Also applies to selectionMode: "children" and "single-persist"', async () => { + const page = await newE2EPage(); + await page.setContent(html` + + + Child 1 + + + Child 2 + + + `); + + const host = await page.find("calcite-tree-item[role='treeitem']"); + + expect(host).toHaveAttribute("aria-selected"); + }); + + it('should contain aria-checked attribute when selectionMode is "multiple". Also applies to selectionMode: "multichildren" and "ancestors"', async () => { + const page = await newE2EPage(); + await page.setContent(html` + + + Child 1 + + + Child 2 + + + `); + + const host = await page.find("calcite-tree-item[role='treeitem']"); + + expect(host).toHaveAttribute("aria-checked"); + }); + it("displaying an expanded item is visible", async () => { const page = await newE2EPage(); await page.setContent(html` diff --git a/packages/calcite-components/src/components/tree-item/tree-item.scss b/packages/calcite-components/src/components/tree-item/tree-item.scss index ec63fb81660..5707586a6da 100644 --- a/packages/calcite-components/src/components/tree-item/tree-item.scss +++ b/packages/calcite-components/src/components/tree-item/tree-item.scss @@ -128,24 +128,30 @@ .node-container { @apply focus-inset outline-none; } + + .checkbox { + @apply outline-none; + } } .actions-end { @apply flex self-stretch flex-row items-center; } +.checkbox-container { + display: flex; + align-items: center; +} + .checkbox { line-height: 0; + color: var(--calcite-color-border-input); } .checkbox-label { @apply pointer-events-none flex items-center; } -.checkbox:focus { - @apply outline-none; -} - .children-container { @apply relative h-0 overflow-hidden opacity-0 origin-top; margin-inline-start: theme("margin.5"); @@ -253,6 +259,15 @@ .bullet-point { color: var(--calcite-color-brand); } + .checkbox { + color: var(--calcite-color-brand); + } +} + +:host([has-children][indeterminate]) { + .checkbox { + color: var(--calcite-color-brand); + } } @include base-component(); diff --git a/packages/calcite-components/src/components/tree-item/tree-item.tsx b/packages/calcite-components/src/components/tree-item/tree-item.tsx index 296592cb9da..72ea3c64cc8 100644 --- a/packages/calcite-components/src/components/tree-item/tree-item.tsx +++ b/packages/calcite-components/src/components/tree-item/tree-item.tsx @@ -59,6 +59,9 @@ export class TreeItem implements ConditionalSlotComponent, InteractiveComponent */ @Prop({ reflect: true }) disabled = false; + /** Accessible name for the component. */ + @Prop() label: string; + /** When `true`, the component is expanded. */ @Prop({ mutable: true, reflect: true }) expanded = false; @@ -210,6 +213,7 @@ export class TreeItem implements ConditionalSlotComponent, InteractiveComponent const showCheckmark = this.selectionMode === "multiple" || this.selectionMode === "multichildren"; const showBlank = this.selectionMode === "none" && !this.hasChildren; + const checkboxIsIndeterminate = this.hasChildren && this.indeterminate; const chevron = this.hasChildren ? ( - + - {defaultSlotNode} - + + ) : null; const selectedIcon = showBulletPoint ? ICONS.bulletPoint @@ -280,9 +287,23 @@ export class TreeItem implements ConditionalSlotComponent, InteractiveComponent return ( { `, }); - const checkbox = await page.find( - `calcite-tree-item >>> .${CSS.nodeContainer} .${CSS.checkboxLabel} .${CSS.checkbox}`, - ); + const checkbox = await page.find(`calcite-tree-item >>> .${CSS.nodeContainer} .${CSS.checkboxContainer}`); expect(checkbox).not.toBeNull(); }); }); diff --git a/packages/calcite-components/src/components/tree/tree.stories.ts b/packages/calcite-components/src/components/tree/tree.stories.ts index eb3f7637fc5..94a2de250f0 100644 --- a/packages/calcite-components/src/components/tree/tree.stories.ts +++ b/packages/calcite-components/src/components/tree/tree.stories.ts @@ -31,32 +31,32 @@ export default { }; const treeItems = html` - + Child 1 - + Child 2 - + Grandchild 1 - + Grandchild 2 - + Great-Grandchild 1 - + Great-Grandchild 2 - + Child 3 - + Grandchild 1 @@ -106,17 +106,17 @@ const slottedSmallDropdown = html` `; const iconStartLargeActionsEnd = html` - + Child 1 ${slottedLargeDropdown} ${slottedLargeDropdown} - + Child 2 - + Grandchild 1 - + Great-Grandchild 1 ${slottedLargeDropdown}${slottedLargeDropdown} @@ -124,14 +124,14 @@ const iconStartLargeActionsEnd = html` - + Child 3 ${slottedLargeDropdown} - + Grandchild 1 - + Grandchild 2 ${slottedLargeDropdown} @@ -140,16 +140,16 @@ const iconStartLargeActionsEnd = html` `; const slottedDefaultActionsEnd = html` - + Child 1 - + Child 2 - + Grandchild 1 - + Great-Grandchild 1 ${slottedDefaultDropdown}${slottedDefaultDropdown} @@ -160,16 +160,16 @@ const slottedDefaultActionsEnd = html` `; const slottedSmallActionsEnd = html` - + Child 1 - + Child 2 - + Grandchild 1 - + Great-Grandchild 1 ${slottedSmallDropdown}${slottedSmallDropdown} @@ -189,18 +189,18 @@ export const selectionModeNone = (): string => html` html` - Child 1 - + Child 1 + Child 2 - Grandchild 1 - Grandchild 2 - + Grandchild 1 + Grandchild 2 + Grandchild 3 - Great-Grandchild 1 - Great-Grandchild 2 - Great-Grandchild 3 + Great-Grandchild 1 + Great-Grandchild 2 + Great-Grandchild 3 @@ -218,7 +218,7 @@ export const iconStartAndActionsEnd = (): string => html` export const treeItemTextContentWraps_TestOnly = (): string => html` - + Possibly_long_tree_item_name_because_it_is_a_user_generated_layer_name @@ -231,14 +231,14 @@ export const treeItemContentIsNotClipped_TestOnly = (): string => html` } - +
content from tree item below should not be clipped 👇✂️ 🚫clipped ✂️
- +
value @@ -264,10 +264,10 @@ darkModeRTL_TestOnly.parameters = { themes: modesDarkDefault }; export const OverflowingSubtree = (): string => html`
- + Layer 2 - + Layer 2.1 - + Layer 3 @@ -301,22 +301,22 @@ export const OverflowingSubtree = (): string => export const allSelectionModesExpanded_TestOnly = (): string => html`

ancestors

- Child 1 + Child 1 - + Child 2 - Grandchild 1 + Grandchild 1 - Grandchild 2 + Grandchild 2 - + Grandchild 3 - Great-Grandchild 1 - Great-Grandchild 2 - Great-Grandchild 3 + Great-Grandchild 1 + Great-Grandchild 2 + Great-Grandchild 3 @@ -325,22 +325,22 @@ export const allSelectionModesExpanded_TestOnly = (): string => html`

children

- Child 1 + Child 1 - + Child 2 - Grandchild 1 + Grandchild 1 - Grandchild 2 + Grandchild 2 - + Grandchild 3 - Great-Grandchild 1 - Great-Grandchild 2 - Great-Grandchild 3 + Great-Grandchild 1 + Great-Grandchild 2 + Great-Grandchild 3 @@ -349,22 +349,22 @@ export const allSelectionModesExpanded_TestOnly = (): string => html`

multichildren

- Child 1 + Child 1 - + Child 2 - Grandchild 1 + Grandchild 1 - Grandchild 2 + Grandchild 2 - + Grandchild 3 - Great-Grandchild 1 - Great-Grandchild 2 - Great-Grandchild 3 + Great-Grandchild 1 + Great-Grandchild 2 + Great-Grandchild 3 @@ -373,22 +373,22 @@ export const allSelectionModesExpanded_TestOnly = (): string => html`

multiple

- Child 1 + Child 1 - + Child 2 - Grandchild 1 + Grandchild 1 - Grandchild 2 + Grandchild 2 - + Grandchild 3 - Great-Grandchild 1 - Great-Grandchild 2 - Great-Grandchild 3 + Great-Grandchild 1 + Great-Grandchild 2 + Great-Grandchild 3 @@ -397,22 +397,22 @@ export const allSelectionModesExpanded_TestOnly = (): string => html`

none

- Child 1 + Child 1 - + Child 2 - Grandchild 1 + Grandchild 1 - Grandchild 2 + Grandchild 2 - + Grandchild 3 - Great-Grandchild 1 - Great-Grandchild 2 - Great-Grandchild 3 + Great-Grandchild 1 + Great-Grandchild 2 + Great-Grandchild 3 @@ -421,22 +421,22 @@ export const allSelectionModesExpanded_TestOnly = (): string => html`

single

- Child 1 + Child 1 - + Child 2 - Grandchild 1 + Grandchild 1 - Grandchild 2 + Grandchild 2 - + Grandchild 3 - Great-Grandchild 1 - Great-Grandchild 2 - Great-Grandchild 3 + Great-Grandchild 1 + Great-Grandchild 2 + Great-Grandchild 3 @@ -445,22 +445,22 @@ export const allSelectionModesExpanded_TestOnly = (): string => html`

single-persist

- Child 1 + Child 1 - + Child 2 - Grandchild 1 + Grandchild 1 - Grandchild 2 + Grandchild 2 - + Grandchild 3 - Great-Grandchild 1 - Great-Grandchild 2 - Great-Grandchild 3 + Great-Grandchild 1 + Great-Grandchild 2 + Great-Grandchild 3 diff --git a/packages/calcite-components/src/demos/tree.html b/packages/calcite-components/src/demos/tree.html index 6f6c9984966..e455bacea52 100644 --- a/packages/calcite-components/src/demos/tree.html +++ b/packages/calcite-components/src/demos/tree.html @@ -376,41 +376,41 @@

Tree

- + Child 1 - + Child 2 - + Grandchild 1 - + Great Grandchild 1 - + Great Grandchild 2 - + Great Grandchild 3 - + Grandchild 2 - + Grandchild 3 - + Child 3 - + Child 4 @@ -418,41 +418,41 @@

Tree

- + Child 1 - + Child 2 - + Grandchild 1 - + Great Grandchild 1 - + Great Grandchild 2 - + Great Grandchild 3 - + Grandchild 2 - + Grandchild 3 - + Child 3 - + Child 4