import { IFnVoid } from './types' const EVENTS = ['initialized', 'loaded', 'languageChanged'] export class BaseElement extends HTMLElement { private _off: IFnVoid[] private _observedAttributes: string[] = [] private _attrMap: (arg0: string) => string protected _initialized: boolean = false protected _props: object = {} protected _i18next: any set i18next (i18next :any) { if (this._i18next !== i18next) { this._disconnect() this._i18next = i18next this._connect() } } static get observedAttributes () { return [] } constructor (attributes: string[]) { super() this._i18next = (window as any).i18next const attrMap = attributes.reduce((map, name) => { const lc = name.toLowerCase() if (lc !== name) { map[lc] = name } return map }, {}) this._observedAttributes = attributes this._attrMap = (name: string) : string => attrMap[name] || name } connectedCallback () { this._assignProps() this._disconnect() // try not to attach more than one event handler this._connect() this._initialized = true this._render() } disconnectedCallback () { this._disconnect() } attributeChangedCallback (name: string, oldValue: unknown, value: unknown) { if (oldValue !== value) { // camelCase attributes arrive only as lowercase this._properties(this._attrMap(name), value) window.requestAnimationFrame(() => { this._render() }) } } private _assignProps () { Array.from(this.attributes).forEach(item => this._properties(this._attrMap(item.name), item.value)) this._observedAttributes.forEach(name => { if (this[name] !== undefined) this._properties(name, this[name]) Object.defineProperty(this, name, { get (): any { return this._props[name] }, set (this: BaseElement, value: unknown) { this.attributeChangedCallback(name, this._props[name], value) }, configurable: true, enumerable: true }) }) } private _connect () { if (this._i18next) { this._off = EVENTS.map(evName => { const fn = () => { window.requestAnimationFrame(() => { this._assignProps() // reassure that props are assigned after a remount this._render() }) } this._i18next.on(evName, fn) return () => this._i18next.off(evName, fn) }) } } private _disconnect () { this._off && this._off.forEach(fn => fn()) } protected _languages (lng: string | undefined) { const lngs = (this._i18next && this._i18next.languages) || (navigator as any).languages return [].concat(lng, lngs).filter(Boolean) } protected _properties (name: string, value: unknown) { // Needs implementation } protected _render () { // Needs implementation } }