diff --git a/packages/ui-components/src/components/UIComboBox/UIComboBox.tsx b/packages/ui-components/src/components/UIComboBox/UIComboBox.tsx index d7a0a1191c..dbff7a75f2 100644 --- a/packages/ui-components/src/components/UIComboBox/UIComboBox.tsx +++ b/packages/ui-components/src/components/UIComboBox/UIComboBox.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import type { IComboBoxProps, IComboBoxState, IAutofillProps, IButtonProps, ICalloutProps } from '@fluentui/react'; +import type { IComboBoxProps, IComboBoxState, IAutofillProps, IButtonProps } from '@fluentui/react'; import { ComboBox, IComboBox, @@ -18,7 +18,7 @@ import { UiIcons } from '../Icons'; import type { UIMessagesExtendedProps, InputValidationMessageInfo } from '../../helper/ValidationMessage'; import { getMessageInfo, MESSAGE_TYPES_CLASSNAME_MAP } from '../../helper/ValidationMessage'; import { labelGlobalStyle } from '../UILabel'; -import { isDropdownEmpty, getCalloutCollisionTransformationProps } from '../UIDropdown'; +import { isDropdownEmpty, getCalloutCollisionTransformationPropsForDropdown } from '../UIDropdown'; import { CalloutCollisionTransform } from '../UICallout'; import { isHTMLInputElement } from '../../utilities'; @@ -116,7 +116,7 @@ export class UIComboBox extends React.Component = ['Meta', 'Control', 'Shift', 'Tab', 'Alt', 'CapsLock']; private isListHidden = false; - private calloutCollisionTransform = new CalloutCollisionTransform(this.comboboxDomRef, this.menuDomRef); + public readonly calloutCollisionTransform = new CalloutCollisionTransform(this.comboboxDomRef, this.menuDomRef); /** * Initializes component properties. @@ -139,9 +139,6 @@ export class UIComboBox extends React.Component | React.KeyboardEvent | React.MouseEvent - ): boolean { - let preventDismiss = false; - if (this.props.calloutProps?.preventDismissOnEvent) { - preventDismiss = this.props.calloutProps.preventDismissOnEvent(event); - } - if (!preventDismiss) { - const { preventDismissOnEvent } = this.getCalloutCollisionTransformationProps(); - if (preventDismissOnEvent) { - return preventDismissOnEvent(event); - } - } - return preventDismiss; - } - - /** - * Callback for when the callout layer is mounted. - */ - private onLayerDidMount(): void { - const { layerProps } = this.getCalloutCollisionTransformationProps(); - if (this.props.calloutProps?.layerProps?.onLayerDidMount) { - this.props.calloutProps?.layerProps?.onLayerDidMount(); - } - if (layerProps?.onLayerDidMount) { - layerProps.onLayerDidMount(); - } - } - - /** - * Callback for when the callout layer is unmounted. - */ - private onLayerWillUnmount(): void { - const { layerProps } = this.getCalloutCollisionTransformationProps(); - if (this.props.calloutProps?.layerProps?.onLayerWillUnmount) { - this.props.calloutProps?.layerProps?.onLayerWillUnmount(); - } - if (layerProps?.onLayerWillUnmount) { - layerProps.onLayerWillUnmount(); - } - } - /** * @returns {JSX.Element} */ @@ -830,12 +764,7 @@ export class UIComboBox extends React.Component { private dropdownDomRef = React.createRef(); private menuDomRef = React.createRef(); - private calloutCollisionTransform = new CalloutCollisionTransform(this.dropdownDomRef, this.menuDomRef); + public readonly calloutCollisionTransform = new CalloutCollisionTransform(this.dropdownDomRef, this.menuDomRef); /** * Initializes component properties. @@ -78,9 +77,6 @@ export class UIDropdown extends React.Component { @@ -286,69 +282,6 @@ export class UIDropdown extends React.Component | React.KeyboardEvent | React.MouseEvent - ): boolean { - let preventDismiss = false; - if (this.props.calloutProps?.preventDismissOnEvent) { - preventDismiss = this.props.calloutProps.preventDismissOnEvent(event); - } - if (!preventDismiss) { - const { preventDismissOnEvent } = this.getCalloutCollisionTransformationProps(); - if (preventDismissOnEvent) { - return preventDismissOnEvent(event); - } - } - return preventDismiss; - } - - /** - * Callback for when the callout layer is mounted. - */ - private onLayerDidMount(): void { - const { layerProps } = this.getCalloutCollisionTransformationProps(); - if (this.props.calloutProps?.layerProps?.onLayerDidMount) { - this.props.calloutProps?.layerProps?.onLayerDidMount(); - } - if (layerProps?.onLayerDidMount) { - layerProps.onLayerDidMount(); - } - } - - /** - * Callback for when the callout layer is unmounted. - */ - private onLayerWillUnmount(): void { - const { layerProps } = this.getCalloutCollisionTransformationProps(); - if (this.props.calloutProps?.layerProps?.onLayerWillUnmount) { - this.props.calloutProps?.layerProps?.onLayerWillUnmount(); - } - if (layerProps?.onLayerWillUnmount) { - layerProps.onLayerWillUnmount(); - } - } - /** * @returns {JSX.Element} */ @@ -397,12 +330,7 @@ export class UIDropdown extends React.Component void { + return () => { + const { layerProps } = + getCalloutCollisionTransformationProps( + dropdown.calloutCollisionTransform, + dropdown.props.multiSelect, + dropdown.props.calloutCollisionTransformation + ) ?? {}; + if (dropdown.props.calloutProps?.layerProps?.onLayerDidMount) { + dropdown.props.calloutProps?.layerProps?.onLayerDidMount(); + } + if (layerProps?.onLayerDidMount) { + layerProps.onLayerDidMount(); + } + }; +} + +/** + * Method returns callback function to 'onLayerWillUnmount' property of dropdown 'callout'. + * + * @param dropdown Instance of dropdown. + * @returns Returns callback function to 'onLayerWillUnmount' property of dropdown 'callout'. + */ +function getOnLayerWillUnmount(dropdown: UIDropdown | UIComboBox): () => void { + return () => { + const { layerProps } = + getCalloutCollisionTransformationProps( + dropdown.calloutCollisionTransform, + dropdown.props.multiSelect, + dropdown.props.calloutCollisionTransformation + ) ?? {}; + if (dropdown.props.calloutProps?.layerProps?.onLayerWillUnmount) { + dropdown.props.calloutProps?.layerProps?.onLayerWillUnmount(); + } + if (layerProps?.onLayerWillUnmount) { + layerProps.onLayerWillUnmount(); + } + }; +} + +/** + * Method returns callback function to 'preventDismissOnEvent' property of dropdown 'callout', which prevents callout dismiss/close if focus/click on target elements. + * + * @param dropdown Instance of dropdown. + * @returns Returns callback function to 'preventDismissOnEvent' property of dropdown 'callout'. + */ +function getPreventDismissOnEvent( + dropdown: UIDropdown | UIComboBox +): ( + event: Event | React.FocusEvent | React.KeyboardEvent | React.MouseEvent +) => boolean { + return (event) => { + let preventDismiss = false; + if (dropdown.props.calloutProps?.preventDismissOnEvent) { + preventDismiss = dropdown.props.calloutProps.preventDismissOnEvent(event); + } + if (!preventDismiss) { + const { preventDismissOnEvent } = + getCalloutCollisionTransformationProps( + dropdown.calloutCollisionTransform, + dropdown.props.multiSelect, + dropdown.props.calloutCollisionTransformation + ) ?? {}; + if (preventDismissOnEvent) { + return preventDismissOnEvent(event); + } + } + return preventDismiss; + }; +} + +/** + * Method returns additional callout props for callout collision transformation if feature is enabled. + * Callout collision transformation checks if dropdown menu overlaps with dialog action/submit buttons + * and if overlap happens, then additional offset is applied to make action buttons visible. + * + * @param dropdown Instance of dropdown. + * @returns Callout props to enable callout collision transformation. + */ +export function getCalloutCollisionTransformationPropsForDropdown( + dropdown: UIDropdown | UIComboBox +): ICalloutProps | undefined { + if (dropdown.props.multiSelect && dropdown.props.calloutCollisionTransformation) { + return { + preventDismissOnEvent: getPreventDismissOnEvent(dropdown), + layerProps: { + onLayerDidMount: getOnLayerDidMount(dropdown), + onLayerWillUnmount: getOnLayerWillUnmount(dropdown) + } + }; + } + return undefined; +} diff --git a/packages/ui-components/test/unit/components/UICombobox.test.tsx b/packages/ui-components/test/unit/components/UICombobox.test.tsx index a1f68a94ad..86515c4e36 100644 --- a/packages/ui-components/test/unit/components/UICombobox.test.tsx +++ b/packages/ui-components/test/unit/components/UICombobox.test.tsx @@ -792,16 +792,22 @@ describe('', () => { const dropdown = wrapper.find(ComboBox); expect(dropdown.length).toEqual(1); const calloutProps = dropdown.prop('calloutProps'); - expect(calloutProps?.preventDismissOnEvent).toBeDefined(); - expect(calloutProps?.layerProps?.onLayerDidMount).toBeDefined(); - expect(calloutProps?.layerProps?.onLayerWillUnmount).toBeDefined(); - - calloutProps?.preventDismissOnEvent?.({} as Event); - calloutProps?.layerProps?.onLayerDidMount?.(); - calloutProps?.layerProps?.onLayerWillUnmount?.(); - expect(CalloutCollisionTransformSpy.preventDismissOnEvent).toBeCalledTimes(expected ? 1 : 0); - expect(CalloutCollisionTransformSpy.applyTransformation).toBeCalledTimes(expected ? 1 : 0); - expect(CalloutCollisionTransformSpy.resetTransformation).toBeCalledTimes(expected ? 1 : 0); + if (expected) { + expect(calloutProps?.preventDismissOnEvent).toBeDefined(); + expect(calloutProps?.layerProps?.onLayerDidMount).toBeDefined(); + expect(calloutProps?.layerProps?.onLayerWillUnmount).toBeDefined(); + + calloutProps?.preventDismissOnEvent?.({} as Event); + calloutProps?.layerProps?.onLayerDidMount?.(); + calloutProps?.layerProps?.onLayerWillUnmount?.(); + expect(CalloutCollisionTransformSpy.preventDismissOnEvent).toBeCalledTimes(expected ? 1 : 0); + expect(CalloutCollisionTransformSpy.applyTransformation).toBeCalledTimes(expected ? 1 : 0); + expect(CalloutCollisionTransformSpy.resetTransformation).toBeCalledTimes(expected ? 1 : 0); + } else { + expect(calloutProps?.preventDismissOnEvent).toBeUndefined(); + expect(calloutProps?.layerProps?.onLayerDidMount).toBeUndefined(); + expect(calloutProps?.layerProps?.onLayerWillUnmount).toBeUndefined(); + } }); } @@ -823,10 +829,6 @@ describe('', () => { const dropdown = wrapper.find(ComboBox); expect(dropdown.length).toEqual(1); const calloutProps = dropdown.prop('calloutProps'); - // if (expected) { - expect(calloutProps?.preventDismissOnEvent).toBeDefined(); - expect(calloutProps?.layerProps?.onLayerDidMount).toBeDefined(); - expect(calloutProps?.layerProps?.onLayerWillUnmount).toBeDefined(); calloutProps?.preventDismissOnEvent?.({} as Event); calloutProps?.layerProps?.onLayerDidMount?.(); diff --git a/packages/ui-components/test/unit/components/UIDropdown.test.tsx b/packages/ui-components/test/unit/components/UIDropdown.test.tsx index 934c8ad0fb..75187fcc25 100644 --- a/packages/ui-components/test/unit/components/UIDropdown.test.tsx +++ b/packages/ui-components/test/unit/components/UIDropdown.test.tsx @@ -368,16 +368,23 @@ describe('', () => { const dropdown = wrapper.find(Dropdown); expect(dropdown.length).toEqual(1); const calloutProps = dropdown.prop('calloutProps'); - expect(calloutProps?.preventDismissOnEvent).toBeDefined(); - expect(calloutProps?.layerProps?.onLayerDidMount).toBeDefined(); - expect(calloutProps?.layerProps?.onLayerWillUnmount).toBeDefined(); - - calloutProps?.preventDismissOnEvent?.({} as Event); - calloutProps?.layerProps?.onLayerDidMount?.(); - calloutProps?.layerProps?.onLayerWillUnmount?.(); - expect(CalloutCollisionTransformSpy.preventDismissOnEvent).toBeCalledTimes(expected ? 1 : 0); - expect(CalloutCollisionTransformSpy.applyTransformation).toBeCalledTimes(expected ? 1 : 0); - expect(CalloutCollisionTransformSpy.resetTransformation).toBeCalledTimes(expected ? 1 : 0); + + if (expected) { + expect(calloutProps?.preventDismissOnEvent).toBeDefined(); + expect(calloutProps?.layerProps?.onLayerDidMount).toBeDefined(); + expect(calloutProps?.layerProps?.onLayerWillUnmount).toBeDefined(); + + calloutProps?.preventDismissOnEvent?.({} as Event); + calloutProps?.layerProps?.onLayerDidMount?.(); + calloutProps?.layerProps?.onLayerWillUnmount?.(); + expect(CalloutCollisionTransformSpy.preventDismissOnEvent).toBeCalledTimes(1); + expect(CalloutCollisionTransformSpy.applyTransformation).toBeCalledTimes(1); + expect(CalloutCollisionTransformSpy.resetTransformation).toBeCalledTimes(1); + } else { + expect(calloutProps?.preventDismissOnEvent).toBeUndefined(); + expect(calloutProps?.layerProps?.onLayerDidMount).toBeUndefined(); + expect(calloutProps?.layerProps?.onLayerWillUnmount).toBeUndefined(); + } }); }