UNPKG

outsystems-ui

Version:
382 lines (344 loc) 11 kB
// eslint-disable-next-line @typescript-eslint/no-unused-vars namespace OSFramework.OSUI.Patterns.OverflowMenu { /** * Class for the OverflowMenu Pattern * * @export * @class OverflowMenu * @extends {AbstractPattern<OverflowMenuConfig>} * @implements {IOverflowMenu} */ export class OverflowMenu extends AbstractPattern<OverflowMenuConfig> implements IOverflowMenu { // Store the aria-label text used on the Trigger Element private _ariaLabelTrigger: string; // Store the Balloon Element private _balloonElem: HTMLElement; // Store the Balloon Class instance private _balloonFeature: Feature.Balloon.IBalloon; // Store the Event Callbacks private _eventBalloonOnToggle: GlobalCallbacks.Generic; private _eventOnClick: GlobalCallbacks.Generic; private _eventOnKeydown: GlobalCallbacks.Generic; // Flag used to set the enable/disable state private _isDisabled = false; // Flag used to deal with onBodyClick and open api concurrency methods! private _isOpenedByApi = false; // Store the platform events private _platformEventOnToggle: Callbacks.OSOnToggleEvent; // Store the element that triggers the balloon private _triggerElem: HTMLElement; // Store the Balloon options to pass to the Balloon Class public balloonOptions: Feature.Balloon.BalloonOptions; // Store the isOpen status public isOpen = false; constructor(uniqueId: string, configs: JSON) { super(uniqueId, new OverflowMenuConfig(configs)); } // Method to handle the custom BalloonOnToggle callback private _balloonOnToggleCallback(_args: string, e: CustomEvent): void { // If the balloon closed is the one from this pattern, toggle the OverflowMenu if (e.detail.balloonElem === this._balloonElem) { this._togglePattern(e.detail.isOpen); } } // Method to handle the trigger click callback private _onClickCallback(): void { if (this._balloonFeature.isOpen) { this.close(); } else { this._isOpenedByApi = false; this.open(this._isOpenedByApi); } } // Method to handle the trigger Keydown callback private _onKeydownCallback(event: KeyboardEvent): void { if ( !this._balloonFeature.isOpen && (event.key === GlobalEnum.Keycodes.ArrowDown || event.key === GlobalEnum.Keycodes.ArrowUp) ) { this._isOpenedByApi = false; this.open(this._isOpenedByApi, event.key); event.preventDefault(); // Prevent scroll } } // Method to call the Balloon Class private _setBalloonFeature(): void { this.setBalloonOptions(); this._balloonFeature = new OSFramework.OSUI.Feature.Balloon.Balloon<IOverflowMenu>( this, this._balloonElem, this.balloonOptions ); } // Method to handle the Shape config css variable private _setOverflowMenuShape(shape?: GlobalEnum.ShapeTypes): void { if (shape !== undefined) { this.configs.Shape = shape; } Helper.Dom.Styles.SetStyleAttribute( this.selfElement, Enum.CssCustomProperties.Shape, `var(--border-radius-${this.configs.Shape})` ); } // Method to toggle the Pattern private _togglePattern(isOpen: boolean): void { if (isOpen) { Helper.Dom.Styles.AddClass(this.selfElement, Enum.CssClass.Open); } else { Helper.Dom.Styles.RemoveClass(this.selfElement, Enum.CssClass.Open); } this.isOpen = isOpen; this.setA11YProperties(); this._triggerOnToggleEvent(); } // Method that triggers the OnToggle event private _triggerOnToggleEvent(): void { this.triggerPlatformEventCallback(this._platformEventOnToggle, this.isOpen); } /** * Method that removes the event listeners * * @protected * @memberof OverflowMenu */ protected removeEventListeners(): void { this._triggerElem.removeEventListener(GlobalEnum.HTMLEvent.Click, this._eventOnClick); this._triggerElem.removeEventListener(GlobalEnum.HTMLEvent.keyDown, this._eventOnKeydown); Event.DOMEvents.Listeners.GlobalListenerManager.Instance.removeHandler( Event.DOMEvents.Listeners.Type.BalloonOnToggle, this._eventBalloonOnToggle ); } /** * Add the Accessibility Attributes values * * @protected * @memberof OSFramework.Patterns.OverflowMenu.OverflowMenu */ protected setA11YProperties(): void { if (this.isBuilt === false) { Helper.A11Y.AriaHasPopup(this._triggerElem, Constants.A11YAttributes.Aria.Haspopup.value.Menu); Helper.A11Y.AriaControls(this._triggerElem, this._balloonElem.id); Helper.A11Y.RoleMenu(this._balloonElem); this.setTriggerAriaLabel(Enum.AriaLabel.Trigger); Helper.Dom.Attribute.Set(this._triggerElem, Constants.FocusTrapIgnoreAttr, true); // Set the attr that will be used to define the default tabindex element Helper.Dom.Attribute.Set(this._triggerElem, Constants.FocusableTabIndexDefault, Constants.EmptyString); } Helper.A11Y.AriaExpanded(this._triggerElem, this.isOpen.toString()); } /** * Set the callbacks * * @protected * @memberof OSFramework.Patterns.OverflowMenu.OverflowMenu */ protected setCallbacks(): void { this._eventBalloonOnToggle = this._balloonOnToggleCallback.bind(this); this._eventOnClick = this._onClickCallback.bind(this); this._eventOnKeydown = this._onKeydownCallback.bind(this); } /** * Method to set the event listeners * * @protected * @memberof OverflowMenu */ protected setEventListeners(): void { this._triggerElem.addEventListener(GlobalEnum.HTMLEvent.Click, this._eventOnClick); this._triggerElem.addEventListener(GlobalEnum.HTMLEvent.keyDown, this._eventOnKeydown); Event.DOMEvents.Listeners.GlobalListenerManager.Instance.addHandler( Event.DOMEvents.Listeners.Type.BalloonOnToggle, this._eventBalloonOnToggle ); } /** * Update info based on htmlContent * * @protected * @memberof OSFramework.Patterns.OverflowMenu.OverflowMenu */ protected setHtmlElements(): void { this._triggerElem = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.Trigger); this._balloonElem = Helper.Dom.ClassSelector(this.selfElement, Enum.CssClass.Balloon); } /** * Method to unset the callbacks. * * @protected * @memberof OSFramework.Patterns.OverflowMenu.OverflowMenu */ protected unsetCallbacks(): void { this._eventBalloonOnToggle = undefined; this._eventOnClick = undefined; } /** * Removes the local value of the variables pointing to HTML elements; * * @protected * @memberof OSFramework.Patterns.OverflowMenu.OverflowMenu */ protected unsetHtmlElements(): void { this._balloonElem = undefined; this._triggerElem = undefined; this._balloonFeature = undefined; } /** * Method to build the OverflowMenu * * @memberof OverflowMenu */ public build(): void { super.build(); this.setHtmlElements(); this.setA11YProperties(); this._setBalloonFeature(); this._setOverflowMenuShape(); this.setCallbacks(); this.setEventListeners(); this.finishBuild(); } /** * Method to change the value of configs/current state. * * @param {string} propertyName * @param {unknown} propertyValue * @memberof OverflowMenu */ public changeProperty(propertyName: string, propertyValue: unknown): void { super.changeProperty(propertyName, propertyValue); if (this.isBuilt) { switch (propertyName) { case Enum.Properties.Position: this._balloonFeature.updatePositionOption(propertyValue as GlobalEnum.FloatingPosition); break; case Enum.Properties.Shape: this._setOverflowMenuShape(propertyValue as GlobalEnum.ShapeTypes); this._balloonFeature.setBalloonShape(propertyValue as GlobalEnum.ShapeTypes); break; } } } /** * Method to close the Pattern * * @memberof OverflowMenu */ public close(): void { if (this._balloonFeature.isOpen) { this._balloonFeature.close(); } } /** * Method to disable the pattern * * @memberof OverflowMenu */ public disable(): void { this._isDisabled = true; this.close(); Helper.Dom.Attribute.Set( this._triggerElem, GlobalEnum.HTMLAttributes.Disabled, OSFramework.OSUI.Constants.EmptyString ); } /** * Method to destroy the Pattern * * @memberof OverflowMenu */ public dispose(): void { this._balloonFeature?.dispose(); this.removeEventListeners(); this.unsetCallbacks(); this.unsetHtmlElements(); super.dispose(); } /** * Method to enable the pattern * * @memberof OverflowMenu */ public enable(): void { this._isDisabled = false; Helper.Dom.Attribute.Remove(this._triggerElem, GlobalEnum.HTMLAttributes.Disabled); } /** * Method to open the Pattern * * @memberof OverflowMenu */ public open( isOpenedByApi: boolean, keyPressed?: GlobalEnum.Keycodes.ArrowDown | GlobalEnum.Keycodes.ArrowUp ): void { if (this._balloonFeature.isOpen === false && this._isDisabled === false) { this._isOpenedByApi = isOpenedByApi; this._balloonFeature.open(this._isOpenedByApi, keyPressed); } } /** * Register the callbacks for the Pattern * * @param {string} eventName * @param {GlobalCallbacks.OSGeneric} callback @memberof OSFramework.Patterns.OverflowMenu.OverflowMenu */ public registerCallback(eventName: string, callback: GlobalCallbacks.OSGeneric): void { switch (eventName) { case Patterns.OverflowMenu.Enum.Events.OnMenuToggle: if (this._platformEventOnToggle === undefined) { this._platformEventOnToggle = callback; } else { console.warn(`The ${GlobalEnum.PatternName.OverflowMenu} already has the toggle callback set.`); } break; default: super.registerCallback(eventName, callback); } } /** * Method to set the Balloon Feature options * * @param {Feature.Balloon.BalloonOptions} [balloonOptions] * @memberof OverflowMenu */ public setBalloonOptions(balloonOptions?: Feature.Balloon.BalloonOptions): void { if (balloonOptions !== undefined) { this.balloonOptions = balloonOptions; } else { // Set focus options to pass to the Balloon feature const _focusOptions = { useFocus: true, }; this.balloonOptions = { alignment: GlobalEnum.FloatingAlignment.Start, allowedPlacements: [ GlobalEnum.FloatingPosition.BottomStart, GlobalEnum.FloatingPosition.BottomEnd, GlobalEnum.FloatingPosition.TopStart, GlobalEnum.FloatingPosition.TopEnd, ], anchorElem: this._triggerElem, position: this.configs.Position, shape: this.configs.Shape, focusOptions: _focusOptions, }; } } /** * Method to set the aria-label text for the Trigger element * * @param {string} ariaLabelText * @memberof OverflowMenu */ public setTriggerAriaLabel(ariaLabelText: string): void { if (ariaLabelText !== Constants.EmptyString) { this._ariaLabelTrigger = ariaLabelText; Helper.A11Y.AriaLabel(this._triggerElem, this._ariaLabelTrigger); } } } }