gd-bs
Version:
Bootstrap JavaScript, TypeScript and Web Components library.
340 lines (290 loc) • 11 kB
text/typescript
import { IOffcanvas, IOffcanvasOptions, IOffcanvasProps } from "./types";
import { ClassNames } from "../classNames";
import { Base } from "../base";
import { appendContent } from "../common";
import { HTML } from "./templates";
/**
* Offcanvas Size
*/
export enum OffcanvasSize {
Small = 1,
Small1 = 2,
Small2 = 3,
Small3 = 4,
Medium = 5,
Medium1 = 6,
Medium2 = 7,
Medium3 = 8,
Large = 9,
Large1 = 10,
Large2 = 11,
Large3 = 12,
XLarge = 13,
XXLarge = 14,
Full = 15
}
/**
* Offcanvas Size Classes
*/
export const OffcanvasSizeClassNames = new ClassNames([
"offcanvas-sm",
"offcanvas-size-sm1",
"offcanvas-size-sm2",
"offcanvas-size-sm3",
"offcanvas-md",
"offcanvas-size-md1",
"offcanvas-size-md2",
"offcanvas-size-md3",
"offcanvas-lg",
"offcanvas-size-lg1",
"offcanvas-size-lg2",
"offcanvas-size-lg3",
"offcanvas-xl",
"offcanvas-xxl",
"offcanvas-size-full"
]);
/**
* Offcanvas Types
*/
export enum OffcanvasTypes {
Bottom = 1,
End = 2,
Start = 3,
Top = 4
}
/**
* Offcanvas Types Classes
*/
export const OffcanvasTypesClassNames = new ClassNames([
"offcanvas-bottom",
"offcanvas-end",
"offcanvas-start",
"offcanvas-top"
]);
/**
* Offcanvas
*/
class _Offcanvas extends Base<IOffcanvasProps> implements IOffcanvas {
private _autoClose: boolean = null;
private _eventCreated: boolean = false;
private _tranisitioningFl: boolean = false;
// Constructor
constructor(props: IOffcanvasProps, template: string = HTML) {
super(template, props);
// Configure the offcanvas
this.configure();
// Configure the events
this.configureEvents();
// Configure the parent
this.configureParent();
}
// Configure the card group
private configure() {
// Set the attributes
this.props.id ? this.el.id = this.props.id : null;
// Set the size & type
this.setSize(this.props.size);
this.setType(this.props.type);
// Get the options
let options: IOffcanvasOptions = this.props.options || {
backdrop: true,
keyboard: true,
scroll: false
};
// Set the properties
options.backdrop ? this.el.setAttribute("data-bs-body", "backdrop") : null;
options.scroll ? this.el.setAttribute("data-bs-body", "scroll") : null;
// Set the header
appendContent(this.el.querySelector(".offcanvas-header > div"), this.props.title);
// Set the body
appendContent(this.el.querySelector(".offcanvas-body"), this.props.body);
// Set the focus
if (options.focus) { (this.el as HTMLElement).focus(); }
// Set the visibility
if (options.visible) { this.toggle(); }
}
// Configures the auto-close event
private configureAutoCloseEvent() {
// See if the event exists
if (this._eventCreated) { return; }
// Ensure the body exists
if (document.body) {
// Add a click event to the modal
document.body.addEventListener("click", (ev: MouseEvent) => {
// See if the auto close flag is set
if (this._autoClose) {
// Do nothing if we are tranisitionsing
if (this._tranisitioningFl) { return; }
// Do nothing if we clicked within the offcanvas
if (ev.composedPath().includes(this.el)) { return; }
else {
// Get the mouse coordinates
let x = ev.clientX;
let y = ev.clientY;
let elCoordinate = (this.el as HTMLElement).getBoundingClientRect();
// See if we clicked within the offcanvas
if (x <= elCoordinate.right && x >= elCoordinate.left && y <= elCoordinate.bottom && y >= elCoordinate.top) { return; }
// Else, see if something was selected
else if (x == 0 && y == 0) { return; }
}
// Close the offcanvas if it's visible
if (this.isVisible) { this.toggle(); }
}
});
// Set the flag
this._eventCreated = true;
} else {
// Add the load event
window.addEventListener("load", () => {
// Configure the event
this.configureAutoCloseEvent();
});
}
}
// Configure the events
private configureEvents() {
// Execute the events
this.props.onRenderHeader ? this.props.onRenderHeader(this.el.querySelector(".offcanvas-header > div"), this.props) : null;
this.props.onRenderBody ? this.props.onRenderBody(this.el.querySelector(".offcanvas-body"), this.props) : null;
// Get the close button
let elClose = this.el.querySelector(".btn-close");
if (elClose) {
// Add a click event
elClose.addEventListener("click", () => {
// Hide the modal
this.hide();
});
}
// Set the flag to determine if the modal is sticky
this.setAutoClose(this.props.options && typeof (this.props.options.autoClose) === "boolean" ? this.props.options.autoClose : true);
// See if the keyboard option is set
if (this.props.options && this.props.options.keyboard) {
// Add a click event
(this.el as HTMLElement).addEventListener("keydown", ev => {
// See if the escape key was clicked and the modal is visible
if ((ev.key === "Escape" || ev.keyCode == 27) && this.isVisible) {
// Toggle the modal
this.toggle();
}
});
}
}
/**
* Public Interface
*/
// Hides the modal
hide() {
// See if a transition is currently happening
if (this._tranisitioningFl) {
// Wait for the transition to complete
let id = setInterval(() => {
// See if the transition is complete
if (!this._tranisitioningFl) {
// Stop the loop
clearInterval(id);
// Toggle the modal
this.isVisible ? this.toggle() : null;
}
}, 250);
} else {
// Toggle the modal
this.isVisible ? this.toggle() : null;
}
}
// Returns true if the modal is visible
get isVisible() { return this.el.classList.contains("show"); }
// Updates the auto close flag
setAutoClose(value: boolean) {
// Set the flag
this._autoClose = value;
// Configure the event if we are setting the value
this._autoClose ? this.configureAutoCloseEvent() : null;
}
// Sets the offcanvas size
setSize(offcanvasSize: number) {
// Parse the class names
OffcanvasSizeClassNames.parse(className => {
// Remove the class names
this.el.classList.remove(className);
});
// Set the class name
let className = OffcanvasSizeClassNames.getByType(offcanvasSize);
if (className) { this.el.classList.add(className); }
}
// Sets the offcanvas type
setType(offcanvasType: number) {
// Parse the class names
OffcanvasTypesClassNames.parse(className => {
// Remove the class names
this.el.classList.remove(className);
});
// Set the class name
let className = OffcanvasTypesClassNames.getByType(offcanvasType) || OffcanvasTypesClassNames.getByType(OffcanvasTypes.End);
this.el.classList.add(className);
}
// Shows the modal
show() {
// See if a transition is currently happening
if (this._tranisitioningFl) {
// Wait for the transition to complete
let id = setInterval(() => {
// See if the transition is complete
if (!this._tranisitioningFl) {
// Stop the loop
clearInterval(id);
// Toggle the modal
this.isVisible ? null : this.toggle();
}
}, 250);
} else {
// Toggle the modal
this.isVisible ? null : this.toggle();
}
}
// Toggles the modal
toggle() {
let backdrop = document.querySelector(".offcanvas-backdrop");
// Set the flag
this._tranisitioningFl = true;
// See if this modal is visible
if (this.isVisible) {
// Hide the modal
this.el.classList.add("offcanvas-toggling");
this.el.classList.remove("show");
// Wait for the animation to complete
setTimeout(() => {
this.el.style.visibility = "hidden";
this.el.classList.remove("offcanvas-toggling");
// Remove the backdrop
backdrop ? document.body.removeChild(backdrop) : null;
backdrop = null;
// Set the flag
this._tranisitioningFl = false;
// Call the event
this.props.onClose ? this.props.onClose(this.el) : null;
}, 250);
} else {
// Create the backdrop if we are showing it
let showBackdrop = this.props.options && typeof (this.props.options.backdrop) === "boolean" ? this.props.options.backdrop : true;
if (showBackdrop && backdrop == null) {
backdrop = document.createElement("div");
backdrop.classList.add("offcanvas-backdrop");
backdrop.classList.add("fade");
backdrop.classList.add("show");
document.body.appendChild(backdrop);
}
// Show the modal
this.el.style.visibility = "visible";
this.el.classList.add("offcanvas-toggling");
this.el.classList.add("show");
// Wait for the animation to complete
setTimeout(() => {
// Update the class
this.el.classList.remove("offcanvas-toggling");
// Set the flag
this._tranisitioningFl = false;
}, 250);
}
}
}
export const Offcanvas = (props: IOffcanvasProps, template?: string): IOffcanvas => { return new _Offcanvas(props, template); }