File

src/components/sq-modal/sq-modal.component.ts

Description

Represents a modal component with customizable options and event handling.

Look the link about the component in original framework and the appearance

See https://css.squidit.com.br/components/modal

Example :
<sq-modal [open]="isModalOpen" (modalClose)="onModalClose()">
  <ng-template #headerModal>
    <h2>Title</h2>
  </ng-template>
  <div>
    <!-- Your content here -->
  </div>
   <ng-template #footerModal>
    Footer
  </ng-template>
</sq-modal>
<button (click)='isModalOpen = true'>Open Modal</button>

Implements

OnChanges OnDestroy

Metadata

Index

Properties
Methods
Inputs
Outputs

Constructor

constructor(documentImported: Document, router: Router, getWindow: GetWindow)

Creates an instance of SqModalComponent.

Parameters :
Name Type Optional Description
documentImported Document No
  • The injected Document object for DOM manipulation.
router Router No
  • The Router service for programmatic navigation.
getWindow GetWindow No
  • The GetWindow service for safely accessing the window object.

Inputs

backdrop
Type : string
Default value : 'static'

Determines whether clicking outside the modal closes it. Options: 'static' (no close), 'true' (close).

backdropClass
Type : string

Additional CSS classes to apply to the modal backdrop element.

bodyBackgroundColor
Type : string
Default value : ''

Determines the body background color.

bodyPadding
Type : string
Default value : '0 1rem'

Determines the body padding.

buttonClose
Type : boolean
Default value : true

Determines whether to display the close button.

footerBackgroundColor
Type : string
Default value : ''

Determines the footer background color.

footerPadding
Type : string
Default value : ''

Determines the footer padding.

headerBackgroundColor
Type : string
Default value : ''

Determines the header background color.

headerPadding
Type : string
Default value : ''

Determines the header padding.

id
Type : string
Default value : `modal-random-id-${(1 + Date.now() + Math.random()).toString().replace('.', '')}`

A unique identifier for the modal component.

modalClass
Type : string

Additional CSS classes to apply to the modal element.

modalSize
Type : "sm" | "md" | "lg" | "xl" | string
Default value : 'md'

The size of the modal, which can be 'sm' (small), 'md' (medium), 'lg' (large), or 'xl' (extra large).

open
Type : boolean

Indicates whether the modal should be open or closed.

Outputs

leftPress
Type : EventEmitter<void>

Event emitted when the left arrow key is pressed while the modal is open.

modalClose
Type : EventEmitter<void>

Event emitted when the modal is closed.

rightPress
Type : EventEmitter<void>

Event emitted when the right arrow key is pressed while the modal is open.

Methods

events
events(key: string)

Handles specific keyboard events.

Parameters :
Name Type Optional Description
key string No
  • The key code of the pressed key.
Returns : void
Async ngOnChanges
ngOnChanges(changes: SimpleChanges)

Lifecycle hook that detects changes to the 'open' input property and handles modal behavior accordingly.

Parameters :
Name Type Optional Description
changes SimpleChanges No
  • The changes detected in the component's input properties.
Returns : any
ngOnDestroy
ngOnDestroy()

Performs actions before the component is destroyed.

Returns : void
observeRouter
observeRouter()

Function that init the routerObservable.

Returns : void
onKeydown
onKeydown(event: KeyboardEvent)

Handles keyboard events for the modal component.

Parameters :
Name Type Optional Description
event KeyboardEvent No
  • The keyboard event object.
Returns : void
removeModalFromBody
removeModalFromBody()

Removes the modal element from document body.

Returns : void

Properties

document
Type : Document

Reference to the Document object for interacting with the DOM.

Public documentImported
Type : Document
Decorators :
@Inject(DOCUMENT)
- The injected Document object for DOM manipulation.
Optional footerTemplate
Type : TemplateRef<ElementRef> | null
Default value : null
Decorators :
@ContentChild('footerModal')

