/**
* JQHTML v2 Component Base Class
*
* Core component implementation following v2 specification:
* - 5-stage lifecycle coordinated by LifecycleManager
* - Direct jQuery manipulation (no virtual DOM)
* - Scoped IDs using _cid pattern
* - Event emission and CSS class hierarchy
*/
import { dynamic_component_name } from './component-name.js';
import { print_object } from './value-printers.js';
import { escape_html, escape_html_nl2br } from './escape.js';
import { Jqhtml_Markup } from './markup.js';
/**
* The `jqhtml` argument every compiled render function receives: the markup
* buffer class each render/content/slot function returns, the same escaping
* the runtime exports, the object-printer entry point, and the dynamic tag
* name check. One shared object, not one per render.
*/
export declare const render_utilities: {
Markup: typeof Jqhtml_Markup;
escape_html: typeof escape_html;
escape_html_nl2br: typeof escape_html_nl2br;
print_object: typeof print_object;
dynamic_component_name: typeof dynamic_component_name;
};
declare global {
interface Window {
JQHTML_DEBUG?: {
log: (componentName: string, phase: string, message: string, data?: any) => void;
updateTree: () => void;
};
}
}
export declare class Jqhtml_Component {
static __jqhtml_component: boolean;
static template?: any;
static readonly COMPONENT_NAME_PATTERN: RegExp;
$: any;
args: Record;
data: Record;
state: Record;
_cid: string;
_component_name: string;
_ready_state: number;
_debug_no_data: boolean;
private _lifecycle_manager;
private _instantiator;
private _dom_parent;
private _dom_children;
private _use_dom_fallback;
private _stopped;
private _booted;
private _data_before_render;
private _lifecycle_callbacks;
private _lifecycle_states;
private _did_first_render;
private _render_count;
private _args_on_last_render;
private _data_on_last_render;
private __initial_data_text;
private __data_frozen;
private __data_text;
private __data_text_of;
private _queue;
private _load_seq;
private _ready_owed;
private _locked_sync_hook;
private _async_hook;
private _cache_key;
private _data_cache_key;
private _cached_html;
private _used_cached_html;
private _should_cache_html_after_ready;
private _is_dynamic;
private _on_render_complete;
private _use_cached_data_hit;
private _skip_cache_read;
private _load_only;
private _load_render_only;
_is_detached: boolean;
private _ready_before;
private _force_initial_render;
private _has_rendered;
private _load_queue;
private __has_custom_on_load;
private __has_custom_on_load_error;
private _load_failed;
private _load_error_printed;
private __load_gates;
private __first_load_started;
private __gate_resume;
private _attached;
private _attachment_checked;
private _attachment_tracking;
constructor(element?: any, args?: Record);
/**
* Protect lifecycle methods from manual invocation
* Stores original implementations in WeakMap, replaces with guarded wrappers
* @private
*/
private _protect_lifecycle_methods;
/**
* Call a lifecycle method (async)
* Framework calls this to invoke lifecycle methods, bypassing the protection wrapper.
* @private
*/
private _call_lifecycle;
/**
* Call a lifecycle method (sync version for sync methods like on_stop)
* @private
*/
private _call_lifecycle_sync;
/**
* Call on_create() or on_render() and record that it is running, so a render(),
* load(), reload() or refresh() made from inside it can be warned about (see
* _queue_call()). Both run inside the lifecycle lock.
* @private
*/
private _call_locked_sync_hook;
/**
* Fire on_viewport_resize() with the current viewport width.
* Called after every on_render() and on_ready(); the debounced window
* listener in viewport.ts drives it from then on. Errors are isolated so a
* bad handler cannot break the lifecycle.
*
* Skipped while the hook resolves to the base no-op (nothing could observe the
* call). Read live on each fire, so a hook assigned to the instance (class field,
* on_create(), later) or patched onto a prototype is called like a class override.
* @private
*/
private _fire_viewport_resize;
/**
* Check if on_load() is overridden in a subclass
* Used to skip the load phase entirely for components that don't fetch data
* Returns cached value set in _boot() for O(1) performance
* @private
*/
private _has_on_load;
/**
* The loaded step for a component with NO custom on_load(): on_loaded(), then the
* 'loaded' event, at the point where on_load() would have completed (boot,
* load(), reload(), refresh()). Every component gets them, whether or not it fetches
* data; a component with on_load() gets them from _apply_load_result() / load().
*
* Suppressed by _load_only and _load_render_only (preloading mode), exactly as for a
* component with on_load(). Never runs on a stopped component, and 'loaded' does not
* fire if on_loaded() awaited across a stop().
*
* Returns null when nothing was awaited: on_loaded() is the base no-op, so 'loaded'
* fired synchronously and the caller's chain does not yield - a template-only
* component's boot stays synchronous. Otherwise a promise for on_loaded() + 'loaded'.
* @private
*/
private _loaded_without_on_load;
/**
* Boot - Start the full component lifecycle
* Called immediately after construction by instruction processor
*/
/**
* Internal boot method - starts component lifecycle
* @private
*/
_boot(): Promise;
/**
* The component render(sid) and _render(sid) delegate to: the one on this
* component's $sid element. Throws when there is no such element, or when it is
* not a component (no $redrawable).
* @private
*/
private _redraw_target;
/**
* Internal render phase - Create DOM structure
* Called top-down (parent before children) when part of lifecycle
* This is an internal method - users should call render() instead
*
* @param id Optional scoped ID - if provided, delegates to child component's _render()
* @returns The current _render_count after incrementing (used to detect stale renders)
* @private
*/
_render(id?: string | null, options?: {
skip_on_render?: boolean;
}): number;
/**
* Record what this render was made from, for _reload(): the args (did they change
* since, so try the cache?) and the data (did refresh() bring anything new?).
*
* Skipped for a component with no custom on_load(). _reload() is the only reader of
* both fields and returns before reading them for such a component - it just
* re-renders - so serializing args and data on every one of its renders bought
* nothing. For a list of row components that was most of the cost of a re-render.
* @private
*/
private _record_render_snapshots;
/**
* The serialized text of this.data - Jqhtml_Local_Storage.serialize() output - used
* for every "did this.data change?" decision (_should_rerender(), load(), _reload(),
* refresh()) and as the text written to the cache and handed to load followers.
*
* One text per data generation. The text a load result was normalized through is
* installed directly (_assign_load_result()), and any other text is computed once
* and kept while this.data is frozen - frozen data cannot change, and every way of
* replacing it (the data setter, an unfrozen set/delete) clears the text. It is
* computed from the raw data object, not through the deep-freeze proxies, which
* would allocate a read-only wrapper for every nested object they hand out.
*
* The cache serializer, not JSON.stringify: JSON renders every Map and Set as {},
* so data loaded into one never registered as changed.
* @private
*/
_data_text(): string;
/**
* Make a load result this.data: normalize it through the cache serializer, assign it
* (unfreeze, assign, refreeze), and keep the text it was normalized through as the new
* generation's _data_text(). Returns that text.
*
* Every on_load() result goes through here - in EVERY cache mode, not just 'data'.
* Two consequences the rest of the framework depends on:
* - this.data is jqhtml's own copy. The author's object graph is never aliased,
* so mutating what on_load() returned cannot reach a frozen this.data.
* - this.data is BY CONSTRUCTION what the cache stores and what a cache hit
* returns. Hot and cold are the same object shape in every mode, so turning
* caching on can never change what a component sees.
* Dates, Maps, Sets and register_cache_class() classes are reconstructed; anything
* the serializer cannot express is dropped and warned about once (local-storage.ts).
* @private
*/
private _assign_load_result;
/**
* Public render method - re-renders component and completes lifecycle
* This is what users should call when they want to update a component.
*
* Lifecycle sequence (serialized via component queue):
* 1. _render() - Updates DOM synchronously, calls on_render(), fires 'render' event
* 2. _wait_for_children_ready() - Waits for all children to reach ready state
* 3. on_ready() - Calls user's ready hook
* 4. trigger('ready') - Fires ready event
*
* Goes through the component queue to prevent concurrent lifecycle operations.
* Returns a Promise that resolves when the full render lifecycle completes.
*/
render(id?: string | null): Promise;
/**
* Alias for render() - re-renders component with current data
* Provided for API consistency and clarity
*/
redraw(id?: string | null): Promise;
/**
* Re-fetch data by executing on_load() without triggering re-render or lifecycle.
*
* Runs on_load() on a detached proxy (same restrictions as during boot):
* - this.data is restored to on_create() snapshot, then unfrozen for writing
* - this.args is read-only
* - All other properties (this.$, this.$sid, etc.) are blocked
*
* After on_load() completes, this.data is atomically updated and re-frozen.
* The developer decides what to do next (e.g., call this.render()).
*
* Pattern: load() invokes on_load(), just as render() invokes on_render().
*
* @returns true if this.data changed, false if unchanged
*/
load(): Promise;
/**
* Create phase - Quick setup, prepare UI
* SYNCHRONOUS - no awaits, no yields
*/
create(): void;
/**
* Register a "load gate" — a promise that must settle before this component's
* FIRST on_load() runs.
*
* Register gates during on_create(). Multiple calls accumulate; all gates are
* awaited together (via Promise.allSettled) at the seam between create and the
* first load. A REJECTED gate does NOT block or abort the load — it is an
* ordering hint, not a data dependency, so loading proceeds after settlement
* (rejections are logged through the debug channel).
*
* Gates are ONE-SHOT: they delay the first on_load() only. Once that load
* begins the gate list is cleared, and reload()/refresh() never re-await gates.
* Calling reload()/refresh() while a gate is still holding the first load
* releases the wait immediately and resumes the lifecycle (a later gate
* settlement then does nothing).
*
* Gates delay ONLY the on_load phase — never create(), the initial render,
* on_render(), or the cached-content first paint (stale-while-revalidate is
* preserved). Components with no custom on_load() ignore gates entirely.
*
* No-op during SSR (the server cannot await arbitrary client promises).
*
* jqhtml has no knowledge of WHAT is being awaited — the caller supplies the
* promise, and policy such as timeouts is the caller's responsibility (bake it
* into the promise you pass).
*
* @param promise The promise to await before the first on_load().
* @throws If called after the component's first load has already started.
*/
gate_load(promise: Promise): void;
/**
* Await all registered load gates before the first on_load().
*
* Called exactly once, at the boot seam immediately before the load phase
* (only for components with a custom on_load()). Latches "first load started"
* so any later gate_load() throws and reload()/refresh() never re-await.
*
* Returns a promise to await when there are gates to wait on, or `null` when
* there is nothing to wait for (no gates, or SSR). Returning `null` keeps the
* common no-gate path free of an extra microtask hop, so it does not shift the
* timing of ungated components.
*
* The returned promise resolves as soon as any of the following happens
* (whichever is first — the rest become no-ops):
* - all gates settle (Promise.allSettled),
* - reload()/refresh() is called while gated (they resolve __gate_resume),
* - the component is stopped (stop() resolves __gate_resume).
*
* @private
*/
private _await_load_gates;
/**
* Load phase - Fetch asynchronous data
* Called bottom-up, fully parallel
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*
* Key design: on_load() runs on a DETACHED proxy with a clone of this.data.
* The real component's this.data is not touched during on_load execution.
* After on_load completes, the result is applied via a sequential queue.
*
* @private - Internal lifecycle method, not for external use
*/
_load(): Promise;
/**
* Execute on_load() on a fully detached proxy.
* @see data-proxy.ts for full implementation
* @private
*/
private _execute_on_load_detached;
/**
* Run on_load() for a load, routing a failure through on_load_error().
*
* @returns What _execute_on_load_detached() returns on success. After a failure an
* on_load_error() override recovered from: the fallback data, with no
* coordination (fallback data is never handed to deduplication followers).
* null when the component was stopped and the load is abandoned.
* Throws when the failure is not recovered (see _run_load_error_hook()).
* @private
*/
private _execute_on_load_or_recover;
/**
* A load failed: this component's on_load() threw, or the deduplicated load it shared
* did. Every caller (boot, reload(), refresh(), load()) comes through here.
*
* - Latches _load_failed: from now on this instance neither reads nor writes the
* cache, in any cache mode, even after a later successful load.
* - on_load_error() not overridden: the base prints the error and it is rethrown. The
* boot path stops the component; reload()/refresh()/load() reject. Unchanged.
* - Overridden: the override runs in on_load()'s detached context (read this.args,
* write this.data from the on_create() snapshot) and the data it leaves is
* returned as the fallback; the lifecycle continues with it. If the override
* throws, that is rethrown - the no-override path, without the base's print.
*
* @returns The fallback data, or null if the component is (or became) stopped
* @private
*/
private _run_load_error_hook;
/**
* Throw unless on_load() left this.data as an object (arrays included).
* @private
*/
private _check_load_result;
/**
* Apply the result of on_load() to this.data via the sequential queue.
*
* Multiple _load calls can execute on_load in parallel, but this method
* ensures that setting this.data happens in FIFO order.
*
* @param result_data - The data returned from _execute_on_load_detached
* @param data_before_load - _data_text() of this.data before on_load (for change detection), or null
* @returns The serialized text of the data applied (see _assign_load_result())
* @private
*/
private _apply_load_result;
/**
* Ready phase - Component fully initialized
* Called bottom-up (children before parent)
* @private
*/
_ready(): Promise;
/**
* Set the ready state and fire 'ready'. The state is set BEFORE the trigger so a
* handler that calls ready() during dispatch takes the already-ready fast path.
* @private
*/
private _fire_ready;
/**
* Whether a previous instance of this component, under the cache key of its current
* args (or cache_id()), reached ready in the current cache scope. A hint for the
* application (e.g. show a spinner only on a cold first visit); jqhtml never acts on it.
*
* Opt-in: only a component that calls this records a marker when it reaches ready, so
* components that never ask cost nothing and store nothing. The answer is captured the
* first time it is asked for a key and frozen for that key: this instance reaching
* ready does not turn its own answer true. New args (a different key) are looked up
* afresh. False for unkeyable args, a detached component, or no cache key set.
*
* Ready is reached bottom-up, so a marker implies the component's whole subtree
* reached ready at least once in that scope.
*/
was_ready_before(): boolean;
/**
* At a ready point, record the marker was_ready_before() reads - only for the key this
* component asked about, once, and not when the answer was already true.
* @private
*/
private _write_ready_marker;
/**
* A render/reload/refresh entry reached its 'ready' point. Deferred to the queue going
* idle (_on_queue_idle()) while 'ready' is already owed (boot ended with calls queued)
* or a promoted call is still running (see _queue_call()) - a promoted entry counts
* itself, so it never fires 'ready' in the middle of the cycle it is nested in.
* @private
*/
private _entry_ready_point;
/**
* The lifecycle queue went idle. Fires the 'ready' boot deferred because calls were
* queued when it ended (see _ready()).
* @private
*/
private _on_queue_idle;
/**
* True (and logged in verbose mode) when a newer load has started since the load
* numbered `load_seq` began. The older result is dropped: applying it would put
* data fetched for older args over newer data. The lifecycle lock already prevents
* overlapping loads; this guards any path that ever does overlap.
* @private
*/
private _load_superseded;
/**
* Put a render/load/reload/refresh call on this component's lifecycle queue.
*
* Where the call was made decides what its promise does when awaited:
* - from this component's own on_create()/on_render(): warned at once (development).
* It is queued behind the running lifecycle step, so it does not run there.
* - while this component's on_ready() runs: awaiting (or chaining) the promise before
* on_ready() has settled pulls the queued call forward and runs it at once, nested
* (Component_Queue.promote()). Unawaited, it runs after on_ready() returns.
* - while on_loaded() runs: awaiting it before on_loaded() has settled warns
* (development). It cannot start until on_loaded() returns - a deadlock.
* - while on_load() runs, or anywhere else: a plain promise; the call waits its turn.
* @param method - Public method name, for the warnings
* @param key - Queue key: method plus arguments. Only identical keys merge.
* @param async_hops - Settle the caller a few microtasks after the queue does, as an
* async method would. load()/reload()/refresh() were async methods; the extra hops
* put a caller's `await` after the queue has released the lock, so
* `await c.reload(); c.render();` starts the render at once instead of queueing it.
* @private
*/
private _queue_call;
/**
* Public API: Wait for component to be fully ready
* Returns a promise that resolves when the component reaches ready state.
* Optionally accepts a callback that executes when ready.
*
* @example
* // Promise pattern
* await component.ready();
*
* @example
* // Callback pattern
* component.ready(() => {
* console.log('Component is ready!');
* });
*
* @example
* // Both patterns work together
* await component.ready(() => console.log('Callback fired'));
*
* @param callback Optional callback to execute when ready
*/
ready(callback?: () => void): Promise;
/**
* Promise that resolves when `event_name` fires OR the component stops, whichever
* comes first - resolves immediately if the component is already stopped. Used by
* ready(), rendered() and _wait_for_children_ready(): a stopped component never
* fires either event again, so waiting on the event alone hangs every waiter
* forever.
*
* Resolves rather than rejects: the documented contract is that waiters on a
* failed or stopped component are RELEASED, and existing `await comp.ready()` code
* does not catch. Callers that need to know can check is_stopped() afterwards.
*
* The optional callback runs ONLY when the event itself fires, never on stop: it
* is developer code written for a ready/rendered DOM, and calling it on a stopped
* component would be a new call into user code the contract never made.
*
* Both listeners are one-shot and the first to fire removes the other, so repeated
* waits on a long-lived component do not accumulate handlers in either queue.
* @private
*/
private _settle_on;
/**
* Returns a promise that resolves when the component has completed its
* synchronous render chain (after on_load's re-render if applicable,
* or after first render if no on_load override).
*
* This fires BEFORE on_ready() and before children are waited on.
* Use this when you need to know the component's DOM is stable but
* don't need to wait for the full async ready phase.
*
* The 'rendered' event fires exactly once per component lifecycle.
*
* @example
* await component.rendered();
* // DOM is now stable, on_render has completed
*
* @param callback Optional callback to execute when rendered
*/
rendered(callback?: () => void): Promise;
/**
* Wait for all child components to reach ready state
* Ensures bottom-up ordering (children ready before parent)
* @private
*/
private _wait_for_children_ready;
/**
* Wait for all child components to complete on_render (post-on_load)
* Used by HTML cache mode to ensure DOM is fully rendered before taking snapshot
*
* HTML CACHE ARCHITECTURE:
* - Parent waits for all children to complete their on_render after on_load
* - This ensures the HTML snapshot captures fully rendered DOM
* - Static parents (is_dynamic=false) don't block - they immediately let children proceed
*
* @private
*/
private _wait_for_children_on_render;
/**
* Reload component - re-fetch data and re-render.
*
* Goes through the component's lifecycle queue: while the component is busy (boot,
* or another entry running) the call waits as an entry and runs in order. It merges
* only into an identical call waiting right before it (see Component_Queue).
*
* @param always_render - If true (default), always re-render after on_load().
* If false, only re-render if data actually changed (refresh()).
*/
reload(always_render?: boolean): Promise;
/**
* Refresh component - re-fetch data and re-render only if data changed.
*
* Same as reload(false). Queued like reload(); a refresh() and a reload() are
* separate entries and each runs with its own render rule.
*/
refresh(): Promise;
/** @private - shared by reload() and refresh() */
private _queue_reload;
/**
* Re-render this component from its on_create() state with its cache dropped and
* WITHOUT running on_load()/on_loaded()/on_ready().
*
* @internal debug tool. Used by the debug overlay's lifecycle footer to see what a
* component looks like with no loaded data at all. Not public API, not documented,
* and no downstream code should call it: reload() or refresh() afterwards returns
* the component to normal (the snapshot is intact and the cache is gone, so the
* next load is a genuine fresh fetch).
*/
_reload_without_data(): Promise;
/**
* Internal reload implementation - re-fetch data and re-render
*
* COMPLETE RELOAD PROCESS (Source of Truth):
*
* STEP 1: Cache key refresh, cache check (if args changed since last render)
* - Generate cache key from component name + current args, and ALWAYS store it
* (null included) so this reload's writes go to the current args' entry
* - Set or clear data-nocache (and the dev warning) as boot and load() do
* - If cached data exists and is non-empty:
* - Unfreeze this.data (temporarily)
* - Hydrate this.data with cached data
* - Re-freeze this.data
* - Render immediately (stale-while-revalidate)
* - Set rendered_from_cache flag
*
* STEP 2: Call on_load() to fetch fresh data
* - Unfreeze this.data
* - Restore this.data to on_create() snapshot
* - Call on_load() directly (no _load() wrapper)
* - Freeze this.data after completion
* - If data changed and non-empty, or is unchanged but the cache key moved
* (new args): write to cache
*
* STEP 3: Conditionally re-render
* - If didn't render from cache yet, OR data changed after on_load():
* - Call render() to update DOM
*
* STEP 3.5: Wait for all children to be ready
* - Bottom-up ordering (children ready before parent)
* - Same as main lifecycle
* - html cache mode: snapshot the post-reload markup if this load changed data
*
* STEP 4: Call on_ready()
* - Always call on_ready() after reload completes
*
* A failed cycle (on_load() failure not recovered by on_load_error(), or any other
* throw) restores the ready state it found before rejecting - see below.
*
* @param force_render - true for reload() (always render), false for refresh()
* (render only if the data changed)
* @private - Use reload() / refresh() instead (queued wrappers)
*/
_reload(force_render?: boolean): Promise;
/**
* The body of _reload(), after the ready marker has been invalidated.
* @private
*/
private _reload_cycle;
/**
* Destroy the component and cleanup
* Called automatically by MutationObserver when component is removed from DOM
* Can also be called manually for explicit cleanup
*/
/**
* Internal stop method - stops just this component (no children)
* Sets stopped flag, calls lifecycle hooks, but leaves DOM intact
* @private
*/
_stop(): void;
/**
* Stop component lifecycle - stops all descendant components then self
* Leaves DOM intact, just stops lifecycle engine and fires cleanup hooks
*/
stop(): void;
on_render(): void;
on_create(): void;
on_load(): void | Promise;
on_loaded(): void | Promise;
/**
* Called when on_load() throws (or the deduplicated load this component shared
* fails), with the error. Same access as on_load(): read this.args, write this.data.
*
* This base implementation prints the error. Not overriding it, the failure then
* takes its usual course: a failed boot stops the component, a failed
* reload()/refresh()/load() rejects. Overriding it, the component continues - it
* renders the this.data the override leaves, then fires 'ready'. An override may call
* super.on_load_error(error) to keep the print; one that throws stops the component.
*/
on_load_error(error: any): void | Promise;
on_ready(): Promise;
on_stop(): void;
on_attach(): void;
on_detach(): void;
/**
* Whether this component's root is in the document right now.
*
* A live answer - one isConnected read - so it is correct at every point in the
* lifecycle (including on_create()/on_load()/on_ready(), before the first 'attach')
* and for untracked components (a natively-removed component that nothing watches
* still answers false). A stopped component always answers false.
*
* It does NOT read the recorded state behind 'attach'/'detach' (_attached), and it
* never starts tracking. The two can disagree briefly: is_attached() can be true
* before the first 'attach' has fired, and changes a moment before the matching
* event, since the events come from the document observer.
*
* Not the same as !is_stopped(): a live component can be detached (moved, not yet
* inserted), and a stopped one answers false here even while its element is in the DOM.
*/
is_attached(): boolean;
/**
* Whether this component has been stopped. True from the moment stop() (or _stop(),
* reached by an ancestor's stop, a parent re-render or a jQuery removal) begins on it -
* before on_stop() runs - and never false again.
*
* Code that continues after an `await` outside the lifecycle hooks (an event handler,
* an unawaited helper, a timer) checks it before touching this.$, this.$sid(),
* this.sid(), this.data or this.state. ready() and rendered() resolve on stop too, so
* code after `await child.ready()` makes the same check.
*
* Not the same as !is_attached(): stop() leaves the DOM intact, so a stopped
* component's element can still be in the document.
*/
is_stopped(): boolean;
/**
* Wrap a wait so that it never resumes on a stopped component:
*
* const res = await this.alive(Some_Controller.resolve(parent));
* this.sid('picker').val(res.id); // only ever runs on a live component
*
* Returns a new promise that settles the way `value` does (a plain value resolves) if
* this component is not stopped when `value` settles, and NEVER settles if it is. The
* decision is made at settle time, so nothing is tracked and stop() has nothing to clean
* up; a stopped component's never-settling promise is garbage-collected with the
* suspended function. Already stopped at the call: never settles either, since stopped
* is permanent. A rejection after the stop is swallowed (the handler below is always
* attached), so it never surfaces as an unhandled rejection.
*
* A `finally` after the await does not run on a stopped component. Work that must happen
* regardless awaits the plain promise and checks is_stopped() instead.
*/
alive(value: T | PromiseLike): Promise>;
/**
* Flip the recorded state and fire the matching hook + event. The opposite
* sticky event is invalidated first, so a late subscriber only ever sees the
* current state. Hook errors are isolated; the event still fires.
* @private
*/
private _transition_attachment;
/**
* Compare the root's live connectivity with the recorded state and transition
* on a difference. Idempotent. Called by the document observer on every
* mutation batch for tracked components, and directly by the framework after
* 'ready' and when interest is registered late.
* @private
*/
_attachment_check(): void;
/**
* The first check, run once the 'ready' handlers have completed. Fires 'attach'
* if the component was born attached, even with no listener, so a later
* .on('attach') gets the sticky fire. Deferred interest starts tracking here.
* @private
*/
private _attachment_first_check;
/**
* Register interest in attach/detach. Before the first ready check this only
* records the interest; after it, tracking starts immediately and a check runs
* at once, so a late subscriber on a natively-removed component sees 'detach'.
* @private
*/
private _watch_attachment;
/**
* Called with the viewport width in CSS pixels:
* - after every on_render()
* - after every on_ready()
* - on window resize, debounced 30ms, for every component in the document
*
* MUST be synchronous. Prefer CSS media/container queries for pure styling -
* override this only when layout requires imperative measurement.
*/
on_viewport_resize(viewport_width: number): void;
/**
* Optional: Override cache key generation
*
* By default, cache keys are generated from component name + args.
* Override this method to provide a custom cache key for this component instance.
*
* If this method throws an error, caching will be disabled for this component.
*
* @returns Custom cache key string (will be prefixed with component name)
*/
cache_id?(): string;
/**
* Should component re-render after load?
* By default, only re-renders if data has changed
* Override to control re-rendering behavior
*/
/**
* Internal method to determine if component should re-render after on_load()
* @private
*/
_should_rerender(): boolean;
/**
* Get component name for debugging
*/
/**
* The name this component was invoked as - the tag written in the template
* ( -> 'User_Card'), the name given to $(el).component('User_Card'),
* or the boot placeholder's data-component-init-name. For a template-only
* component this is the template name, not 'Jqhtml_Component'. Falls back to
* the class's static component_name, then its JS class name.
*/
component_name(): string;
/**
* Register event callback - delegates to component-events.ts
* @see component-events.ts for full documentation
*/
on(event_name: string, callback: (this: Jqhtml_Component, component: Jqhtml_Component, data?: any) => void): this;
/**
* Register a callback that fires exactly once - delegates to component-events.ts
* If the event already occurred, fires immediately and does not register.
* @see component-events.ts for full documentation
*/
once(event_name: string, callback: (this: Jqhtml_Component, component: Jqhtml_Component, data?: any) => void): this;
/**
* Trigger a lifecycle event - delegates to component-events.ts
* @see component-events.ts for full documentation
*
* 'ready' is special: once its handlers have run, the component runs its first
* attachment check (and every later one, idempotently). Doing it here rather than
* at each of the call sites that trigger 'ready' guarantees the order
* on_ready() -> 'ready' handlers -> 'attach', so everything on_ready() set up is
* complete before anyone learns the component is in the document.
*/
trigger(event_name: string, data?: any): void;
/**
* Check if any callbacks are registered for a given event
*/
_on_registered(event_name: string): boolean;
/**
* Invalidate a lifecycle event - delegates to component-events.ts
* @see component-events.ts for full documentation
*/
invalidate(event_name: string): void;
/**
* Find element by scoped ID
*
* Searches for elements with id="local_id:THIS_COMPONENT_CID"
*
* Example:
* Template:
* Rendered:
* Access: this.$sid('save_btn') // Returns jQuery element
*
* Performance: Uses native document.getElementById() when component is in DOM,
* falls back to jQuery.find() for components not yet attached to DOM.
*
* @param local_id The local ID (without _cid suffix)
* @returns jQuery element with id="local_id:_cid", or empty jQuery object if not found
*/
$sid(local_id: string): any;
/**
* Get component instance by scoped ID
*
* Convenience method that finds element by scoped ID and returns the component instance.
*
* Example:
* Template:
* Access: const user = this.sid('active_user'); // Returns User_Card instance
* user.data.name // Access component's data
*
* To get the scoped ID string itself:
* this.$sid('active_user').attr('id') // Returns "active_user:abc123xyz"
*
* @param local_id The local ID (without _cid suffix)
* @returns Component instance or null if not found or not a component
*/
sid(local_id: string): Jqhtml_Component | null;
/**
* Get the component that instantiated this component (rendered it in their template)
* Returns null if component was created programmatically via $().component()
*/
instantiator(): Jqhtml_Component | null;
/**
* Find descendant components by CSS selector
*/
find(selector: string): Jqhtml_Component[];
/**
* Find closest ancestor component matching selector
*
* A bare component name - a plain identifier starting with a capital letter,
* optionally preceded by a single underscore, such as 'Parent_Dashboard' or
* '_Root_Layout' - is treated as the component-name class
* selector '.Parent_Dashboard'. Components carry a CSS class for every name
* in their prototype chain, so a base class name matches its subclasses too.
* Every other selector ('.Foo', '#id', '[attr]', 'div > .Component') is
* passed to jQuery unchanged.
*/
closest(selector: string): Jqhtml_Component | null;
/**
* Get CSS class hierarchy for this component type
*/
static get_class_hierarchy(): string[];
private _generate_cid;
/**
* Re-scope every scoped id in an HTML-cache snapshot so it belongs to THIS instance.
*
* A snapshot is the inner HTML of some component instance, so it contains ids of the
* form `:`: the snapshotting component's own elements carry its cid, and
* elements rendered by nested children carry each child's cid. Replayed verbatim into
* a second instance those ids are duplicates of the originals, and $sid() resolves via
* getElementById(), which returns whichever matching element is first in the document -
* so one instance could read and write another instance's DOM.
*
* Mapping: the snapshot owner's cid becomes this._cid (so $sid() finds the elements
* this component is about to treat as its own), and every other cid becomes a fresh
* uid(), consistently within one injection. Two instances hydrated from one snapshot
* therefore share no id at all.
*
* The ':' separator is written only by the compiler ($sid) and the instruction
* processor (a scoped plain id), and cids are uid() output - [a-z][a-z0-9]* - so the
* pattern is exact. A plain unscoped id has no ':' and is left alone.
*
* data-cid is the debug mirror of a component's own _cid (config.ts debug_attributes).
* The snapshot's values are stale cids belonging to components that no longer exist,
* so they go through the same mapping rather than being left to lie in DevTools.
*
* @private
*/
private _rescope_cached_html;
private _apply_css_classes;
private _apply_default_attributes;
private _set_attributes;
private _update_debug_attrs;
private _find_dom_parent;
/**
* Get DOM children (components in DOM subtree)
* Uses fast _dom_children registry when possible, falls back to DOM traversal for off-DOM components
* @private - Used internally for lifecycle coordination
*/
private _get_dom_children;
private _log_lifecycle;
}
export declare const base_on_viewport_resize: (viewport_width: number) => void;
//# sourceMappingURL=component.d.ts.map