Component contract
Every JS component exposes the same shape: an initX(scope) function with a complete meta object, registered in the ng.core registry. Same lifecycle, same cleanup, same events — for all of them.
The contract is what lets the core mount, track and unmount any component uniformly, and what the mutation observer relies on to handle markup injected at runtime.
The contract
export function initX(scope = document) {
// find roots NOT yet initialized
const roots = scope.querySelectorAll('.ng-x:not([data-ng-uid])');
// ... setup, listeners via u.listen(...), per-instance API ...
return [...roots]; // array of initialized roots
}
initX.meta = {
name: 'x',
version: '1.0',
description: 'Short operational description of the component.',
dependencies: [], // required components, e.g. ['overlay']
author: 'NexiGrid',
experimental: false
};
if (window.ng) {
window.ng.registerComponent('x', initX);
}
Required meta fields
name | string — lowercase, one word ("popover"). |
|---|---|
version | string — "1.0", "2.37.0". |
description | string — one sentence, max 200 chars. |
dependencies | string[] — [] or e.g. ["overlay"]. |
author | string — always "NexiGrid". |
experimental | boolean — true while WIP. |
Init rules
| Scope | initX(scope = document) — accepts an optional scope so the observer can init partial DOM. |
|---|---|
| Selector | Only roots not yet mounted: .ng-x:not([data-ng-uid]). The core assigns data-ng-uid in initComponent — never set it manually. |
| Listeners | Always via u.listen(...), tracked in root.__ngListeners ({el, type, handler, options}). Direct addEventListener escapes ng.unmount cleanup. |
| Return | Array of initialized roots; [] for delegated components (one global listener). |
Teardown — __ngProbe
Unmounting (ng.unmount, cleanRegistry) automatically removes the listeners tracked in __ngListeners — but not other resources. Timers, observers (IntersectionObserver, ResizeObserver, MutationObserver), BroadcastChannel, rAF loops or third-party instances are released through a teardown hook on the root:
root.__ngProbe = {
teardown() {
clearInterval(timer);
observer.disconnect();
channel.close();
}
};
The core invokes it once at unmount, before any DOM removal, then deletes it (no double teardown). It is the only supported teardown hook, and it is only for non-listener resources.
Runtime API
Global APIs attach to window.ng.<name>; anchored components expose their per-instance API on the root as root.__ng<Name>:
// global runtime API
window.ng.snackbar = { show, dismiss };
window.ng.state = { get, set, watch /* ... */ };
// per-instance API for anchored components (popover, modal, range...)
root.__ngPopover = { open, close, toggle, update };
SCSS-only components
Components without JS (badge, card, pagination, skeleton, spinner, page-loader…) still declare their version in a docblock at the top of _ng_<name>.scss:
/* ==========================================================
NexiGrid — <Name>
Version: 1.0
Status: stable
========================================================== */
Custom events
Event names follow ng:<component>:<action> — lowercase, max two levels after ng:. Dispatch on the component root (not on document, except global events like ng:ready), with the standard detail { <target>, trigger?, ...payload }:
root.dispatchEvent(new CustomEvent('ng:popover:open', {
detail: { popover: root, trigger: activeTrigger }
}));
Examples
ng:ready ng:popover:open ng:popover:close ng:modal:confirm ng:modal:cancel ng:state:change ng:aux:copy ng:range:change Forms like ng-popover-opened or popover:open are invalid.