1
0
mirror of https://github.com/flarum/core.git synced 2025-10-27 21:46:17 +01:00
Files
php-flarum/js/src/common/components/ModalManager.tsx
Alexander Skvortsov b7f2fe2429 Fix consecutive shows of same modal with different attrs
We need to specify a unique key for each modal so that the modals are fully destroyed and recreated. For instance, this fixes the signup modal being empty with OAuth register flows.
2021-12-27 18:28:11 -05:00

100 lines
2.9 KiB
TypeScript

import Component from '../Component';
import { createFocusTrap, FocusTrap } from '../utils/focusTrap';
import type ModalManagerState from '../states/ModalManagerState';
import type Mithril from 'mithril';
interface IModalManagerAttrs {
state: ModalManagerState;
}
/**
* The `ModalManager` component manages a modal dialog. Only one modal dialog
* can be shown at once; loading a new component into the ModalManager will
* overwrite the previous one.
*/
export default class ModalManager extends Component<IModalManagerAttrs> {
protected focusTrap: FocusTrap | undefined;
/**
* Whether a modal is currently shown by this modal manager.
*/
protected modalShown: boolean = false;
view(vnode: Mithril.VnodeDOM<IModalManagerAttrs, this>): Mithril.Children {
const modal = this.attrs.state.modal;
const Tag = modal?.componentClass;
return (
<div className="ModalManager modal fade">
{!!Tag && (
<Tag
key={modal?.key}
{...modal.attrs}
animateShow={this.animateShow.bind(this)}
animateHide={this.animateHide.bind(this)}
state={this.attrs.state}
/>
)}
</div>
);
}
oncreate(vnode: Mithril.VnodeDOM<IModalManagerAttrs, this>): void {
super.oncreate(vnode);
// Ensure the modal state is notified about a closed modal, even when the
// DOM-based Bootstrap JavaScript code triggered the closing of the modal,
// e.g. via ESC key or a click on the modal backdrop.
this.$().on('hidden.bs.modal', this.attrs.state.close.bind(this.attrs.state));
this.focusTrap = createFocusTrap(this.element as HTMLElement);
}
onupdate(vnode: Mithril.VnodeDOM<IModalManagerAttrs, this>): void {
super.onupdate(vnode);
requestAnimationFrame(() => {
try {
if (this.modalShown) this.focusTrap!.activate?.();
else this.focusTrap!.deactivate?.();
} catch {
// We can expect errors to occur here due to the nature of mithril rendering
}
});
}
animateShow(readyCallback: () => void): void {
if (!this.attrs.state.modal) return;
const dismissible = !!this.attrs.state.modal.componentClass.isDismissible;
this.modalShown = true;
// If we are opening this modal while another modal is already open,
// the shown event will not run, because the modal is already open.
// So, we need to manually trigger the readyCallback.
if (this.$().hasClass('in')) {
readyCallback();
return;
}
this.$()
.one('shown.bs.modal', readyCallback)
// @ts-expect-error: No typings available for Bootstrap modals.
.modal({
backdrop: dismissible || 'static',
keyboard: dismissible,
})
.modal('show');
}
animateHide(): void {
// @ts-expect-error: No typings available for Bootstrap modals.
this.$().modal('hide');
this.modalShown = false;
}
}