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/components/tree-item/tree-item.tsx b/packages/calcite-components/src/components/tree-item/tree-item.tsx index 5977b27eecb..3dd14f10874 100644 --- a/packages/calcite-components/src/components/tree-item/tree-item.tsx +++ b/packages/calcite-components/src/components/tree-item/tree-item.tsx @@ -14,6 +14,7 @@ import { InteractiveContainer, updateHostInteraction, } from "../../utils/interactive"; +import { onToggleOpenCloseComponent, OpenCloseComponent } from "../../utils/openCloseComponent"; import { CSS_UTILITY } from "../../utils/resources"; import { FlipContext, Scale, SelectionMode } from "../interfaces"; import { getIconScale } from "../../utils/component"; @@ -34,7 +35,7 @@ declare global { * @slot children - A slot for adding nested `calcite-tree` elements. * @slot actions-end - A slot for adding actions to the end of the component. It is recommended to use two or fewer actions. */ -export class TreeItem extends LitElement implements InteractiveComponent { +export class TreeItem extends LitElement implements InteractiveComponent, OpenCloseComponent { // #region Static Members static override styles = styles; @@ -53,6 +54,10 @@ export class TreeItem extends LitElement implements InteractiveComponent { private userChangedValue = false; + transitionProp = "opacity"; + + transitionEl: HTMLDivElement; + // #endregion // #region State Properties @@ -121,6 +126,18 @@ export class TreeItem extends LitElement implements InteractiveComponent { /** @private */ calciteInternalTreeItemSelect = createEvent({ cancelable: false }); + /** Fires when the component is requested to be collapsed and before the closing transition begins. */ + calciteTreeItemBeforeCollapsed = createEvent({ cancelable: false }); + + /** Fires when the component is added to the DOM but not rendered, and before the opening transition begins. */ + calciteTreeItemBeforeExpanded = createEvent({ cancelable: false }); + + /** Fires when the component is collapsed and animation is complete. */ + calciteTreeItemCollapsed = createEvent({ cancelable: false }); + + /** Fires when the component is expanded and animation is complete. */ + calciteTreeItemExpanded = createEvent({ cancelable: false }); + // #endregion // #region Lifecycle @@ -137,6 +154,7 @@ export class TreeItem extends LitElement implements InteractiveComponent { load(): void { requestAnimationFrame(() => (this.updateAfterInitialRender = true)); + onToggleOpenCloseComponent(this); } override willUpdate(changes: PropertyValues): void { @@ -147,6 +165,7 @@ export class TreeItem extends LitElement implements InteractiveComponent { Docs: https://qawebgis.esri.com/arcgis-components/?path=/docs/lumina-transition-from-stencil--docs#watching-for-property-changes */ if (changes.has("expanded") && (this.hasUpdated || this.expanded !== false)) { this.updateChildTree(); + onToggleOpenCloseComponent(this); } if (changes.has("selected") && (this.hasUpdated || this.selected !== false)) { @@ -208,6 +227,7 @@ export class TreeItem extends LitElement implements InteractiveComponent { private iconClickHandler(event: MouseEvent): void { event.stopPropagation(); this.expanded = !this.expanded; + onToggleOpenCloseComponent(this); } private childrenClickHandler(event: MouseEvent): void { @@ -230,6 +250,8 @@ export class TreeItem extends LitElement implements InteractiveComponent { break; case "Enter": { // activates a node, i.e., performs its default action. For parent nodes, one possible default action is to open or close the node. In single-select trees where selection does not follow focus (see note below), the default action is typically to select the focused node. + onToggleOpenCloseComponent(this); + const link = Array.from(this.el.children).find((el) => el.matches("a"), ) as HTMLAnchorElement; @@ -245,6 +267,7 @@ export class TreeItem extends LitElement implements InteractiveComponent { updateItem: true, }); } + event.preventDefault(); } } @@ -340,6 +363,26 @@ export class TreeItem extends LitElement implements InteractiveComponent { } } + private setTransitionEl(el: HTMLDivElement): void { + this.transitionEl = el; + } + + onBeforeExpanded(): void { + this.calciteTreeItemBeforeExpanded.emit(); + } + + onExpanded(): void { + this.calciteTreeItemExpanded.emit(); + } + + onBeforeCollapsed(): void { + this.calciteTreeItemBeforeCollapsed.emit(); + } + + onCollapsed(): void { + this.calciteTreeItemCollapsed.emit(); + } + // #endregion // #region Rendering @@ -483,6 +526,7 @@ export class TreeItem extends LitElement implements InteractiveComponent { }} data-test-id="calcite-tree-children" onClick={this.childrenClickHandler} + ref={this.setTransitionEl} role={this.hasChildren ? "group" : undefined} > diff --git a/packages/calcite-components/src/demos/tree.html b/packages/calcite-components/src/demos/tree.html index ba5fb9b300b..32464b5fb3b 100644 --- a/packages/calcite-components/src/demos/tree.html +++ b/packages/calcite-components/src/demos/tree.html @@ -51,7 +51,7 @@

Tree

Child 2 - + Grandchild 1 Grandchild 2 @@ -67,6 +67,29 @@

Tree

+
diff --git a/packages/calcite-components/src/utils/openCloseComponent.ts b/packages/calcite-components/src/utils/openCloseComponent.ts index 64a65bec863..5c73b8747c2 100644 --- a/packages/calcite-components/src/utils/openCloseComponent.ts +++ b/packages/calcite-components/src/utils/openCloseComponent.ts @@ -10,6 +10,15 @@ export interface OpenCloseComponent { /** The host element. */ readonly el: HTMLElement; + /** When true, the component closes. */ + closed?: boolean; + + /** When true, the component collapses. */ + collapsed?: boolean; + + /** When true, the component is expanded. */ + expanded?: boolean; + /** * Specifies property on which active transition is watched for. * @@ -24,16 +33,28 @@ export interface OpenCloseComponent { transitionEl: HTMLElement; /** Defines method for `beforeOpen` event handler. */ - onBeforeOpen: () => void; + onBeforeOpen?: () => void; /** Defines method for `open` event handler: */ - onOpen: () => void; + onOpen?: () => void; /** Defines method for `beforeClose` event handler: */ - onBeforeClose: () => void; + onBeforeClose?: () => void; /** Defines method for `close` event handler: */ - onClose: () => void; + onClose?: () => void; + + /** Defines method for `beforeExpanded` event handler. */ + onBeforeExpanded?: () => void; + + /** Defines method for `expanded` event handler: */ + onExpanded?: () => void; + + /** Defines method for `beforeCollapsed` event handler: */ + onBeforeCollapsed?: () => void; + + /** Defines method for `collapsed` event handler: */ + onCollapsed?: () => void; } function isOpen(component: OpenCloseComponent): boolean { @@ -56,7 +77,7 @@ function isOpen(component: OpenCloseComponent): boolean { * async toggleModal(value: boolean): Promise { * onToggleOpenCloseComponent(this); * } - * @param component - OpenCloseComponent uses `open` prop to emit (before)open/close. + * @param component - OpenCloseComponent uses `open`/`close` or `expanded`/`collapsed` props to emit (before)open/close or (before)expanded/collapsed respectively. */ export function onToggleOpenCloseComponent(component: OpenCloseComponent): void { requestAnimationFrame((): void => {