be-enhanced
Version:
be-enhanced provides a base class that enables casting spells, or enhancing server-rendered DOM elements based on cross-cutting custom attributes
212 lines (179 loc) • 7.22 kB
text/typescript
import {RoundaboutReady} from './ts-refs/trans-render/froop/types';
import {assignGingerly} from 'trans-render/lib/assignGingerly.js';
import { RoundAbout } from 'trans-render/froop/roundabout.js';
import { EnhancementInfo, IEnhancement, BEAllProps} from './ts-refs/trans-render/be/types';
import { BEConfig, PropInfo, PropLookup} from './index';
import {dispatchEvent} from 'trans-render/positractions/dispatchEvent.js';
import { IMountObserver } from './ts-refs/mount-observer/types';
import {RRMixin} from 'trans-render/froop/RRMixin.js';
export {BEConfig} from './index';
const publicPrivateStore = Symbol();
export class BE<TProps = any, TActions=TProps, TElement extends Element = Element>
extends RRMixin(EventTarget) implements RoundaboutReady, IEnhancement<TElement>{
propagator = new EventTarget();
[publicPrivateStore]: Partial<TProps> = {};
async covertAssignment(obj: TProps): Promise<void> {
await assignGingerly(this[publicPrivateStore], obj) ;
}
#disconnectedAbortController = new AbortController();
get disconnectedSignal(){
return this.#disconnectedAbortController.signal;
}
get #config(){
return (<any>this.constructor).config as BEConfig;
}
de = dispatchEvent;
channelEvent(event: Event){
(<any>event).enh = this.#ei?.mountCnfg?.enhPropKey;
this.#enhancedElement!.dispatchEvent(event);
}
#enhancedElement: TElement | undefined;
#ei: EnhancementInfo | undefined;
get enhancedElement(){
return this.#enhancedElement!;
}
async attach(el: TElement, enhancementInfo: EnhancementInfo){
this.#enhancedElement = el;
this.#ei = enhancementInfo;
await this.covertAssignment({enhancedElement: el} as TProps);
const props = (<any>this.constructor).props as PropLookup;
await this.#propUp(props, enhancementInfo);
await this.#instantiateRoundaboutIfApplicable(el);
}
/**
* Needed for asynchronous loading
* @param props Array of property names to "upgrade", without losing value set while element was Unknown
* @param defaultValues: If property value not set, set it from the defaultValues lookup
* @private
*/
async #propUp<T>(props: PropLookup, enhancementInfo: EnhancementInfo){
const {initialPropValues} = enhancementInfo;
const objToMerge: any = {...initialPropValues};
for(const key in props){
if(key in objToMerge) continue;
const propInfo = props[key];
const value = propInfo.def;
if(value !== undefined){
objToMerge[key] = value;
}
}
await this.covertAssignment(objToMerge);
}
async detach(el: TElement){
this.propagator.dispatchEvent(new Event('disconnectedCallback'));
this.#disconnectedAbortController.abort();
}
#roundabout: RoundAbout | undefined;
async #instantiateRoundaboutIfApplicable(container: TElement){
const config = this.#config;
const {actions, compacts, infractions, handlers, positractions, hitch, isSleepless} = config;
if((actions || compacts || infractions || handlers || positractions) !== undefined){
let mountObservers: Set<IMountObserver> | undefined;
if(!isSleepless){
const {guid} = await import('mount-observer/MountObserver.js');
mountObservers = (<any>this)[guid];
}
const {roundabout} = await import('trans-render/froop/roundabout.js');
const [vm, ra] = await roundabout({
vm: this,
container,
actions,
compacts,
handlers,
positractions,
hitch,
mountObservers
}, infractions);
this.#roundabout = ra;
}
}
whenResolved(): Promise<boolean>{
return new Promise((resolve, reject) => {
if(this.rejected) {
resolve(false);
return;
//reject(false);
}
if(this.resolved){
resolve(true);
return;
}
this.addEventListener('resolved', e => {
if(this.resolved){
resolve(true);
}
});
})
}
static config: BEConfig | undefined;
static async bootUp(){
const config = this.config!;
const {propDefaults, propInfo} = config;
const props = {...this.props as PropLookup};
Object.assign(props, propInfo);
if(propDefaults !== undefined){
for(const key in propDefaults){
const def = propDefaults[key];
const propInfo = {
...defaultProp,
def,
propName: key
} as PropInfo;
props[key] = propInfo;
}
}
if(propInfo !== undefined){
for(const key in propInfo){
const prop = propInfo[key]!;
const mergedPropInfo = {
...defaultProp,
...prop,
propName: key
} as PropInfo
props[key] = mergedPropInfo;
}
}
this.props = props;
this.addProps(this, props);
}
static addProps(newClass: {new(): BE}, props: PropLookup){
const proto = newClass.prototype;
for(const key in props){
if(key in proto) continue;
const prop = props[key];
const {ro} = prop;
if(ro){
Object.defineProperty(proto, key, {
get(){
return this[publicPrivateStore][key];
},
enumerable: true,
configurable: true,
});
}else{
Object.defineProperty(proto, key, {
get(){
return this[publicPrivateStore][key];
},
set(nv: any){
const ov = this[publicPrivateStore][key];
if(prop.dry && ov === nv) return;
this[publicPrivateStore][key] = nv;
(this as BE).propagator.dispatchEvent(new Event(key));
},
enumerable: true,
configurable: true,
});
}
}
}
static props: PropLookup = {};
}
export interface BE<TProps = any, TActions=TProps, TElement extends Element = Element> extends BEAllProps, IEnhancement<TElement>{}
const defaultProp: PropInfo = {
dry: true,
};
export const propDefaults: Partial<{[key in keyof BEAllProps]: IEnhancement[key]}> = {
resolved: false,
rejected: false,
}