Reference to the footer template provided in the component's content.

Public getWindow
Type : GetWindow
- The GetWindow service for safely accessing the window object.
hasHeader
Default value : false

Indicates whether the modal has a header template.

Optional headerTemplate
Type : TemplateRef<ElementRef> | null
Default value : null
Decorators :
@ContentChild('headerModal')

Reference to the header template provided in the component's content.

localized
Type : URL

Indicates the origin path from modal.

modal
Type : ElementRef | null
Default value : null
Decorators :
@ViewChild('modal')

Reference to the modal element in the component's template.

modalNumber
Type : number
Default value : 0

The number of open modals in the document.

modals
Type : HTMLCollectionOf<Element> | undefined

HTML collection of modal elements in the document.

Public router
Type : Router
- The Router service for programmatic navigation.
routerObservable
Type : Subscription

A subscription to the router change url.

scrollY
Default value : this.getWindow?.window()?.scrollY

Indicates the scroll position of the window.

import { DOCUMENT } from '@angular/common'
import {
  Component,
  ContentChild,
  ElementRef,
  EventEmitter,
  Inject,
  Input,
  OnChanges,
  OnDestroy,
  Output,
  SimpleChanges,
  TemplateRef,
  ViewChild,
} from '@angular/core'
import { sleep } from '../../helpers/sleep.helper'
import { NavigationStart, Router } from '@angular/router'
import { Subscription } from 'rxjs'
import { GetWindow } from '../../helpers/window.helper'

/**
 * Represents a modal component with customizable options and event handling.
 *
 * Look the link about the component in original framework and the appearance
 *
 * @see {@link https://css.squidit.com.br/components/modal}
 *
 * @example
 * <sq-modal [open]="isModalOpen" (modalClose)="onModalClose()">
 *   <ng-template #headerModal>
 *     <h2>Title</h2>
 *   </ng-template>
 *   <div>
 *     <!-- Your content here -->
 *   </div>
*    <ng-template #footerModal>
 *     Footer
 *   </ng-template>
 * </sq-modal>
 * <button (click)='isModalOpen = true'>Open Modal</button>
 *
 * @implements {OnChanges}
 * @implements {OnDestroy}
 */
@Component({
  selector: 'sq-modal',
  templateUrl: './sq-modal.component.html',
  styleUrls: ['./sq-modal.component.scss'],
})
export class SqModalComponent implements OnChanges, OnDestroy {
  /**
   * A unique identifier for the modal component.
   */
  @Input() id = `modal-random-id-${(1 + Date.now() + Math.random()).toString().replace('.', '')}`

  /**
   * Indicates whether the modal should be open or closed.
   */
  @Input() open?: boolean

  /**
   * The size of the modal, which can be 'sm' (small), 'md' (medium), 'lg' (large), or 'xl' (extra large).
   */
  @Input() modalSize: 'sm' | 'md' | 'lg' | 'xl' | '' = 'md'

  /**
   * Additional CSS classes to apply to the modal element.
   */
  @Input() modalClass?: string

  /**
   * Additional CSS classes to apply to the modal backdrop element.
   */
  @Input() backdropClass?: string

  /**
   * Determines whether clicking outside the modal closes it. Options: 'static' (no close), 'true' (close).
   */
  @Input() backdrop = 'static'

  /**
   * Determines whether to display the close button.
   */
  @Input() buttonClose = true

  /**
   * Determines the header padding.
   */
  @Input() headerPadding = ''

  /**
   * Determines the body padding.
   */
  @Input() bodyPadding = '0 1rem'

  /**
   * Determines the footer padding.
   */
  @Input() footerPadding = ''

  /**
   * Determines the header background color.
   */
  @Input() headerBackgroundColor = ''

  /**
   * Determines the body background color.
   */
  @Input() bodyBackgroundColor = ''

  /**
   * Determines the footer background color.
   */
  @Input() footerBackgroundColor = ''

