fuse-angular/src/@fuse/components/sidebar/sidebar.component.ts

630 lines
16 KiB
TypeScript

import { ChangeDetectorRef, Component, ElementRef, HostBinding, HostListener, Input, OnDestroy, OnInit, Renderer2, RendererStyleFlags2, ViewEncapsulation } from '@angular/core';
import { animate, AnimationBuilder, AnimationPlayer, style } from '@angular/animations';
import { ObservableMedia } from '@angular/flex-layout';
import { Subject } from 'rxjs';
import { FuseSidebarService } from './sidebar.service';
import { FuseMatchMediaService } from '@fuse/services/match-media.service';
import { FuseConfigService } from '@fuse/services/config.service';
import { takeUntil } from 'rxjs/internal/operators';
@Component({
selector : 'fuse-sidebar',
templateUrl : './sidebar.component.html',
styleUrls : ['./sidebar.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class FuseSidebarComponent implements OnInit, OnDestroy
{
// Name
@Input()
name: string;
// Position
@Input()
position: 'left' | 'right';
// Open
@HostBinding('class.open')
opened: boolean;
// Locked Open
@Input()
lockedOpen: string;
// isLockedOpen
@HostBinding('class.locked-open')
isLockedOpen: boolean;
// Folded unfolded
@HostBinding('class.unfolded')
unfolded: boolean;
// Invisible overlay
@Input()
invisibleOverlay: boolean;
// Private
private _folded: boolean;
private _fuseConfig: any;
private _wasActive: boolean;
private _backdrop: HTMLElement | null = null;
private _player: AnimationPlayer;
private _unsubscribeAll: Subject<any>;
@HostBinding('class.animations-enabled')
private _animationsEnabled: boolean;
/**
* Constructor
*
* @param {AnimationBuilder} _animationBuilder
* @param {ChangeDetectorRef} _changeDetectorRef
* @param {ElementRef} _elementRef
* @param {FuseConfigService} _fuseConfigService
* @param {FuseMatchMediaService} _fuseMatchMediaService
* @param {FuseSidebarService} _fuseSidebarService
* @param {ObservableMedia} _observableMedia
* @param {Renderer2} _renderer
*/
constructor(
private _animationBuilder: AnimationBuilder,
private _changeDetectorRef: ChangeDetectorRef,
private _elementRef: ElementRef,
private _fuseConfigService: FuseConfigService,
private _fuseMatchMediaService: FuseMatchMediaService,
private _fuseSidebarService: FuseSidebarService,
private _observableMedia: ObservableMedia,
private _renderer: Renderer2
)
{
// Set the defaults
this.folded = false;
this.opened = false;
this.position = 'left';
this.invisibleOverlay = false;
// Set the private defaults
this._animationsEnabled = false;
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
// Folded
@HostBinding('class.folded')
@Input()
set folded(value: boolean)
{
// Only work if the sidebar is not closed
if ( !this.opened )
{
return;
}
// Set the folded
this._folded = value;
// Programmatically add/remove margin to the element
// that comes after or before based on the position
let sibling,
styleRule;
const styleValue = '64px';
// Get the sibling and set the style rule
if ( this.position === 'left' )
{
sibling = this._elementRef.nativeElement.nextElementSibling;
styleRule = 'margin-left';
}
else
{
sibling = this._elementRef.nativeElement.previousElementSibling;
styleRule = 'margin-right';
}
// If there is no sibling, return...
if ( !sibling )
{
return;
}
// If folded...
if ( value )
{
// Set the style
this._renderer.setStyle(sibling, styleRule, styleValue, RendererStyleFlags2.Important + RendererStyleFlags2.DashCase);
}
// If unfolded...
else
{
// Remove the style
this._renderer.removeStyle(sibling, styleRule);
}
}
get folded(): boolean
{
return this._folded;
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Subscribe to config changes
this._fuseConfigService.config
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((config) => {
this._fuseConfig = config;
});
// Register the sidebar
this._fuseSidebarService.register(this.name, this);
// Setup visibility
this._setupVisibility();
// Setup position
this._setupPosition();
// Setup lockedOpen
this._setupLockedOpen();
}
/**
* On destroy
*/
ngOnDestroy(): void
{
// If the sidebar is folded, unfold it to revert modifications
if ( this.folded )
{
this.unfold();
}
// Unregister the sidebar
this._fuseSidebarService.unregister(this.name);
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
/**
* Setup the visibility of the sidebar
*
* @private
*/
private _setupVisibility(): void
{
// Remove the existing box-shadow
this._renderer.setStyle(this._elementRef.nativeElement, 'box-shadow', 'none');
// Make the sidebar invisible
this._renderer.setStyle(this._elementRef.nativeElement, 'visibility', 'hidden');
}
/**
* Setup the sidebar position
*
* @private
*/
private _setupPosition(): void
{
// Add the correct class name to the sidebar
// element depending on the position attribute
if ( this.position === 'right' )
{
this._renderer.addClass(this._elementRef.nativeElement, 'right-positioned');
}
else
{
this._renderer.addClass(this._elementRef.nativeElement, 'left-positioned');
}
}
/**
* Setup the lockedOpen handler
*
* @private
*/
private _setupLockedOpen(): void
{
// Return if the lockedOpen wasn't set
if ( !this.lockedOpen )
{
// Return
return;
}
// Set the wasActive for the first time
this._wasActive = false;
// Show the sidebar
this._showSidebar();
// Act on every media change
this._fuseMatchMediaService.onMediaChange
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(() => {
// Get the active status
const isActive = this._observableMedia.isActive(this.lockedOpen);
// If the both status are the same, don't act
if ( this._wasActive === isActive )
{
return;
}
// Activate the lockedOpen
if ( isActive )
{
// Set the lockedOpen status
this.isLockedOpen = true;
// Show the sidebar
this._showSidebar();
// Force the the opened status to true
this.opened = true;
// Read the folded setting from the config
// and fold the sidebar if it's true
if ( this._fuseConfig.layout.navbar.folded )
{
this.fold();
}
// Hide the backdrop if any exists
this._hideBackdrop();
}
// De-Activate the lockedOpen
else
{
// Set the lockedOpen status
this.isLockedOpen = false;
// Unfold the sidebar in case if it was folded
this.unfold();
// Force the the opened status to close
this.opened = false;
// Hide the sidebar
this._hideSidebar();
}
// Store the new active status
this._wasActive = isActive;
});
}
/**
* Show the backdrop
*
* @private
*/
private _showBackdrop(): void
{
// Create the backdrop element
this._backdrop = this._renderer.createElement('div');
// Add a class to the backdrop element
this._backdrop.classList.add('fuse-sidebar-overlay');
// Add a class depending on the invisibleOverlay option
if ( this.invisibleOverlay )
{
this._backdrop.classList.add('fuse-sidebar-overlay-invisible');
}
// Append the backdrop to the parent of the sidebar
this._renderer.appendChild(this._elementRef.nativeElement.parentElement, this._backdrop);
// Create the enter animation and attach it to the player
this._player =
this._animationBuilder
.build([
animate('300ms ease', style({opacity: 1}))
]).create(this._backdrop);
// Play the animation
this._player.play();
// Add an event listener to the overlay
this._backdrop.addEventListener('click', () => {
this.close();
}
);
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Hide the backdrop
*
* @private
*/
private _hideBackdrop(): void
{
if ( !this._backdrop )
{
return;
}
// Create the leave animation and attach it to the player
this._player =
this._animationBuilder
.build([
animate('300ms ease', style({opacity: 0}))
]).create(this._backdrop);
// Play the animation
this._player.play();
// Once the animation is done...
this._player.onDone(() => {
// If the backdrop still exists...
if ( this._backdrop )
{
// Remove the backdrop
this._backdrop.parentNode.removeChild(this._backdrop);
this._backdrop = null;
}
});
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Change some properties of the sidebar
* and make it visible
*
* @private
*/
private _showSidebar(): void
{
// Remove the box-shadow style
this._renderer.removeStyle(this._elementRef.nativeElement, 'box-shadow');
// Make the sidebar invisible
this._renderer.removeStyle(this._elementRef.nativeElement, 'visibility');
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Change some properties of the sidebar
* and make it invisible
*
* @private
*/
private _hideSidebar(delay = true): void
{
const delayAmount = delay ? 300 : 0;
// Add a delay so close animation can play
setTimeout(() => {
// Remove the box-shadow
this._renderer.setStyle(this._elementRef.nativeElement, 'box-shadow', 'none');
// Make the sidebar invisible
this._renderer.setStyle(this._elementRef.nativeElement, 'visibility', 'hidden');
}, delayAmount);
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Enable the animations
*
* @private
*/
private _enableAnimations(): void
{
// Return if animations already enabled
if ( this._animationsEnabled )
{
return;
}
// Enable the animations
this._animationsEnabled = true;
// Mark for check
this._changeDetectorRef.markForCheck();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Open the sidebar
*/
open(): void
{
if ( this.opened || this.isLockedOpen )
{
return;
}
// Enable the animations
this._enableAnimations();
// Show the sidebar
this._showSidebar();
// Show the backdrop
this._showBackdrop();
// Set the opened status
this.opened = true;
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Close the sidebar
*/
close(): void
{
if ( !this.opened || this.isLockedOpen )
{
return;
}
// Enable the animations
this._enableAnimations();
// Hide the backdrop
this._hideBackdrop();
// Set the opened status
this.opened = false;
// Hide the sidebar
this._hideSidebar();
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Toggle open/close the sidebar
*/
toggleOpen(): void
{
if ( this.opened )
{
this.close();
}
else
{
this.open();
}
}
/**
* Mouseenter
*/
@HostListener('mouseenter')
onMouseEnter(): void
{
// Only work if the sidebar is folded
if ( !this.folded )
{
return;
}
// Enable the animations
this._enableAnimations();
// Unfold the sidebar temporarily
this.unfolded = true;
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Mouseleave
*/
@HostListener('mouseleave')
onMouseLeave(): void
{
// Only work if the sidebar is folded
if ( !this.folded )
{
return;
}
// Enable the animations
this._enableAnimations();
// Fold the sidebar back
this.unfolded = false;
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Fold the sidebar permanently
*/
fold(): void
{
// Only work if the sidebar is not folded
if ( this.folded )
{
return;
}
// Enable the animations
this._enableAnimations();
// Fold
this.folded = true;
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Unfold the sidebar permanently
*/
unfold(): void
{
// Only work if the sidebar is folded
if ( !this.folded )
{
return;
}
// Enable the animations
this._enableAnimations();
// Unfold
this.folded = false;
// Mark for check
this._changeDetectorRef.markForCheck();
}
/**
* Toggle the sidebar fold/unfold permanently
*/
toggleFold(): void
{
if ( this.folded )
{
this.unfold();
}
else
{
this.fold();
}
}
}