blob: fa01a65d699422c26fb18e331da7a955b0938311 [file] [edit]
/**
* --------------------------------------------------------------------------
* Bootstrap base-component.ts
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
import Data from './dom/data.js'
import EventHandler from './dom/event-handler.js'
import Config, { type ComponentConfig } from './util/config.js'
import { executeAfterTransition, getElement } from './util/index.js'
/**
* Constants
*/
const VERSION = '6.0.0-alpha1'
/**
* Class definition
*/
class BaseComponent extends Config {
declare ['constructor']: typeof BaseComponent
protected declare _element: HTMLElement
protected declare _config: ComponentConfig
constructor(element?: string | Element | null, config?: ComponentConfig | null) {
super()
element = getElement(element)
if (!element) {
return
}
this._element = element as HTMLElement
this._config = this._getConfig(config)
// Dispose any existing instance bound to this element before registering the new one,
// so its event listeners and timers are cleaned up instead of leaking
const existingInstance = Data.get(this._element, this.constructor.DATA_KEY)
if (existingInstance) {
existingInstance.dispose()
}
Data.set(this._element, this.constructor.DATA_KEY, this)
}
// Public
dispose(): void {
Data.remove(this._element, this.constructor.DATA_KEY)
EventHandler.off(this._element, this.constructor.EVENT_KEY)
for (const propertyName of Object.getOwnPropertyNames(this)) {
(this as Record<string, any>)[propertyName] = null
}
}
// Protected
// Runs `callback` once the transition on `element` ends, and resolves afterwards.
// Lifecycle methods return this promise, so `await instance.show()` continues at the
// same moment a `shown.bs.*` listener would run.
protected _queueCallback(callback: () => void, element: Element, isAnimated = true): Promise<void> {
return new Promise(resolve => {
executeAfterTransition(() => {
// Don't run the completion callback if the instance was disposed mid-transition.
// Still settle the promise, so an awaiting caller resumes instead of hanging.
if (this._element) {
callback()
}
resolve()
}, element, isAnimated)
})
}
protected override _getConfig(config?: ComponentConfig | null): ComponentConfig {
config = this._mergeConfigObj(config, this._element)
config = this._configAfterMerge(config)
this._typeCheckConfig(config)
return config
}
// Static
static getInstance<T extends typeof BaseComponent>(this: T, element?: string | Element | null): InstanceType<T> | null {
return Data.get(getElement(element), this.DATA_KEY)
}
static getOrCreateInstance<T extends typeof BaseComponent>(this: T, element?: string | Element | null, config: NonNullable<ConstructorParameters<T>[1]> | null = {}): InstanceType<T> {
return this.getInstance(element) || (new this(element, typeof config === 'object' ? config : null) as InstanceType<T>)
}
static get VERSION(): string {
return VERSION
}
static get DATA_KEY(): string {
return `bs.${this.NAME}`
}
static get EVENT_KEY(): string {
return `.${this.DATA_KEY}`
}
static eventName(name: string): string {
return `${name}${this.EVENT_KEY}`
}
}
export default BaseComponent