  /**
   * Event emitted when the modal is closed.
   */
  @Output() modalClose: EventEmitter<void> = new EventEmitter()

  /**
   * Event emitted when the left arrow key is pressed while the modal is open.
   */
  @Output() leftPress: EventEmitter<void> = new EventEmitter()

  /**
   * Event emitted when the right arrow key is pressed while the modal is open.
   */
  @Output() rightPress: EventEmitter<void> = new EventEmitter()

  /**
   * Reference to the modal element in the component's template.
   */
  @ViewChild('modal') modal: ElementRef | null = null

  /**
   * Reference to the header template provided in the component's content.
   */
  @ContentChild('headerModal') headerTemplate?: TemplateRef<ElementRef> | null = null

  /**
   * Reference to the footer template provided in the component's content.
   */
  @ContentChild('footerModal') footerTemplate?: TemplateRef<ElementRef> | null = null

  /**
   * HTML collection of modal elements in the document.
   */
  modals: HTMLCollectionOf<Element> | undefined

  /**
   * The number of open modals in the document.
   */
  modalNumber = 0

  /**
   * Indicates whether the modal has a header template.
   */
  hasHeader = false

  /**
   * Reference to the Document object for interacting with the DOM.
   */
  document: Document

  /**
   * Indicates the origin path from modal.
   *
   */
  localized: URL

  /**
   * A subscription to the router change url.
   */
  routerObservable!: Subscription

  /**
   * Indicates the scroll position of the window.
   */
  scrollY = this.getWindow?.window()?.scrollY

  /**
   * Creates an instance of `SqModalComponent`.
   * @constructor
   * @param {Document} documentImported - The injected Document object for DOM manipulation.
   * @param {Router} router - The Router service for programmatic navigation.
   * @param {GetWindow} getWindow - The GetWindow service for safely accessing the window object.
   */
  constructor(@Inject(DOCUMENT) public documentImported: Document, public router: Router, public getWindow: GetWindow) {
    this.onKeydown = this.onKeydown.bind(this)
    this.document = documentImported || document
    this.localized = new URL(this.getWindow.href())
  }

  /**
   * Lifecycle hook that detects changes to the 'open' input property and handles modal behavior accordingly.
   *
   * @param changes - The changes detected in the component's input properties.
   */
  async ngOnChanges(changes: SimpleChanges) {
    if (changes.hasOwnProperty('open')) {
      const modal = this.modal
      if (modal) {
        const body = this.document.getElementsByTagName('body')[0]
        const backdrop = this.document.getElementById('modal-backdrop') || this.document.createElement('div')
        if (this.open) {
          this.scrollY = this.getWindow?.window()?.scrollY
          body.appendChild(modal.nativeElement)
          this.observeRouter()
          this.hasHeader = !!this.headerTemplate
          body.classList.add('block')
          modal.nativeElement.style.display = 'flex'
          this.getWindow?.window()?.addEventListener('keydown', this.onKeydown)
          this.modals = this.document.getElementsByClassName('modal open')
          await sleep(10)
          this.modalNumber = this.modals?.length || 0
          if (this.modalNumber <= 1) {
            backdrop.setAttribute('id', 'modal-backdrop')
            backdrop.setAttribute('class', 'modal-backdrop show')
            body.appendChild(backdrop)
          } else if (this.modalNumber > 1) {
            modal.nativeElement.style.zIndex = 1060 + this.modalNumber + 1
            backdrop.setAttribute('style', `z-index: ${1060 + this.modalNumber};`)
          }
        } else {
          this.removeModalFromBody()
        }
      }
    }
  }

  /**
   * Performs actions before the component is destroyed.
   */
  ngOnDestroy(): void {
    this.routerObservable?.unsubscribe()
  }

