From 38ba7adb786461c7538ba98d9b216f4fc4c08a89 Mon Sep 17 00:00:00 2001 From: eliza Date: Thu, 9 Jan 2025 14:57:39 -0800 Subject: [PATCH 1/2] feat(tree-item): apply inhanced openCloseComponent interface to tree-item to emit expanded/collapsed events --- .../src/components/tree-item/tree-item.tsx | 46 +++++++++- .../calcite-components/src/demos/tree.html | 25 ++++- .../src/utils/openCloseComponent.ts | 91 +++++++++++++++---- 3 files changed, 140 insertions(+), 22 deletions(-) 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 fe6304b842a..d175ac98ab8 100644 --- a/packages/calcite-components/src/utils/openCloseComponent.ts +++ b/packages/calcite-components/src/utils/openCloseComponent.ts @@ -9,36 +9,65 @@ 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; + /** When true, the component opens. */ open?: boolean; /** When true, the component is open. */ opened?: boolean; - /** Specifies the name of transitionProp. */ + /** The name of the transition to watch for completion. */ transitionProp?: string; - /** Specifies property on which active transition is watched for. */ - openTransitionProp: string; - /** Specifies element that the transition is allowed to emit on. */ 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 { - return "opened" in component ? component.opened : component.open; +function isOpenOrExpanded(component: OpenCloseComponent): boolean { + switch (true) { + case "expanded" in component: + return component.expanded; + case "opened" in component || "open" in component: + return component.open; + case "collapsed" in component: + return component.collapsed; + case "closed" in component: + return component.closed; + default: + return false; + } } /** @@ -57,7 +86,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 => { @@ -67,19 +96,41 @@ export function onToggleOpenCloseComponent(component: OpenCloseComponent): void whenTransitionDone( component.transitionEl, - component.openTransitionProp, + component.transitionProp, () => { - if (isOpen(component)) { - component.onBeforeOpen(); - } else { - component.onBeforeClose(); + if (isOpenOrExpanded(component)) { + switch (true) { + case component.expanded: + component.onBeforeExpanded(); + break; + case component.open: + component.onBeforeOpen(); + break; + case component.collapsed || (component.expanded !== undefined && !component.expanded): + component.onBeforeCollapsed(); + break; + case component.closed || (component.open !== undefined && !component.open): + component.onBeforeClose(); + break; + } } }, () => { - if (isOpen(component)) { - component.onOpen(); - } else { - component.onClose(); + if (isOpenOrExpanded(component)) { + switch (true) { + case component.expanded: + component.onExpanded(); + break; + case component.open: + component.onOpen(); + break; + case component.collapsed || (component.expanded !== undefined && !component.expanded): + component.onCollapsed(); + break; + case component.closed || (component.open !== undefined && !component.open): + component.onClose(); + break; + } } }, ); From 4ea5a32eedc0babeb9b5a0147c2ba27cabdc92ed Mon Sep 17 00:00:00 2001 From: eliza Date: Mon, 27 Jan 2025 15:45:02 -0800 Subject: [PATCH 2/2] roll back unrelated changes --- packages/calcite-components/src/lumina.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/calcite-components/src/lumina.ts b/packages/calcite-components/src/lumina.ts index d289aa77d84..394bb69852e 100644 --- a/packages/calcite-components/src/lumina.ts +++ b/packages/calcite-components/src/lumina.ts @@ -8,4 +8,3 @@ * If you need to provide additional typings, create a separate file. */ /// -///