// Remote Call Caller
// Augments any nested component to dispatch remote-call events declaratively
// Usage:
//
// When the inner component fires the event(s) specified by `listen`, this mixin
// will forward the call to the receiver whose id is `target-id`.
import { MixinBase } from '../common/mixin-base.js';
const AttributeKeys = {
TARGET: 'target-id',
METHOD: 'method',
LISTEN: 'listen',
ARGSMAP: 'args-map', // JSON string mapping event.detail keys to positional argument index
};
const EVENT_NAME = 'remote-call';
class RemoteCallCallerMixin extends MixinBase() {
[key: string]: any;
constructor() {
super();
this._boundHandlers = [];
this._isInitialized = false;
}
static get observedAttributes() {
return Object.values(AttributeKeys);
}
connectedCallback() {
super.connectedCallback();
this._isInitialized = true;
this._setupListeners();
}
disconnectedCallback() {
super.disconnectedCallback && super.disconnectedCallback();
this._removeListeners();
}
attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null) {
super.attributeChangedCallback && super.attributeChangedCallback(name, oldVal, newVal);
if (this._isInitialized && oldVal !== newVal && Object.values(AttributeKeys).includes(name)) {
this._removeListeners();
this._setupListeners();
}
}
_buildArgs(e: CustomEvent) {
const mapAttr = this.getAttribute(AttributeKeys.ARGSMAP);
if (!mapAttr) {
return [];
}
let mapObj;
try { mapObj = JSON.parse(mapAttr); } catch(err) { console.error('Invalid args-map JSON:', err); return []; }
const argsArr: unknown[] = [];
Object.entries(mapObj).forEach(([key, pos])=>{
argsArr[pos as number] = e.detail ? (e.detail as Record)[key] : undefined;
});
return argsArr;
}
_setupListeners() {
this._removeListeners();
const listenAttr = (this.getAttribute(AttributeKeys.LISTEN) || '').split(',').map(s=>s.trim()).filter(Boolean);
if (listenAttr.length === 0) return;
const target = this.findActualTargetComponent();
if (!target) { console.warn('RemoteCallCallerMixin: no inner target found'); return; }
const handlerFactory = (_evtName: string) => (e: CustomEvent) => {
const targetId = this.getAttribute(AttributeKeys.TARGET);
const method = this.getAttribute(AttributeKeys.METHOD);
if (!targetId || !method) {
console.warn('RemoteCallCallerMixin: target-id or method missing');
return;
}
const args = this._buildArgs(e);
this._callRemote(targetId, method, ...args);
};
listenAttr.forEach(evtName => {
const h = handlerFactory(evtName);
target.addEventListener(evtName, h as EventListener);
this._boundHandlers.push({evt: evtName, handler: h as EventListener, target});
});
}
_removeListeners() {
this._boundHandlers.forEach(({evt, handler, target}: {evt: string, handler: EventListener, target: Element}) => {
target.removeEventListener(evt, handler);
});
this._boundHandlers = [];
}
_callRemote(targetId: string, methodName: string, ...args: unknown[]) {
const detail = {
targetId,
method: methodName,
args,
timestamp: Date.now(),
};
const event = new CustomEvent(EVENT_NAME, {
detail,
bubbles: true,
composed: true,
});
document.dispatchEvent(event);
}
// Public API so external scripts (demo) can trigger remote calls programmatically
callRemote(targetId: string, methodName: string, ...args: unknown[]) {
this._callRemote(targetId, methodName, ...args);
}
// Simple logger used by the demo page
log(message: string, type: string = 'info') {
console[type === 'error' ? 'error' : 'log']('[DemoCaller]', message);
}
}
if (typeof module !== 'undefined') {
module.exports = { RemoteCallCallerMixin };
} else if (typeof window !== 'undefined') {
(window as any).RemoteCallCallerMixin = RemoteCallCallerMixin;
}
export { RemoteCallCallerMixin };