  /**
   * Function that init the routerObservable.
   */
  observeRouter() {
    this.routerObservable = this.router.events.subscribe(async (event) => {
      if (event instanceof NavigationStart) {
        const destinationRoute = new URL(event.url, this.localized.origin)
        if ((this.localized.origin + this.localized.pathname) !== (destinationRoute.origin + destinationRoute.pathname)) {
          this.removeModalFromBody()
          await sleep(1000)
        }
      }
    })
  }

  /**
   * Removes the modal element from document body.
   */
  removeModalFromBody() {
    const body = this.document.getElementsByTagName('body')[0]
    if (this.modalNumber <= 1) {
      body?.classList?.remove('block')
      if (this.getWindow?.window()?.scrollY !== this.scrollY) {
        if (this.scrollY) this.getWindow?.window()?.scrollTo(0, this.scrollY)
      }
    }
    const backdrop = this.document.getElementById('modal-backdrop')
    const modal = this.document.getElementById(this.id)
    this.modalClose.emit()
    modal?.parentNode?.removeChild(modal)
    if (this.modalNumber === 2) {
      backdrop?.removeAttribute('style')
    } else if (this.modalNumber <= 1) {
      backdrop?.parentNode?.removeChild(backdrop)
    }
    this.getWindow?.window()?.removeEventListener('keydown', this.onKeydown)
  }

  /**
   * Handles keyboard events for the modal component.
   *
   * @param event - The keyboard event object.
   */
  onKeydown(event: KeyboardEvent) {
    if (this.open) {
      this.modals = this.document.getElementsByClassName('modal open')
      if (this.modals?.length && this.modals[this.modals.length - 1]?.id === this.id) {
        this.events(event.key)
      }
    }
  }

  /**
   * Handles specific keyboard events.
   *
   * @param key - The key code of the pressed key.
   */
  events(key: string) {
    switch (key) {
      case 'Escape':
        this.modalClose.emit()
        break
      case 'ArrowLeft':
        this.leftPress.emit()
        break
      case 'ArrowRight':
        this.rightPress.emit()
        break
    }
  }
}
<div
  class="modal align-items-center {{ backdropClass }}"
  [id]="id"
  [ngClass]="{
    open: open,
  }"
  #modal
>
  <div
    class="modal-dialog modal-{{ modalSize }} {{ modalClass }}"
    [clickOutsideEnabled]="!!(open && backdrop !== 'static' && modal)"
    (clickOutside)="modalClose.emit()"
  >
    <div class="modal-content modal-sq">
      <div
        class="modal-header"
        [ngClass]="{
          'without-header': !hasHeader
        }"
        [ngStyle]="{
          background: headerBackgroundColor,
          padding: headerPadding
        }"
      >
        <ng-container *ngIf="headerTemplate">
          <ng-container *ngTemplateOutlet="headerTemplate"></ng-container>
        </ng-container>
        <button
          *ngIf="buttonClose"
          type="button"
          class="close button-close"
          aria-label="Close"
          (click)="modalClose.emit()"
        >
          <i class="fa-solid fa-xmark fa-lg"></i>
        </button>
      </div>

      <div
        class="modal-body scrollbar"
        [ngStyle]="{ background: bodyBackgroundColor, padding: bodyPadding }"
      >
        <ng-content></ng-content>
      </div>

      <div
        class="modal-footer"
        [ngStyle]="{
          background: footerBackgroundColor,
          padding: footerPadding
        }"
        *ngIf="footerTemplate"
      >
        <ng-container *ngTemplateOutlet="footerTemplate"></ng-container>
      </div>
    </div>
  </div>
</div>

./sq-modal.component.scss

.modal {
  min-height: auto;
  &.need-priority {
    z-index: 1081;
  }
  .modal-sq {
    border-radius: 4px;
    .modal-header {
      display: flex;
      justify-content: space-between;
    }
    .without-header {
      padding: 0 0.08rem;
      border: none;
      justify-content: flex-end;
      button.close {
        z-index: 1;
        position: relative;
        top: 5px;
        left: -4px;
        margin: 0;
        padding: 0;
      }
    }
  }
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""