Custom events and parent event chains
import { map, Evented } from 'sqsmap' provides session-local events. Use map.on/off/once/listens/fire/setEventedParent or new Evented() to connect your layer panel, tools and business state. Applications own their UI. These events do not use host RPC, change map data or require a DOM.
import { map, Evented } from 'sqsmap';
const tools = new Evented();
const received = [];
const subscription = tools.on('tool:changed', event => {
received.push({ tool: event.tool, parentTarget: event.target === tools });
});
map.setEventedParent(tools, () => ({ source: 'map' }));
map.fire('tool:changed', { tool: 'identify' });
const next = tools.once('tool:changed');
map.fire({ type: 'tool:changed', tool: 'pan' });
const event = await next;
subscription.unsubscribe();
map.setEventedParent(null);
tools.destroy();
export default { received, last: event.tool };
Standalone browser SDK callers import Evented from @sqsmap/core and use their own map object. Native persistent QuickJS, desktop scripts and browser scripts use the shared module above. Native one-shot runs support local custom events but retain their existing unavailable host-event subscription behavior; use a persistent session for actual map lifecycle events.
on retains its subscription return with unsubscribe(). off, fire and setEventedParent return the receiver. once(type, callback) returns the receiver; once(type) returns a Promise. Ordinary callbacks run before one-time callbacks. One-time listeners are removed before invocation, preventing repeated reentrant delivery. Following the checked reference behavior, a one-time listener added by an ordinary callback runs in the one-time phase of the same dispatch.
fire(type, data?) and fire({type, ...data}) dispatch synchronously. Data must be a plain object; names contain 1–128 characters and payloads have at most 256 string data properties. Symbols and getters are rejected. The outer event envelope is readonly; nested objects and local function values retain identity without JSON conversion. Callers supply the payload for manually fired built-in events; fire('move', ...) does not move the camera.
setEventedParent(parent, data?) accepts Evented objects and maps from the same host. Parent metadata is a plain object or a function returning one, called with the public child as this. Each receiving node determines type and target; metadata cannot replace them. listens checks the parent chain. Cycles and excessive depth are rejected before changing the chain. Actual map events bubble after their existing local dispatch using a new envelope, preserving readonly camera/feature snapshots. Parent-only listeners also receive them.
Project budgets: 32 nodes per script session including the map facade, 32 shared subscriptions, parent/reentrant depth 16 and 256 callbacks per outer synchronous dispatch. Pending asynchronous callbacks retain the session's budgets and error tracking. Limits fail explicitly; they are SqsMap resource constraints, not MapLibre specification limits.
Evented.destroy() is a project extension that cancels its waits, releases listeners and detaches parents/children. Stopping a script closes all session-owned Evented objects; map removal dispatches its terminal event before cleanup. Standalone SDK callers manage external Evented instances independently. Phone/external-device acceptance and formal publication remain separate.
The complete portable script covers payloads, once/reentry, mixed parent chains, invalid cycles, actual map events and cleanup. Existing built-in events, listener queries and layer/tool UI still apply.