| /** |
| * -------------------------------------------------------------------------- |
| * 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 |