Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions projects/element-ng/modal/si-modal-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ describe('SiModalService', () => {
expect(bodyStyle.overflow).toBe('hidden');

modalRef.hide();
vi.advanceTimersByTime(500);
appRef.tick();

expect(document.querySelector('si-modal')).not.toBeInTheDocument();
Expand All @@ -79,7 +78,6 @@ describe('SiModalService', () => {
expect(modal).toHaveTextContent('test component');

modalRef.hide();
vi.advanceTimersByTime(500);
appRef.tick();

expect(document.querySelector('si-modal')).not.toBeInTheDocument();
Expand All @@ -97,7 +95,6 @@ describe('SiModalService', () => {
appRef.tick();
expect(modal).toHaveTextContent('new input value');
modalRef.hide();
vi.advanceTimersByTime(500);
appRef.tick();
});
});
Expand Down
28 changes: 13 additions & 15 deletions projects/element-ng/modal/si-modal.component.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
@if (showBackdropVisible()) {
<div class="modal-backdrop" animate.leave="backdrop-leave"></div>
}
@if (init) {
<div
#modalContainer
role="dialog"
class="modal d-block"
aria-modal="true"
[class.fade]="modalRef.data.animated !== false"
[class.show]="show()"
[attr.aria-labelledby]="titleId"
>
<div cdkTrapFocus cdkTrapFocusAutoCapture [class]="`modal-dialog ${dialogClass}`">
<div class="modal-content">
<ng-content />
</div>
<div
#modalContainer
role="dialog"
class="modal d-block"
aria-modal="true"
[class.dialog-leave]="!show()"
[attr.aria-labelledby]="titleId"
(transitionend)="onLeaveEnd($event)"
>
<div cdkTrapFocus cdkTrapFocusAutoCapture [class]="`modal-dialog ${dialogClass}`">
<div class="modal-content">
<ng-content />
</div>
</div>
}
</div>
56 changes: 27 additions & 29 deletions projects/element-ng/modal/si-modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import {
Component,
ElementRef,
inject,
OnDestroy,
OnInit,
signal,
viewChild,
DOCUMENT
} from '@angular/core';
import { areAnimationsDisabled } from '@siemens/element-ng/common';

import { ModalRef } from './modalref';

Expand All @@ -31,57 +29,55 @@ import { ModalRef } from './modalref';
'(window:keydown.esc)': 'onEsc($event)'
}
})
export class SiModalComponent implements OnInit, AfterViewInit, OnDestroy {
export class SiModalComponent implements OnInit, AfterViewInit {
protected readonly modalRef = inject(ModalRef<unknown, any>);
protected readonly isAnimated = !areAnimationsDisabled() && this.modalRef.data.animated !== false;

protected readonly dialogClass = this.modalRef.dialogClass ?? '';
protected readonly titleId = this.modalRef.data?.ariaLabelledBy ?? '';
protected init = false;
protected readonly show = signal(false);
protected readonly show = signal(true);
protected readonly showBackdropVisible = signal(false);

private clickStartInDialog = false;
private origBodyOverflow?: string;
private showTimer: any;
private backdropGhostClickPrevention = true;
private detachFn?: () => void;
private readonly document = inject(DOCUMENT);

private readonly modalContainerRef = viewChild.required<ElementRef>('modalContainer');

ngOnInit(): void {
setTimeout(() => (this.backdropGhostClickPrevention = false), this.animationTime(300));
this.init = true;
this.showTimer = setTimeout(() => {
this.show.set(true);
}, this.animationTime(150));
setTimeout(() => (this.backdropGhostClickPrevention = false), 300);
Comment thread
spike-rabbit marked this conversation as resolved.
}

ngAfterViewInit(): void {
queueMicrotask(() => this.modalRef?.shown.next(this.modalContainerRef()));
}

ngOnDestroy(): void {
this.hideBackdrop();
}

/** @internal */
hideDialog(param?: any): void {
clearTimeout(this.showTimer);
hideDialog(param?: unknown): void {
if (!this.show()) {
return;
}

this.show.set(false);
// set `detach()` in modal ref to no-op so that the animation is unaffected if called
const detach = this.modalRef.detach;
// Capture `detach()` in modal ref to no-op so that the animation is unaffected if called
this.detachFn = this.modalRef.detach;
this.modalRef.detach = () => {};

setTimeout(() => {
this.hideBackdrop();
setTimeout(() => detach(), this.animationTime(150));
}, this.animationTime(300));
// Check transition duration before toggling the DOM changes to avoid issues with zero-duration transitions where transitionend won't fire
const isAnimated =
parseFloat(getComputedStyle(this.modalContainerRef().nativeElement).transitionDuration) > 0;

// Toggle the @if condition to trigger animate.leave on the dialog
this.show.set(false);
this.hideBackdrop();
this.modalRef?.hidden.next(param);
this.modalRef?.hidden.complete();
this.modalRef?.message.complete();

// For disabled animations or test environments, transitionend won't fire (0s duration)
if (!isAnimated) {
this.detachFn?.();
}
}

/** @internal */
Expand All @@ -99,6 +95,12 @@ export class SiModalComponent implements OnInit, AfterViewInit, OnDestroy {
}
}

protected onLeaveEnd(event: TransitionEvent): void {
if (!this.show() && event.target === event.currentTarget) {
this.detachFn?.();
}
}

protected clickStarted(event: MouseEvent): void {
this.clickStartInDialog = event.target !== this.modalContainerRef().nativeElement;
}
Expand Down Expand Up @@ -126,8 +128,4 @@ export class SiModalComponent implements OnInit, AfterViewInit, OnDestroy {
this.modalRef.messageOrHide(this.modalRef.closeValue);
}
}

private animationTime(millis: number): number {
return this.isAnimated ? millis : 0;
}
}
23 changes: 19 additions & 4 deletions projects/element-theme/src/styles/bootstrap/_modals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
@use '../variables/typography';
@use '../variables/zindex';
@use '../variables/si-vars';
@use '../variables/animations';
@use './functions';
@use './mixins/backdrop';
@use './mixins/breakpoints';
Expand All @@ -25,6 +26,18 @@
// We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a
// gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342
// See also https://github.com/twbs/bootstrap/issues/17695

transition: variables.$transition-fade;
opacity: 1;

@starting-style {
opacity: 0;
}

&.dialog-leave {
opacity: 0;
transition-duration: animations.element-transition-duration(0.3s);
}
}

.modal-dialog {
Expand All @@ -36,13 +49,15 @@
// because of :focus-visible that shouldn't be visible
box-shadow: none !important; // stylelint-disable-line declaration-no-important

.modal.fade & {
transition: variables.$modal-transition;
transition: variables.$modal-transition;
transform: variables.$modal-show-transform;

@starting-style {
transform: variables.$modal-fade-transform;
}

.modal.show & {
transform: variables.$modal-show-transform;
.modal.dialog-leave & {
transform: variables.$modal-fade-transform;
}

// When trying to close, animate focus to scale
Expand Down
Loading