/** * 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