Refactor filename naming system and apply convention-based renames

Standardize settings file naming and relocate documentation files
Fix code quality violations from rsx:check
Reorganize user_management directory into logical subdirectories
Move Quill Bundle to core and align with Tom Select pattern
Simplify Site Settings page to focus on core site information
Complete Phase 5: Multi-tenant authentication with login flow and site selection
Add route query parameter rule and synchronize filename validation logic
Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs
Implement filename convention rule and resolve VS Code auto-rename conflict
Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns
Implement RPC server architecture for JavaScript parsing
WIP: Add RPC server infrastructure for JS parsing (partial implementation)
Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation
Add JQHTML-CLASS-01 rule and fix redundant class names
Improve code quality rules and resolve violations
Remove legacy fatal error format in favor of unified 'fatal' error type
Filter internal keys from window.rsxapp output
Update button styling and comprehensive form/modal documentation
Add conditional fly-in animation for modals
Fix non-deterministic bundle compilation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-13 19:10:02 +00:00
parent fc494c1e08
commit 77b4d10af8
28155 changed files with 2191860 additions and 12967 deletions

0
node_modules/@jqhtml/core/dist/component-registry.d.ts.map generated vendored Executable file → Normal file
View File

View File

@@ -27,13 +27,14 @@ export declare class Jqhtml_Component {
private _dom_parent;
private _dom_children;
private _use_dom_fallback;
private _destroyed;
private _stopped;
private _booted;
private _data_before_render;
private _lifecycle_callbacks;
private _lifecycle_states;
private __loading;
private _did_first_render;
private _render_count;
constructor(element?: any, args?: Record<string, any>);
/**
* Boot - Start the full component lifecycle
@@ -41,18 +42,34 @@ export declare class Jqhtml_Component {
*/
boot(): Promise<void>;
/**
* Render phase - Create DOM structure
* Internal render phase - Create DOM structure
* Called top-down (parent before children) when part of lifecycle
* Can also be called directly to just re-render DOM without lifecycle hooks
* This is an internal method - users should call render() instead
*
* @param id Optional scoped ID - if provided, delegates to child component's render()
* @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): Promise<void>;
_render(id?: string | null): number;
/**
* Public render method - re-renders component and completes lifecycle
* This is what users should call when they want to update a component.
*
* Lifecycle sequence:
* 1. _render() - Updates DOM synchronously, calls on_render(), fires 'render' event
* 2. Async continuation (fire and forget):
* - _wait_for_children_ready() - Waits for all children to reach ready state
* - on_ready() - Calls user's ready hook
* - trigger('ready') - Fires ready event
*
* Returns immediately after _render() completes - does NOT wait for children
*/
render(id?: string | null): void;
/**
* Alias for render() - re-renders component with current data
* Provided for API consistency and clarity
*/
redraw(id?: string | null): Promise<void>;
redraw(id?: string | null): void;
/**
* Create phase - Quick setup, prepare UI
* Called bottom-up (children before parent)
@@ -81,16 +98,26 @@ export declare class Jqhtml_Component {
*/
reinitialize(): Promise<void>;
/**
* Reload data - re-fetch data and update the component
* Re-runs on_load(), then renders and calls on_ready()
* Reload component - re-fetch data and re-render
* Re-runs on_load(), always renders, and calls on_ready()
*/
reload_data(): Promise<void>;
reload(): Promise<void>;
/**
* Destroy the component and cleanup
* Called automatically by MutationObserver when component is removed from DOM
* Can also be called manually for explicit cleanup
*/
destroy(): void;
/**
* 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 | Promise<void>;
on_create(): void | Promise<void>;
on_load(): Promise<void>;

2
node_modules/@jqhtml/core/dist/component.d.ts.map generated vendored Executable file → Normal file
View File

@@ -1 +1 @@
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,YAAY,CAAC,EAAE;YACb,GAAG,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;YACjF,UAAU,EAAE,MAAM,IAAI,CAAC;SACxB,CAAC;KACH;CACF;AAED,qBAAa,gBAAgB;IAE3B,MAAM,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC;IAGtB,CAAC,EAAE,GAAG,CAAC;IACP,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAM;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAK;IAGzB,OAAO,CAAC,kBAAkB,CAAmB;IAC7C,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,iBAAiB,CAAkB;IAC3C,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,oBAAoB,CAAwE;IACpG,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,iBAAiB,CAAkB;gBAE/B,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM;IAsFzD;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAY3B;;;;;;OAMG;IACG,MAAM,CAAC,EAAE,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA6MrD;;;OAGG;IACG,MAAM,CAAC,EAAE,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD;;;OAGG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB7B;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA6D3B;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB5B;;;;OAIG;YACW,wBAAwB;IA4BtC;;;OAGG;IACG,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAmCnC;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IA2DlC;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAsDf,SAAS,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IACjC,SAAS,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAC3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IACxB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAC/B,UAAU,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAElC;;;;OAIG;IACH,eAAe,IAAI,OAAO;IAiB1B;;OAEG;IACH,cAAc,IAAI,MAAM;IAIxB;;OAEG;IACH,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAK1C;;;;;OAKG;IACH,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,SAAS,EAAE,gBAAgB,KAAK,IAAI,GAAG,IAAI;IA4B7E;;;OAGG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAiBjC;;;OAGG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAK3C;;;;;;;;;;;;;;;OAeG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,GAAG;IAgB1B;;;;;;;;;;;;OAYG;IACH,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAgB7C;;;OAGG;IACH,YAAY,IAAI,gBAAgB,GAAG,IAAI;IAIvC;;OAEG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,EAAE;IAa1C;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI;IAoBlD;;OAEG;IACH,MAAM,CAAC,mBAAmB,IAAI,MAAM,EAAE;IAwCtC,OAAO,CAAC,aAAa;IAIrB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,kBAAkB;IA4B1B,OAAO,CAAC,yBAAyB;IAsFjC,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,gBAAgB;IAcxB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,UAAU;CASnB"}
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"}

0
node_modules/@jqhtml/core/dist/debug-entry.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/debug-overlay.d.ts.map generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/debug.d.ts.map generated vendored Executable file → Normal file
View File

431
node_modules/@jqhtml/core/dist/index.cjs generated vendored Executable file → Normal file
View File

@@ -23,58 +23,11 @@ class LifecycleManager {
}
constructor() {
this.active_components = new Set();
this.observer = null;
// Initialize MutationObserver to detect component removal from DOM
if (typeof window !== 'undefined' && typeof MutationObserver !== 'undefined') {
this.observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// Convert NodeList to Array for iteration
Array.from(mutation.removedNodes).forEach(node => {
if (node.nodeType === 1) { // Element node
this._handle_node_removal(node);
}
});
}
});
// Start observing when DOM is ready
const startObserving = () => {
if (document.body) {
this.observer.observe(document.body, {
childList: true,
subtree: true
});
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startObserving);
}
else {
startObserving();
}
}
}
/**
* Handle node removal from DOM
* Destroys component and all descendant components
*/
_handle_node_removal(element) {
// Need to use global $ here
const $ = globalThis.$;
if (!$)
return;
const $el = $(element);
// Check if element itself is a component
const component = $el.data('_component');
if (component && !component._destroyed) {
component.destroy();
}
// Find and destroy all descendant components (flat iteration, no recursion)
$el.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._destroyed) {
child.destroy();
}
});
// No automatic cleanup on DOM removal - components stop explicitly via:
// 1. Parent component re-render (calls .empty() which stops children)
// 2. Manual .empty(), .html(), .text() calls (overridden to stop children)
// 3. Explicit .stop() call
// This allows components to be moved in DOM without lifecycle interruption
}
/**
* Boot a component - run its full lifecycle
@@ -85,54 +38,40 @@ class LifecycleManager {
try {
// Create phase - runs BEFORE first render
await component.create();
// Check if destroyed during create
if (component._destroyed)
// Check if stopped during create
if (component._stopped)
return;
// Trigger create event
component.trigger('create');
// Render phase - creates DOM and child components
await component.render();
// Check if destroyed during render
if (component._destroyed)
// Note: _render() now calls on_render() internally after DOM update
// Capture render ID to detect if another render happens before ready
let render_id = component._render();
// Check if stopped during render
if (component._stopped)
return;
// Call on_render() (synchronous)
const renderResult = component.on_render();
if (renderResult && typeof renderResult.then === 'function') {
console.warn(`[JQHTML] Component "${component.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
await renderResult;
}
// Check if destroyed during on_render
if (component._destroyed)
return;
// Trigger render event
component.trigger('render');
// Load phase - may modify this.data
await component.load();
// Check if destroyed during load
if (component._destroyed)
// Check if stopped during load
if (component._stopped)
return;
// If data changed during load, re-render
// Note: _render() now calls on_render() internally after DOM update
if (component.should_rerender()) {
await component.render();
// Check if destroyed during re-render
if (component._destroyed)
render_id = component._render();
// Check if stopped during re-render
if (component._stopped)
return;
const rerenderResult = component.on_render();
if (rerenderResult && typeof rerenderResult.then === 'function') {
console.warn(`[JQHTML] Component "${component.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
await rerenderResult;
}
// Check if destroyed during on_render
if (component._destroyed)
return;
component.trigger('render');
}
// Check if this render is still current before proceeding to ready
// If _render_count changed, another render happened and we should skip ready
if (component._render_count !== render_id) {
return; // Stale render, don't call ready
}
// Ready phase - waits for children, then calls on_ready()
await component.ready();
// Check if destroyed during ready
if (component._destroyed)
// Check if stopped during ready
if (component._stopped)
return;
// Trigger ready event
await component.trigger('ready');
@@ -474,7 +413,7 @@ function uid() {
* Process an array of instructions and append to target
* Uses v1 approach: build HTML string, set innerHTML, then initialize
*/
async function process_instructions(instructions, target, context, slots) {
function process_instructions(instructions, target, context, slots) {
// Build HTML string and track elements that need initialization
const html = [];
const tagElements = {};
@@ -498,7 +437,7 @@ async function process_instructions(instructions, target, context, slots) {
}
// Third pass: initialize and boot components in parallel
// Like v1, all sibling components at this level boot simultaneously
const component_boots = [];
// DO NOT await - let children boot in background while parent continues
for (const [cid, compData] of Object.entries(components)) {
// Use native querySelector for better performance
const el = target[0].querySelector(`[data-cid="${cid}"]`);
@@ -506,11 +445,10 @@ async function process_instructions(instructions, target, context, slots) {
const element = $(el);
el.removeAttribute('data-cid');
// Boot this component (which will render and boot its children recursively)
component_boots.push(initialize_component(element, compData));
// Fire and forget - don't wait for boot to complete
initialize_component(element, compData);
}
}
// Wait for all sibling components to complete their boot
await Promise.all(component_boots);
}
/**
* Process a single instruction into HTML
@@ -707,7 +645,8 @@ function apply_attributes(element, attrs, context) {
context.args[dataKey] = value;
// Set to element data-key for convienence if a simple value
if (typeof value == "string" || typeof value == "number") {
element.attr(`data-${dataKey}`, value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr(`data-${dataKey}`, attrValue);
}
// } else if (key.startsWith('@')) {
// // Event handler with @ prefix (e.g., @click)
@@ -748,7 +687,8 @@ function apply_attributes(element, attrs, context) {
}
else if (key.startsWith('data-')) {
// Data attributes - go into both DOM and context.args
element.attr(key, value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr(key, attrValue);
// Get version without 'data-' prefix
const dataKey = key.substring(5);
// Always set element.data
@@ -768,7 +708,8 @@ function apply_attributes(element, attrs, context) {
}
if (!existingClasses) {
// No existing classes - set directly
element.attr('class', value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr('class', attrValue);
}
else {
// Merge classes - split and add if not present
@@ -791,7 +732,8 @@ function apply_attributes(element, attrs, context) {
const existingStyle = element.attr('style');
if (!existingStyle) {
// No existing style - set directly
element.attr('style', value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr('style', attrValue);
}
else {
// Merge styles rule by rule
@@ -822,7 +764,8 @@ function apply_attributes(element, attrs, context) {
// These do NOT become data attributes or go into context.args
// Examples: href, title, role, aria-*, tabindex, etc.
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
element.attr(key, String(value));
const attrValue = typeof value === "string" ? value.trim() : String(value);
element.attr(key, attrValue);
}
else if (typeof value === 'object') {
// Complex values can't be DOM attributes, store as jQuery data only
@@ -1112,13 +1055,14 @@ class Jqhtml_Component {
this._dom_parent = null; // Closest component in DOM tree (for lifecycle)
this._dom_children = new Set(); // Components in DOM subtree (for lifecycle)
this._use_dom_fallback = false; // If true, use find() fallback instead of _dom_children optimization
this._destroyed = false;
this._stopped = false;
this._booted = false; // Guard to prevent double boot() calls
this._data_before_render = null; // Store data state before initial render
this._lifecycle_callbacks = new Map();
this._lifecycle_states = new Set(); // Track which lifecycle events have occurred
this.__loading = false; // Flag to prevent render() calls during on_load()
this._did_first_render = false; // Track if component has rendered at least once
this._render_count = 0; // Incremented each time _render() is called, used to detect stale renders
this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element
@@ -1211,15 +1155,20 @@ class Jqhtml_Component {
// Lifecycle Methods (called by LifecycleManager)
// -------------------------------------------------------------------------
/**
* Render phase - Create DOM structure
* Internal render phase - Create DOM structure
* Called top-down (parent before children) when part of lifecycle
* Can also be called directly to just re-render DOM without lifecycle hooks
* This is an internal method - users should call render() instead
*
* @param id Optional scoped ID - if provided, delegates to child component's render()
* @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
*/
async render(id = null) {
if (this._destroyed)
return;
_render(id = null) {
// Increment render count to track this specific render
this._render_count++;
const current_render_id = this._render_count;
if (this._stopped)
return current_render_id;
// If id provided, delegate to child component
if (id) {
// First check if element with scoped ID exists
@@ -1235,7 +1184,7 @@ class Jqhtml_Component {
`Element with $id="${id}" exists but is not initialized as a component.\n` +
`Add $redrawable attribute or make it a proper component.`);
}
return child.render();
return child._render();
}
// Prevent render() calls during on_load()
if (this.__loading) {
@@ -1255,15 +1204,23 @@ class Jqhtml_Component {
else {
this._use_dom_fallback = false;
}
// If this is not the first render, empty the DOM to clear previous children
// If this is not the first render, stop child components and clear DOM
if (this._did_first_render) {
// Stop all child components before clearing DOM
this.$.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._stopped) {
child._stop(); // Stop just the component, DOM will be cleared next
}
});
// Clear the DOM
this.$[0].innerHTML = '';
}
else {
this._did_first_render = true;
}
// Remove _Component_Destroyed class if present (allows re-render after destroy)
this.$.removeClass('_Component_Destroyed');
// Remove _Component_Stopped class if present (allows re-render after stop)
this.$.removeClass('_Component_Stopped');
// Capture data state before first render for comparison later
if (this._data_before_render === null) {
this._data_before_render = JSON.stringify(this.data);
@@ -1372,22 +1329,80 @@ class Jqhtml_Component {
// Instructions may contain ['_content', [...]] markers from content() calls
const flattenedInstructions = this._flatten_instructions(instructions);
// Process instructions to generate DOM
await process_instructions(flattenedInstructions, this.$, this);
// This kicks off child component boots but doesn't wait for them
process_instructions(flattenedInstructions, this.$, this);
}
// Don't update ready state here - let phases complete in order
this._update_debug_attrs();
this._log_lifecycle('render', 'complete');
// Call on_render() immediately after render completes
// This ensures event handlers are always re-attached after DOM updates
const renderResult = this.on_render();
if (renderResult && typeof renderResult.then === 'function') {
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
}
// Emit lifecycle event
this.trigger('render');
// Apply debug delay after render
const isRerender = this._ready_state >= 3; // Already rendered once
applyDebugDelay(isRerender ? 'rerender' : 'render');
// Return the render ID so callers can check if this render is still current
return current_render_id;
}
/**
* Public render method - re-renders component and completes lifecycle
* This is what users should call when they want to update a component.
*
* Lifecycle sequence:
* 1. _render() - Updates DOM synchronously, calls on_render(), fires 'render' event
* 2. Async continuation (fire and forget):
* - _wait_for_children_ready() - Waits for all children to reach ready state
* - on_ready() - Calls user's ready hook
* - trigger('ready') - Fires ready event
*
* Returns immediately after _render() completes - does NOT wait for children
*/
render(id = null) {
if (this._stopped)
return;
// If id provided, delegate to child component
if (id) {
const $element = this.$id(id);
if ($element.length === 0) {
throw new Error(`[JQHTML] render("${id}") - no such id.\n` +
`Component "${this.component_name()}" has no child element with $id="${id}".`);
}
const child = $element.data('_component');
if (!child) {
throw new Error(`[JQHTML] render("${id}") - element is not a component or does not have $redrawable attribute set.\n` +
`Element with $id="${id}" exists but is not initialized as a component.\n` +
`Add $redrawable attribute or make it a proper component.`);
}
return child.render();
}
// Execute render phase synchronously and capture render ID
const render_id = this._render();
// Fire off async lifecycle completion in background (don't await)
(async () => {
// Wait for all child components to be ready
await this._wait_for_children_ready();
// Check if this render is still current before calling on_ready
// If _render_count changed, another render happened and we should skip on_ready
if (this._render_count !== render_id) {
return; // Stale render, don't call on_ready
}
// Call on_ready hook
await this.on_ready();
// Trigger ready event
await this.trigger('ready');
})();
}
/**
* Alias for render() - re-renders component with current data
* Provided for API consistency and clarity
*/
async redraw(id = null) {
redraw(id = null) {
return this.render(id);
}
/**
@@ -1395,7 +1410,7 @@ class Jqhtml_Component {
* Called bottom-up (children before parent)
*/
async create() {
if (this._destroyed || this._ready_state >= 1)
if (this._stopped || this._ready_state >= 1)
return;
this._log_lifecycle('create', 'start');
// Call on_create() and validate it's synchronous
@@ -1417,7 +1432,7 @@ class Jqhtml_Component {
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*/
async load() {
if (this._destroyed || this._ready_state >= 2)
if (this._stopped || this._ready_state >= 2)
return;
this._log_lifecycle('load', 'start');
// Data is already initialized as {} in the constructor
@@ -1465,7 +1480,7 @@ class Jqhtml_Component {
* Called bottom-up (children before parent)
*/
async ready() {
if (this._destroyed || this._ready_state >= 4)
if (this._stopped || this._ready_state >= 4)
return;
this._log_lifecycle('ready', 'start');
// Wait for all children to reach ready state (bottom-up execution)
@@ -1508,7 +1523,7 @@ class Jqhtml_Component {
* Wipes the innerHTML, resets data to empty, and runs full lifecycle
*/
async reinitialize() {
if (this._destroyed)
if (this._stopped)
return;
this._log_lifecycle('reinitialize', 'start');
// Clear the DOM - use native innerHTML for better performance
@@ -1522,86 +1537,94 @@ class Jqhtml_Component {
// Clear DOM children tracking
this._dom_children.clear();
// Run full lifecycle sequence
await this.render();
await this._render();
await this.create();
await this.load();
// Re-render only if data changed during load
if (this.should_rerender()) {
await this.render();
await this._render();
}
await this.ready();
this._log_lifecycle('reinitialize', 'complete');
}
/**
* Reload data - re-fetch data and update the component
* Re-runs on_load(), then renders and calls on_ready()
* Reload component - re-fetch data and re-render
* Re-runs on_load(), always renders, and calls on_ready()
*/
async reload_data() {
if (this._destroyed)
async reload() {
if (this._stopped)
return;
this._log_lifecycle('reload_data', 'start');
// Capture state before on_load() for validation
const argsBeforeLoad = JSON.stringify(this.args);
const propertiesBeforeLoad = new Set(Object.keys(this));
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
try {
// Re-fetch data
await this.on_load();
this._log_lifecycle('reload', 'start');
// Check if component has custom on_load implementation
const has_custom_on_load = this.on_load !== Jqhtml_Component.prototype.on_load;
if (has_custom_on_load) {
// Capture state before on_load() for validation
const argsBeforeLoad = JSON.stringify(this.args);
const propertiesBeforeLoad = new Set(Object.keys(this));
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
try {
// Re-fetch data
await this.on_load();
}
finally {
// Always clear loading flag, even if on_load() throws
this.__loading = false;
}
// Validate that on_load() only modified this.data
const argsAfterLoad = JSON.stringify(this.args);
const propertiesAfterLoad = Object.keys(this);
// Check if args were modified
if (argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data. The this.args property is read-only.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Move your modifications to this.data instead.`);
}
// Check if new properties were added to the component instance
const newProperties = propertiesAfterLoad.filter(prop => !propertiesBeforeLoad.has(prop) && prop !== 'data');
if (newProperties.length > 0) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" added new properties in on_load().\n` +
`on_load() should ONLY modify this.data. New properties detected: ${newProperties.join(', ')}\n\n` +
`Fix: Store your data in this.data instead:\n` +
` ❌ this.${newProperties[0]} = value;\n` +
` ✅ this.data.${newProperties[0]} = value;`);
}
}
finally {
// Always clear loading flag, even if on_load() throws
this.__loading = false;
}
// Validate that on_load() only modified this.data
const argsAfterLoad = JSON.stringify(this.args);
const propertiesAfterLoad = Object.keys(this);
// Check if args were modified
if (argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data. The this.args property is read-only.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Move your modifications to this.data instead.`);
}
// Check if new properties were added to the component instance
const newProperties = propertiesAfterLoad.filter(prop => !propertiesBeforeLoad.has(prop) && prop !== 'data');
if (newProperties.length > 0) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" added new properties in on_load().\n` +
`on_load() should ONLY modify this.data. New properties detected: ${newProperties.join(', ')}\n\n` +
`Fix: Store your data in this.data instead:\n` +
` ❌ this.${newProperties[0]} = value;\n` +
` ✅ this.data.${newProperties[0]} = value;`);
}
// Re-render the component
// Always re-render the component with full lifecycle
// This calls: _render() → _wait_for_children_ready() → on_ready() → trigger('ready')
await this.render();
// Call on_ready after re-render
await this.on_ready();
this._log_lifecycle('reload_data', 'complete');
this._log_lifecycle('reload', 'complete');
}
/**
* Destroy the component and cleanup
* Called automatically by MutationObserver when component is removed from DOM
* Can also be called manually for explicit cleanup
*/
destroy() {
// Guard: prevent double destroy() calls
if (this._destroyed)
/**
* Internal stop method - stops just this component (no children)
* Sets stopped flag, calls lifecycle hooks, but leaves DOM intact
* @private
*/
_stop() {
// Guard: prevent double _stop() calls
if (this._stopped)
return;
this._destroyed = true;
this._stopped = true;
// Early bailout: skip expensive cleanup if no handlers registered
// Only matters for aborting boot() lifecycle - minimal cleanup sufficient
const has_custom_destroy = this.on_destroy !== Jqhtml_Component.prototype.on_destroy;
const has_destroy_callbacks = this._on_registered('destroy');
if (!has_custom_destroy && !has_destroy_callbacks) {
// Fast path: no cleanup logic defined, just mark as destroyed
// Fast path: no cleanup logic defined, just mark as stopped
this._lifecycle_manager.unregister_component(this);
this._ready_state = 99;
return;
}
// Full cleanup path: component has custom destroy logic
this._log_lifecycle('destroy', 'start');
this.$.addClass('_Component_Destroyed');
this.$.addClass('_Component_Stopped');
// Unregister from lifecycle manager
this._lifecycle_manager.unregister_component(this);
// Call user's on_destroy() hook
@@ -1622,6 +1645,21 @@ class Jqhtml_Component {
this._update_debug_attrs();
this._log_lifecycle('destroy', 'complete');
}
/**
* Stop component lifecycle - stops all descendant components then self
* Leaves DOM intact, just stops lifecycle engine and fires cleanup hooks
*/
stop() {
// Stop all descendant components (flat iteration, no recursion needed)
this.$.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._stopped) {
child._stop(); // Not recursive - we already have all descendants
}
});
// Then stop self
this._stop();
}
// -------------------------------------------------------------------------
// Overridable Lifecycle Hooks
// -------------------------------------------------------------------------
@@ -2766,8 +2804,9 @@ function init_jquery_plugin(jQuery) {
if (firstEl.length === 0)
return undefined;
const component = firstEl.data('_component');
if (component && typeof component.val === 'function') {
// Delegate to component's val() method
const tagName = firstEl.prop('tagName');
if (component && typeof component.val === 'function' && tagName !== 'INPUT' && tagName !== 'TEXTAREA') {
// Delegate to component's val() method (but not if the component IS an input/textarea)
return component.val();
}
// Fall back to original jQuery val()
@@ -2778,8 +2817,9 @@ function init_jquery_plugin(jQuery) {
this.each(function () {
const $el = jQuery(this);
const component = $el.data('_component');
if (component && typeof component.val === 'function') {
// Delegate to component's val() method
const tagName = $el.prop('tagName');
if (component && typeof component.val === 'function' && tagName !== 'INPUT' && tagName !== 'TEXTAREA') {
// Delegate to component's val() method (but not if the component IS an input/textarea)
component.val(value);
}
else {
@@ -2817,12 +2857,14 @@ function init_jquery_plugin(jQuery) {
// Look up by name (use default Component if not found)
componentName = componentOrName;
const found = get_component_class(componentOrName);
// Always pass _component_name when instantiating by string name
// This is critical for template resolution, especially when class is inherited via extends chain
// Example: 'Contacts_DataGrid' uses DataGrid_Abstract class but needs Contacts_DataGrid template
args = { ...args, _component_name: componentName };
if (!found) {
// Only warn if no template is defined either (truly undefined component)
// The get_template() call will handle the warning appropriately
// Use the base Jqhtml_Component class
// Pass the component name in args so it can find its template
args = { ...args, _component_name: componentName };
ComponentClass = Jqhtml_Component;
}
else {
@@ -2960,6 +3002,57 @@ function init_jquery_plugin(jQuery) {
// Return jQuery collection of results
return jQuery(results);
};
// Store original jQuery methods
const originalEmpty = jQuery.fn.empty;
const originalHtml = jQuery.fn.html;
const originalText = jQuery.fn.text;
/**
* Override jQuery.fn.empty() to stop child components before clearing DOM
* This ensures component cleanup hooks fire when DOM is cleared
*/
jQuery.fn.empty = function () {
return this.each(function () {
// Stop all child components before clearing DOM
jQuery(this).find('.Jqhtml_Component').each(function () {
const component = jQuery(this).data('_component');
if (component && !component._stopped) {
component._stop(); // Stop just the component, DOM will be cleared anyway
}
});
// Call original empty
originalEmpty.call(jQuery(this));
});
};
/**
* Override jQuery.fn.html() to stop child components when setting new content
* When used as setter, calls .empty() first to properly cleanup components
*/
jQuery.fn.html = function (value) {
// Getter - just pass through
if (arguments.length === 0) {
return originalHtml.call(this);
}
// Setter - empty first (which stops components), then set content
return this.each(function () {
jQuery(this).empty();
originalHtml.call(jQuery(this), value);
});
};
/**
* Override jQuery.fn.text() to stop child components when setting new content
* When used as setter, calls .empty() first to properly cleanup components
*/
jQuery.fn.text = function (value) {
// Getter - just pass through
if (arguments.length === 0) {
return originalText.call(this);
}
// Setter - empty first (which stops components), then set content
return this.each(function () {
jQuery(this).empty();
originalText.call(jQuery(this), value);
});
};
}
// Try to auto-initialize if global jQuery exists
if (typeof window !== 'undefined' && window.jQuery) {
@@ -2987,7 +3080,7 @@ function init(jQuery) {
}
}
// Version - will be replaced during build with actual version from package.json
const version = '2.2.176';
const version = '2.2.186';
// Default export with all functionality
const jqhtml = {
// Core

2
node_modules/@jqhtml/core/dist/index.cjs.map generated vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

0
node_modules/@jqhtml/core/dist/index.d.ts.map generated vendored Executable file → Normal file
View File

431
node_modules/@jqhtml/core/dist/index.js generated vendored Executable file → Normal file
View File

@@ -19,58 +19,11 @@ class LifecycleManager {
}
constructor() {
this.active_components = new Set();
this.observer = null;
// Initialize MutationObserver to detect component removal from DOM
if (typeof window !== 'undefined' && typeof MutationObserver !== 'undefined') {
this.observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// Convert NodeList to Array for iteration
Array.from(mutation.removedNodes).forEach(node => {
if (node.nodeType === 1) { // Element node
this._handle_node_removal(node);
}
});
}
});
// Start observing when DOM is ready
const startObserving = () => {
if (document.body) {
this.observer.observe(document.body, {
childList: true,
subtree: true
});
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startObserving);
}
else {
startObserving();
}
}
}
/**
* Handle node removal from DOM
* Destroys component and all descendant components
*/
_handle_node_removal(element) {
// Need to use global $ here
const $ = globalThis.$;
if (!$)
return;
const $el = $(element);
// Check if element itself is a component
const component = $el.data('_component');
if (component && !component._destroyed) {
component.destroy();
}
// Find and destroy all descendant components (flat iteration, no recursion)
$el.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._destroyed) {
child.destroy();
}
});
// No automatic cleanup on DOM removal - components stop explicitly via:
// 1. Parent component re-render (calls .empty() which stops children)
// 2. Manual .empty(), .html(), .text() calls (overridden to stop children)
// 3. Explicit .stop() call
// This allows components to be moved in DOM without lifecycle interruption
}
/**
* Boot a component - run its full lifecycle
@@ -81,54 +34,40 @@ class LifecycleManager {
try {
// Create phase - runs BEFORE first render
await component.create();
// Check if destroyed during create
if (component._destroyed)
// Check if stopped during create
if (component._stopped)
return;
// Trigger create event
component.trigger('create');
// Render phase - creates DOM and child components
await component.render();
// Check if destroyed during render
if (component._destroyed)
// Note: _render() now calls on_render() internally after DOM update
// Capture render ID to detect if another render happens before ready
let render_id = component._render();
// Check if stopped during render
if (component._stopped)
return;
// Call on_render() (synchronous)
const renderResult = component.on_render();
if (renderResult && typeof renderResult.then === 'function') {
console.warn(`[JQHTML] Component "${component.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
await renderResult;
}
// Check if destroyed during on_render
if (component._destroyed)
return;
// Trigger render event
component.trigger('render');
// Load phase - may modify this.data
await component.load();
// Check if destroyed during load
if (component._destroyed)
// Check if stopped during load
if (component._stopped)
return;
// If data changed during load, re-render
// Note: _render() now calls on_render() internally after DOM update
if (component.should_rerender()) {
await component.render();
// Check if destroyed during re-render
if (component._destroyed)
render_id = component._render();
// Check if stopped during re-render
if (component._stopped)
return;
const rerenderResult = component.on_render();
if (rerenderResult && typeof rerenderResult.then === 'function') {
console.warn(`[JQHTML] Component "${component.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
await rerenderResult;
}
// Check if destroyed during on_render
if (component._destroyed)
return;
component.trigger('render');
}
// Check if this render is still current before proceeding to ready
// If _render_count changed, another render happened and we should skip ready
if (component._render_count !== render_id) {
return; // Stale render, don't call ready
}
// Ready phase - waits for children, then calls on_ready()
await component.ready();
// Check if destroyed during ready
if (component._destroyed)
// Check if stopped during ready
if (component._stopped)
return;
// Trigger ready event
await component.trigger('ready');
@@ -470,7 +409,7 @@ function uid() {
* Process an array of instructions and append to target
* Uses v1 approach: build HTML string, set innerHTML, then initialize
*/
async function process_instructions(instructions, target, context, slots) {
function process_instructions(instructions, target, context, slots) {
// Build HTML string and track elements that need initialization
const html = [];
const tagElements = {};
@@ -494,7 +433,7 @@ async function process_instructions(instructions, target, context, slots) {
}
// Third pass: initialize and boot components in parallel
// Like v1, all sibling components at this level boot simultaneously
const component_boots = [];
// DO NOT await - let children boot in background while parent continues
for (const [cid, compData] of Object.entries(components)) {
// Use native querySelector for better performance
const el = target[0].querySelector(`[data-cid="${cid}"]`);
@@ -502,11 +441,10 @@ async function process_instructions(instructions, target, context, slots) {
const element = $(el);
el.removeAttribute('data-cid');
// Boot this component (which will render and boot its children recursively)
component_boots.push(initialize_component(element, compData));
// Fire and forget - don't wait for boot to complete
initialize_component(element, compData);
}
}
// Wait for all sibling components to complete their boot
await Promise.all(component_boots);
}
/**
* Process a single instruction into HTML
@@ -703,7 +641,8 @@ function apply_attributes(element, attrs, context) {
context.args[dataKey] = value;
// Set to element data-key for convienence if a simple value
if (typeof value == "string" || typeof value == "number") {
element.attr(`data-${dataKey}`, value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr(`data-${dataKey}`, attrValue);
}
// } else if (key.startsWith('@')) {
// // Event handler with @ prefix (e.g., @click)
@@ -744,7 +683,8 @@ function apply_attributes(element, attrs, context) {
}
else if (key.startsWith('data-')) {
// Data attributes - go into both DOM and context.args
element.attr(key, value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr(key, attrValue);
// Get version without 'data-' prefix
const dataKey = key.substring(5);
// Always set element.data
@@ -764,7 +704,8 @@ function apply_attributes(element, attrs, context) {
}
if (!existingClasses) {
// No existing classes - set directly
element.attr('class', value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr('class', attrValue);
}
else {
// Merge classes - split and add if not present
@@ -787,7 +728,8 @@ function apply_attributes(element, attrs, context) {
const existingStyle = element.attr('style');
if (!existingStyle) {
// No existing style - set directly
element.attr('style', value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr('style', attrValue);
}
else {
// Merge styles rule by rule
@@ -818,7 +760,8 @@ function apply_attributes(element, attrs, context) {
// These do NOT become data attributes or go into context.args
// Examples: href, title, role, aria-*, tabindex, etc.
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
element.attr(key, String(value));
const attrValue = typeof value === "string" ? value.trim() : String(value);
element.attr(key, attrValue);
}
else if (typeof value === 'object') {
// Complex values can't be DOM attributes, store as jQuery data only
@@ -1108,13 +1051,14 @@ class Jqhtml_Component {
this._dom_parent = null; // Closest component in DOM tree (for lifecycle)
this._dom_children = new Set(); // Components in DOM subtree (for lifecycle)
this._use_dom_fallback = false; // If true, use find() fallback instead of _dom_children optimization
this._destroyed = false;
this._stopped = false;
this._booted = false; // Guard to prevent double boot() calls
this._data_before_render = null; // Store data state before initial render
this._lifecycle_callbacks = new Map();
this._lifecycle_states = new Set(); // Track which lifecycle events have occurred
this.__loading = false; // Flag to prevent render() calls during on_load()
this._did_first_render = false; // Track if component has rendered at least once
this._render_count = 0; // Incremented each time _render() is called, used to detect stale renders
this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element
@@ -1207,15 +1151,20 @@ class Jqhtml_Component {
// Lifecycle Methods (called by LifecycleManager)
// -------------------------------------------------------------------------
/**
* Render phase - Create DOM structure
* Internal render phase - Create DOM structure
* Called top-down (parent before children) when part of lifecycle
* Can also be called directly to just re-render DOM without lifecycle hooks
* This is an internal method - users should call render() instead
*
* @param id Optional scoped ID - if provided, delegates to child component's render()
* @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
*/
async render(id = null) {
if (this._destroyed)
return;
_render(id = null) {
// Increment render count to track this specific render
this._render_count++;
const current_render_id = this._render_count;
if (this._stopped)
return current_render_id;
// If id provided, delegate to child component
if (id) {
// First check if element with scoped ID exists
@@ -1231,7 +1180,7 @@ class Jqhtml_Component {
`Element with $id="${id}" exists but is not initialized as a component.\n` +
`Add $redrawable attribute or make it a proper component.`);
}
return child.render();
return child._render();
}
// Prevent render() calls during on_load()
if (this.__loading) {
@@ -1251,15 +1200,23 @@ class Jqhtml_Component {
else {
this._use_dom_fallback = false;
}
// If this is not the first render, empty the DOM to clear previous children
// If this is not the first render, stop child components and clear DOM
if (this._did_first_render) {
// Stop all child components before clearing DOM
this.$.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._stopped) {
child._stop(); // Stop just the component, DOM will be cleared next
}
});
// Clear the DOM
this.$[0].innerHTML = '';
}
else {
this._did_first_render = true;
}
// Remove _Component_Destroyed class if present (allows re-render after destroy)
this.$.removeClass('_Component_Destroyed');
// Remove _Component_Stopped class if present (allows re-render after stop)
this.$.removeClass('_Component_Stopped');
// Capture data state before first render for comparison later
if (this._data_before_render === null) {
this._data_before_render = JSON.stringify(this.data);
@@ -1368,22 +1325,80 @@ class Jqhtml_Component {
// Instructions may contain ['_content', [...]] markers from content() calls
const flattenedInstructions = this._flatten_instructions(instructions);
// Process instructions to generate DOM
await process_instructions(flattenedInstructions, this.$, this);
// This kicks off child component boots but doesn't wait for them
process_instructions(flattenedInstructions, this.$, this);
}
// Don't update ready state here - let phases complete in order
this._update_debug_attrs();
this._log_lifecycle('render', 'complete');
// Call on_render() immediately after render completes
// This ensures event handlers are always re-attached after DOM updates
const renderResult = this.on_render();
if (renderResult && typeof renderResult.then === 'function') {
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
}
// Emit lifecycle event
this.trigger('render');
// Apply debug delay after render
const isRerender = this._ready_state >= 3; // Already rendered once
applyDebugDelay(isRerender ? 'rerender' : 'render');
// Return the render ID so callers can check if this render is still current
return current_render_id;
}
/**
* Public render method - re-renders component and completes lifecycle
* This is what users should call when they want to update a component.
*
* Lifecycle sequence:
* 1. _render() - Updates DOM synchronously, calls on_render(), fires 'render' event
* 2. Async continuation (fire and forget):
* - _wait_for_children_ready() - Waits for all children to reach ready state
* - on_ready() - Calls user's ready hook
* - trigger('ready') - Fires ready event
*
* Returns immediately after _render() completes - does NOT wait for children
*/
render(id = null) {
if (this._stopped)
return;
// If id provided, delegate to child component
if (id) {
const $element = this.$id(id);
if ($element.length === 0) {
throw new Error(`[JQHTML] render("${id}") - no such id.\n` +
`Component "${this.component_name()}" has no child element with $id="${id}".`);
}
const child = $element.data('_component');
if (!child) {
throw new Error(`[JQHTML] render("${id}") - element is not a component or does not have $redrawable attribute set.\n` +
`Element with $id="${id}" exists but is not initialized as a component.\n` +
`Add $redrawable attribute or make it a proper component.`);
}
return child.render();
}
// Execute render phase synchronously and capture render ID
const render_id = this._render();
// Fire off async lifecycle completion in background (don't await)
(async () => {
// Wait for all child components to be ready
await this._wait_for_children_ready();
// Check if this render is still current before calling on_ready
// If _render_count changed, another render happened and we should skip on_ready
if (this._render_count !== render_id) {
return; // Stale render, don't call on_ready
}
// Call on_ready hook
await this.on_ready();
// Trigger ready event
await this.trigger('ready');
})();
}
/**
* Alias for render() - re-renders component with current data
* Provided for API consistency and clarity
*/
async redraw(id = null) {
redraw(id = null) {
return this.render(id);
}
/**
@@ -1391,7 +1406,7 @@ class Jqhtml_Component {
* Called bottom-up (children before parent)
*/
async create() {
if (this._destroyed || this._ready_state >= 1)
if (this._stopped || this._ready_state >= 1)
return;
this._log_lifecycle('create', 'start');
// Call on_create() and validate it's synchronous
@@ -1413,7 +1428,7 @@ class Jqhtml_Component {
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*/
async load() {
if (this._destroyed || this._ready_state >= 2)
if (this._stopped || this._ready_state >= 2)
return;
this._log_lifecycle('load', 'start');
// Data is already initialized as {} in the constructor
@@ -1461,7 +1476,7 @@ class Jqhtml_Component {
* Called bottom-up (children before parent)
*/
async ready() {
if (this._destroyed || this._ready_state >= 4)
if (this._stopped || this._ready_state >= 4)
return;
this._log_lifecycle('ready', 'start');
// Wait for all children to reach ready state (bottom-up execution)
@@ -1504,7 +1519,7 @@ class Jqhtml_Component {
* Wipes the innerHTML, resets data to empty, and runs full lifecycle
*/
async reinitialize() {
if (this._destroyed)
if (this._stopped)
return;
this._log_lifecycle('reinitialize', 'start');
// Clear the DOM - use native innerHTML for better performance
@@ -1518,86 +1533,94 @@ class Jqhtml_Component {
// Clear DOM children tracking
this._dom_children.clear();
// Run full lifecycle sequence
await this.render();
await this._render();
await this.create();
await this.load();
// Re-render only if data changed during load
if (this.should_rerender()) {
await this.render();
await this._render();
}
await this.ready();
this._log_lifecycle('reinitialize', 'complete');
}
/**
* Reload data - re-fetch data and update the component
* Re-runs on_load(), then renders and calls on_ready()
* Reload component - re-fetch data and re-render
* Re-runs on_load(), always renders, and calls on_ready()
*/
async reload_data() {
if (this._destroyed)
async reload() {
if (this._stopped)
return;
this._log_lifecycle('reload_data', 'start');
// Capture state before on_load() for validation
const argsBeforeLoad = JSON.stringify(this.args);
const propertiesBeforeLoad = new Set(Object.keys(this));
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
try {
// Re-fetch data
await this.on_load();
this._log_lifecycle('reload', 'start');
// Check if component has custom on_load implementation
const has_custom_on_load = this.on_load !== Jqhtml_Component.prototype.on_load;
if (has_custom_on_load) {
// Capture state before on_load() for validation
const argsBeforeLoad = JSON.stringify(this.args);
const propertiesBeforeLoad = new Set(Object.keys(this));
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
try {
// Re-fetch data
await this.on_load();
}
finally {
// Always clear loading flag, even if on_load() throws
this.__loading = false;
}
// Validate that on_load() only modified this.data
const argsAfterLoad = JSON.stringify(this.args);
const propertiesAfterLoad = Object.keys(this);
// Check if args were modified
if (argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data. The this.args property is read-only.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Move your modifications to this.data instead.`);
}
// Check if new properties were added to the component instance
const newProperties = propertiesAfterLoad.filter(prop => !propertiesBeforeLoad.has(prop) && prop !== 'data');
if (newProperties.length > 0) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" added new properties in on_load().\n` +
`on_load() should ONLY modify this.data. New properties detected: ${newProperties.join(', ')}\n\n` +
`Fix: Store your data in this.data instead:\n` +
` ❌ this.${newProperties[0]} = value;\n` +
` ✅ this.data.${newProperties[0]} = value;`);
}
}
finally {
// Always clear loading flag, even if on_load() throws
this.__loading = false;
}
// Validate that on_load() only modified this.data
const argsAfterLoad = JSON.stringify(this.args);
const propertiesAfterLoad = Object.keys(this);
// Check if args were modified
if (argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data. The this.args property is read-only.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Move your modifications to this.data instead.`);
}
// Check if new properties were added to the component instance
const newProperties = propertiesAfterLoad.filter(prop => !propertiesBeforeLoad.has(prop) && prop !== 'data');
if (newProperties.length > 0) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" added new properties in on_load().\n` +
`on_load() should ONLY modify this.data. New properties detected: ${newProperties.join(', ')}\n\n` +
`Fix: Store your data in this.data instead:\n` +
` ❌ this.${newProperties[0]} = value;\n` +
` ✅ this.data.${newProperties[0]} = value;`);
}
// Re-render the component
// Always re-render the component with full lifecycle
// This calls: _render() → _wait_for_children_ready() → on_ready() → trigger('ready')
await this.render();
// Call on_ready after re-render
await this.on_ready();
this._log_lifecycle('reload_data', 'complete');
this._log_lifecycle('reload', 'complete');
}
/**
* Destroy the component and cleanup
* Called automatically by MutationObserver when component is removed from DOM
* Can also be called manually for explicit cleanup
*/
destroy() {
// Guard: prevent double destroy() calls
if (this._destroyed)
/**
* Internal stop method - stops just this component (no children)
* Sets stopped flag, calls lifecycle hooks, but leaves DOM intact
* @private
*/
_stop() {
// Guard: prevent double _stop() calls
if (this._stopped)
return;
this._destroyed = true;
this._stopped = true;
// Early bailout: skip expensive cleanup if no handlers registered
// Only matters for aborting boot() lifecycle - minimal cleanup sufficient
const has_custom_destroy = this.on_destroy !== Jqhtml_Component.prototype.on_destroy;
const has_destroy_callbacks = this._on_registered('destroy');
if (!has_custom_destroy && !has_destroy_callbacks) {
// Fast path: no cleanup logic defined, just mark as destroyed
// Fast path: no cleanup logic defined, just mark as stopped
this._lifecycle_manager.unregister_component(this);
this._ready_state = 99;
return;
}
// Full cleanup path: component has custom destroy logic
this._log_lifecycle('destroy', 'start');
this.$.addClass('_Component_Destroyed');
this.$.addClass('_Component_Stopped');
// Unregister from lifecycle manager
this._lifecycle_manager.unregister_component(this);
// Call user's on_destroy() hook
@@ -1618,6 +1641,21 @@ class Jqhtml_Component {
this._update_debug_attrs();
this._log_lifecycle('destroy', 'complete');
}
/**
* Stop component lifecycle - stops all descendant components then self
* Leaves DOM intact, just stops lifecycle engine and fires cleanup hooks
*/
stop() {
// Stop all descendant components (flat iteration, no recursion needed)
this.$.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._stopped) {
child._stop(); // Not recursive - we already have all descendants
}
});
// Then stop self
this._stop();
}
// -------------------------------------------------------------------------
// Overridable Lifecycle Hooks
// -------------------------------------------------------------------------
@@ -2762,8 +2800,9 @@ function init_jquery_plugin(jQuery) {
if (firstEl.length === 0)
return undefined;
const component = firstEl.data('_component');
if (component && typeof component.val === 'function') {
// Delegate to component's val() method
const tagName = firstEl.prop('tagName');
if (component && typeof component.val === 'function' && tagName !== 'INPUT' && tagName !== 'TEXTAREA') {
// Delegate to component's val() method (but not if the component IS an input/textarea)
return component.val();
}
// Fall back to original jQuery val()
@@ -2774,8 +2813,9 @@ function init_jquery_plugin(jQuery) {
this.each(function () {
const $el = jQuery(this);
const component = $el.data('_component');
if (component && typeof component.val === 'function') {
// Delegate to component's val() method
const tagName = $el.prop('tagName');
if (component && typeof component.val === 'function' && tagName !== 'INPUT' && tagName !== 'TEXTAREA') {
// Delegate to component's val() method (but not if the component IS an input/textarea)
component.val(value);
}
else {
@@ -2813,12 +2853,14 @@ function init_jquery_plugin(jQuery) {
// Look up by name (use default Component if not found)
componentName = componentOrName;
const found = get_component_class(componentOrName);
// Always pass _component_name when instantiating by string name
// This is critical for template resolution, especially when class is inherited via extends chain
// Example: 'Contacts_DataGrid' uses DataGrid_Abstract class but needs Contacts_DataGrid template
args = { ...args, _component_name: componentName };
if (!found) {
// Only warn if no template is defined either (truly undefined component)
// The get_template() call will handle the warning appropriately
// Use the base Jqhtml_Component class
// Pass the component name in args so it can find its template
args = { ...args, _component_name: componentName };
ComponentClass = Jqhtml_Component;
}
else {
@@ -2956,6 +2998,57 @@ function init_jquery_plugin(jQuery) {
// Return jQuery collection of results
return jQuery(results);
};
// Store original jQuery methods
const originalEmpty = jQuery.fn.empty;
const originalHtml = jQuery.fn.html;
const originalText = jQuery.fn.text;
/**
* Override jQuery.fn.empty() to stop child components before clearing DOM
* This ensures component cleanup hooks fire when DOM is cleared
*/
jQuery.fn.empty = function () {
return this.each(function () {
// Stop all child components before clearing DOM
jQuery(this).find('.Jqhtml_Component').each(function () {
const component = jQuery(this).data('_component');
if (component && !component._stopped) {
component._stop(); // Stop just the component, DOM will be cleared anyway
}
});
// Call original empty
originalEmpty.call(jQuery(this));
});
};
/**
* Override jQuery.fn.html() to stop child components when setting new content
* When used as setter, calls .empty() first to properly cleanup components
*/
jQuery.fn.html = function (value) {
// Getter - just pass through
if (arguments.length === 0) {
return originalHtml.call(this);
}
// Setter - empty first (which stops components), then set content
return this.each(function () {
jQuery(this).empty();
originalHtml.call(jQuery(this), value);
});
};
/**
* Override jQuery.fn.text() to stop child components when setting new content
* When used as setter, calls .empty() first to properly cleanup components
*/
jQuery.fn.text = function (value) {
// Getter - just pass through
if (arguments.length === 0) {
return originalText.call(this);
}
// Setter - empty first (which stops components), then set content
return this.each(function () {
jQuery(this).empty();
originalText.call(jQuery(this), value);
});
};
}
// Try to auto-initialize if global jQuery exists
if (typeof window !== 'undefined' && window.jQuery) {
@@ -2983,7 +3076,7 @@ function init(jQuery) {
}
}
// Version - will be replaced during build with actual version from package.json
const version = '2.2.176';
const version = '2.2.186';
// Default export with all functionality
const jqhtml = {
// Core

2
node_modules/@jqhtml/core/dist/index.js.map generated vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

View File

@@ -23,7 +23,7 @@ export declare function uid(): string;
* Process an array of instructions and append to target
* Uses v1 approach: build HTML string, set innerHTML, then initialize
*/
export declare function process_instructions(instructions: Instruction[], target: any, context: Jqhtml_Component, slots?: Record<string, SlotInstruction>): Promise<void>;
export declare function process_instructions(instructions: Instruction[], target: any, context: Jqhtml_Component, slots?: Record<string, SlotInstruction>): void;
/**
* Utility to extract slots from instructions
*/

2
node_modules/@jqhtml/core/dist/instruction-processor.d.ts.map generated vendored Executable file → Normal file
View File

@@ -1 +1 @@
{"version":3,"file":"instruction-processor.d.ts","sourceRoot":"","sources":["../src/instruction-processor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAIlD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;CAClH;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;CAClF;AAED,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,eAAe,GAAG,MAAM,CAAC;AAoB/G,wBAAgB,GAAG,IAAI,MAAM,CA2C5B;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,WAAW,EAAE,EAC3B,MAAM,EAAE,GAAG,EACX,OAAO,EAAE,gBAAgB,EACzB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAybD;;GAEG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAW1F"}
{"version":3,"file":"instruction-processor.d.ts","sourceRoot":"","sources":["../src/instruction-processor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAIlD,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;CAClH;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;CAClF;AAED,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,eAAe,GAAG,MAAM,CAAC;AAoB/G,wBAAgB,GAAG,IAAI,MAAM,CA2C5B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,WAAW,EAAE,EAC3B,MAAM,EAAE,GAAG,EACX,OAAO,EAAE,gBAAgB,EACzB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GACtC,IAAI,CAwCN;AA8bD;;GAEG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAW1F"}

433
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js generated vendored Executable file → Normal file
View File

@@ -1,5 +1,5 @@
/**
* JQHTML Core v2.2.176
* JQHTML Core v2.2.186
* (c) 2025 JQHTML Team
* Released under the MIT License
*/
@@ -24,58 +24,11 @@ class LifecycleManager {
}
constructor() {
this.active_components = new Set();
this.observer = null;
// Initialize MutationObserver to detect component removal from DOM
if (typeof window !== 'undefined' && typeof MutationObserver !== 'undefined') {
this.observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
// Convert NodeList to Array for iteration
Array.from(mutation.removedNodes).forEach(node => {
if (node.nodeType === 1) { // Element node
this._handle_node_removal(node);
}
});
}
});
// Start observing when DOM is ready
const startObserving = () => {
if (document.body) {
this.observer.observe(document.body, {
childList: true,
subtree: true
});
}
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startObserving);
}
else {
startObserving();
}
}
}
/**
* Handle node removal from DOM
* Destroys component and all descendant components
*/
_handle_node_removal(element) {
// Need to use global $ here
const $ = globalThis.$;
if (!$)
return;
const $el = $(element);
// Check if element itself is a component
const component = $el.data('_component');
if (component && !component._destroyed) {
component.destroy();
}
// Find and destroy all descendant components (flat iteration, no recursion)
$el.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._destroyed) {
child.destroy();
}
});
// No automatic cleanup on DOM removal - components stop explicitly via:
// 1. Parent component re-render (calls .empty() which stops children)
// 2. Manual .empty(), .html(), .text() calls (overridden to stop children)
// 3. Explicit .stop() call
// This allows components to be moved in DOM without lifecycle interruption
}
/**
* Boot a component - run its full lifecycle
@@ -86,54 +39,40 @@ class LifecycleManager {
try {
// Create phase - runs BEFORE first render
await component.create();
// Check if destroyed during create
if (component._destroyed)
// Check if stopped during create
if (component._stopped)
return;
// Trigger create event
component.trigger('create');
// Render phase - creates DOM and child components
await component.render();
// Check if destroyed during render
if (component._destroyed)
// Note: _render() now calls on_render() internally after DOM update
// Capture render ID to detect if another render happens before ready
let render_id = component._render();
// Check if stopped during render
if (component._stopped)
return;
// Call on_render() (synchronous)
const renderResult = component.on_render();
if (renderResult && typeof renderResult.then === 'function') {
console.warn(`[JQHTML] Component "${component.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
await renderResult;
}
// Check if destroyed during on_render
if (component._destroyed)
return;
// Trigger render event
component.trigger('render');
// Load phase - may modify this.data
await component.load();
// Check if destroyed during load
if (component._destroyed)
// Check if stopped during load
if (component._stopped)
return;
// If data changed during load, re-render
// Note: _render() now calls on_render() internally after DOM update
if (component.should_rerender()) {
await component.render();
// Check if destroyed during re-render
if (component._destroyed)
render_id = component._render();
// Check if stopped during re-render
if (component._stopped)
return;
const rerenderResult = component.on_render();
if (rerenderResult && typeof rerenderResult.then === 'function') {
console.warn(`[JQHTML] Component "${component.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
await rerenderResult;
}
// Check if destroyed during on_render
if (component._destroyed)
return;
component.trigger('render');
}
// Check if this render is still current before proceeding to ready
// If _render_count changed, another render happened and we should skip ready
if (component._render_count !== render_id) {
return; // Stale render, don't call ready
}
// Ready phase - waits for children, then calls on_ready()
await component.ready();
// Check if destroyed during ready
if (component._destroyed)
// Check if stopped during ready
if (component._stopped)
return;
// Trigger ready event
await component.trigger('ready');
@@ -475,7 +414,7 @@ function uid() {
* Process an array of instructions and append to target
* Uses v1 approach: build HTML string, set innerHTML, then initialize
*/
async function process_instructions(instructions, target, context, slots) {
function process_instructions(instructions, target, context, slots) {
// Build HTML string and track elements that need initialization
const html = [];
const tagElements = {};
@@ -499,7 +438,7 @@ async function process_instructions(instructions, target, context, slots) {
}
// Third pass: initialize and boot components in parallel
// Like v1, all sibling components at this level boot simultaneously
const component_boots = [];
// DO NOT await - let children boot in background while parent continues
for (const [cid, compData] of Object.entries(components)) {
// Use native querySelector for better performance
const el = target[0].querySelector(`[data-cid="${cid}"]`);
@@ -507,11 +446,10 @@ async function process_instructions(instructions, target, context, slots) {
const element = $(el);
el.removeAttribute('data-cid');
// Boot this component (which will render and boot its children recursively)
component_boots.push(initialize_component(element, compData));
// Fire and forget - don't wait for boot to complete
initialize_component(element, compData);
}
}
// Wait for all sibling components to complete their boot
await Promise.all(component_boots);
}
/**
* Process a single instruction into HTML
@@ -708,7 +646,8 @@ function apply_attributes(element, attrs, context) {
context.args[dataKey] = value;
// Set to element data-key for convienence if a simple value
if (typeof value == "string" || typeof value == "number") {
element.attr(`data-${dataKey}`, value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr(`data-${dataKey}`, attrValue);
}
// } else if (key.startsWith('@')) {
// // Event handler with @ prefix (e.g., @click)
@@ -749,7 +688,8 @@ function apply_attributes(element, attrs, context) {
}
else if (key.startsWith('data-')) {
// Data attributes - go into both DOM and context.args
element.attr(key, value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr(key, attrValue);
// Get version without 'data-' prefix
const dataKey = key.substring(5);
// Always set element.data
@@ -769,7 +709,8 @@ function apply_attributes(element, attrs, context) {
}
if (!existingClasses) {
// No existing classes - set directly
element.attr('class', value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr('class', attrValue);
}
else {
// Merge classes - split and add if not present
@@ -792,7 +733,8 @@ function apply_attributes(element, attrs, context) {
const existingStyle = element.attr('style');
if (!existingStyle) {
// No existing style - set directly
element.attr('style', value);
const attrValue = typeof value === "string" ? value.trim() : value;
element.attr('style', attrValue);
}
else {
// Merge styles rule by rule
@@ -823,7 +765,8 @@ function apply_attributes(element, attrs, context) {
// These do NOT become data attributes or go into context.args
// Examples: href, title, role, aria-*, tabindex, etc.
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
element.attr(key, String(value));
const attrValue = typeof value === "string" ? value.trim() : String(value);
element.attr(key, attrValue);
}
else if (typeof value === 'object') {
// Complex values can't be DOM attributes, store as jQuery data only
@@ -1113,13 +1056,14 @@ class Jqhtml_Component {
this._dom_parent = null; // Closest component in DOM tree (for lifecycle)
this._dom_children = new Set(); // Components in DOM subtree (for lifecycle)
this._use_dom_fallback = false; // If true, use find() fallback instead of _dom_children optimization
this._destroyed = false;
this._stopped = false;
this._booted = false; // Guard to prevent double boot() calls
this._data_before_render = null; // Store data state before initial render
this._lifecycle_callbacks = new Map();
this._lifecycle_states = new Set(); // Track which lifecycle events have occurred
this.__loading = false; // Flag to prevent render() calls during on_load()
this._did_first_render = false; // Track if component has rendered at least once
this._render_count = 0; // Incremented each time _render() is called, used to detect stale renders
this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element
@@ -1212,15 +1156,20 @@ class Jqhtml_Component {
// Lifecycle Methods (called by LifecycleManager)
// -------------------------------------------------------------------------
/**
* Render phase - Create DOM structure
* Internal render phase - Create DOM structure
* Called top-down (parent before children) when part of lifecycle
* Can also be called directly to just re-render DOM without lifecycle hooks
* This is an internal method - users should call render() instead
*
* @param id Optional scoped ID - if provided, delegates to child component's render()
* @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
*/
async render(id = null) {
if (this._destroyed)
return;
_render(id = null) {
// Increment render count to track this specific render
this._render_count++;
const current_render_id = this._render_count;
if (this._stopped)
return current_render_id;
// If id provided, delegate to child component
if (id) {
// First check if element with scoped ID exists
@@ -1236,7 +1185,7 @@ class Jqhtml_Component {
`Element with $id="${id}" exists but is not initialized as a component.\n` +
`Add $redrawable attribute or make it a proper component.`);
}
return child.render();
return child._render();
}
// Prevent render() calls during on_load()
if (this.__loading) {
@@ -1256,15 +1205,23 @@ class Jqhtml_Component {
else {
this._use_dom_fallback = false;
}
// If this is not the first render, empty the DOM to clear previous children
// If this is not the first render, stop child components and clear DOM
if (this._did_first_render) {
// Stop all child components before clearing DOM
this.$.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._stopped) {
child._stop(); // Stop just the component, DOM will be cleared next
}
});
// Clear the DOM
this.$[0].innerHTML = '';
}
else {
this._did_first_render = true;
}
// Remove _Component_Destroyed class if present (allows re-render after destroy)
this.$.removeClass('_Component_Destroyed');
// Remove _Component_Stopped class if present (allows re-render after stop)
this.$.removeClass('_Component_Stopped');
// Capture data state before first render for comparison later
if (this._data_before_render === null) {
this._data_before_render = JSON.stringify(this.data);
@@ -1373,22 +1330,80 @@ class Jqhtml_Component {
// Instructions may contain ['_content', [...]] markers from content() calls
const flattenedInstructions = this._flatten_instructions(instructions);
// Process instructions to generate DOM
await process_instructions(flattenedInstructions, this.$, this);
// This kicks off child component boots but doesn't wait for them
process_instructions(flattenedInstructions, this.$, this);
}
// Don't update ready state here - let phases complete in order
this._update_debug_attrs();
this._log_lifecycle('render', 'complete');
// Call on_render() immediately after render completes
// This ensures event handlers are always re-attached after DOM updates
const renderResult = this.on_render();
if (renderResult && typeof renderResult.then === 'function') {
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_render(). ` +
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
}
// Emit lifecycle event
this.trigger('render');
// Apply debug delay after render
const isRerender = this._ready_state >= 3; // Already rendered once
applyDebugDelay(isRerender ? 'rerender' : 'render');
// Return the render ID so callers can check if this render is still current
return current_render_id;
}
/**
* Public render method - re-renders component and completes lifecycle
* This is what users should call when they want to update a component.
*
* Lifecycle sequence:
* 1. _render() - Updates DOM synchronously, calls on_render(), fires 'render' event
* 2. Async continuation (fire and forget):
* - _wait_for_children_ready() - Waits for all children to reach ready state
* - on_ready() - Calls user's ready hook
* - trigger('ready') - Fires ready event
*
* Returns immediately after _render() completes - does NOT wait for children
*/
render(id = null) {
if (this._stopped)
return;
// If id provided, delegate to child component
if (id) {
const $element = this.$id(id);
if ($element.length === 0) {
throw new Error(`[JQHTML] render("${id}") - no such id.\n` +
`Component "${this.component_name()}" has no child element with $id="${id}".`);
}
const child = $element.data('_component');
if (!child) {
throw new Error(`[JQHTML] render("${id}") - element is not a component or does not have $redrawable attribute set.\n` +
`Element with $id="${id}" exists but is not initialized as a component.\n` +
`Add $redrawable attribute or make it a proper component.`);
}
return child.render();
}
// Execute render phase synchronously and capture render ID
const render_id = this._render();
// Fire off async lifecycle completion in background (don't await)
(async () => {
// Wait for all child components to be ready
await this._wait_for_children_ready();
// Check if this render is still current before calling on_ready
// If _render_count changed, another render happened and we should skip on_ready
if (this._render_count !== render_id) {
return; // Stale render, don't call on_ready
}
// Call on_ready hook
await this.on_ready();
// Trigger ready event
await this.trigger('ready');
})();
}
/**
* Alias for render() - re-renders component with current data
* Provided for API consistency and clarity
*/
async redraw(id = null) {
redraw(id = null) {
return this.render(id);
}
/**
@@ -1396,7 +1411,7 @@ class Jqhtml_Component {
* Called bottom-up (children before parent)
*/
async create() {
if (this._destroyed || this._ready_state >= 1)
if (this._stopped || this._ready_state >= 1)
return;
this._log_lifecycle('create', 'start');
// Call on_create() and validate it's synchronous
@@ -1418,7 +1433,7 @@ class Jqhtml_Component {
* NO DOM MODIFICATIONS ALLOWED IN THIS PHASE
*/
async load() {
if (this._destroyed || this._ready_state >= 2)
if (this._stopped || this._ready_state >= 2)
return;
this._log_lifecycle('load', 'start');
// Data is already initialized as {} in the constructor
@@ -1466,7 +1481,7 @@ class Jqhtml_Component {
* Called bottom-up (children before parent)
*/
async ready() {
if (this._destroyed || this._ready_state >= 4)
if (this._stopped || this._ready_state >= 4)
return;
this._log_lifecycle('ready', 'start');
// Wait for all children to reach ready state (bottom-up execution)
@@ -1509,7 +1524,7 @@ class Jqhtml_Component {
* Wipes the innerHTML, resets data to empty, and runs full lifecycle
*/
async reinitialize() {
if (this._destroyed)
if (this._stopped)
return;
this._log_lifecycle('reinitialize', 'start');
// Clear the DOM - use native innerHTML for better performance
@@ -1523,86 +1538,94 @@ class Jqhtml_Component {
// Clear DOM children tracking
this._dom_children.clear();
// Run full lifecycle sequence
await this.render();
await this._render();
await this.create();
await this.load();
// Re-render only if data changed during load
if (this.should_rerender()) {
await this.render();
await this._render();
}
await this.ready();
this._log_lifecycle('reinitialize', 'complete');
}
/**
* Reload data - re-fetch data and update the component
* Re-runs on_load(), then renders and calls on_ready()
* Reload component - re-fetch data and re-render
* Re-runs on_load(), always renders, and calls on_ready()
*/
async reload_data() {
if (this._destroyed)
async reload() {
if (this._stopped)
return;
this._log_lifecycle('reload_data', 'start');
// Capture state before on_load() for validation
const argsBeforeLoad = JSON.stringify(this.args);
const propertiesBeforeLoad = new Set(Object.keys(this));
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
try {
// Re-fetch data
await this.on_load();
this._log_lifecycle('reload', 'start');
// Check if component has custom on_load implementation
const has_custom_on_load = this.on_load !== Jqhtml_Component.prototype.on_load;
if (has_custom_on_load) {
// Capture state before on_load() for validation
const argsBeforeLoad = JSON.stringify(this.args);
const propertiesBeforeLoad = new Set(Object.keys(this));
// Set loading flag to prevent render() calls during on_load()
this.__loading = true;
try {
// Re-fetch data
await this.on_load();
}
finally {
// Always clear loading flag, even if on_load() throws
this.__loading = false;
}
// Validate that on_load() only modified this.data
const argsAfterLoad = JSON.stringify(this.args);
const propertiesAfterLoad = Object.keys(this);
// Check if args were modified
if (argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data. The this.args property is read-only.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Move your modifications to this.data instead.`);
}
// Check if new properties were added to the component instance
const newProperties = propertiesAfterLoad.filter(prop => !propertiesBeforeLoad.has(prop) && prop !== 'data');
if (newProperties.length > 0) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" added new properties in on_load().\n` +
`on_load() should ONLY modify this.data. New properties detected: ${newProperties.join(', ')}\n\n` +
`Fix: Store your data in this.data instead:\n` +
` ❌ this.${newProperties[0]} = value;\n` +
` ✅ this.data.${newProperties[0]} = value;`);
}
}
finally {
// Always clear loading flag, even if on_load() throws
this.__loading = false;
}
// Validate that on_load() only modified this.data
const argsAfterLoad = JSON.stringify(this.args);
const propertiesAfterLoad = Object.keys(this);
// Check if args were modified
if (argsBeforeLoad !== argsAfterLoad) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" modified this.args in on_load().\n` +
`on_load() should ONLY modify this.data. The this.args property is read-only.\n\n` +
`Before: ${argsBeforeLoad}\n` +
`After: ${argsAfterLoad}\n\n` +
`Fix: Move your modifications to this.data instead.`);
}
// Check if new properties were added to the component instance
const newProperties = propertiesAfterLoad.filter(prop => !propertiesBeforeLoad.has(prop) && prop !== 'data');
if (newProperties.length > 0) {
console.error(`[JQHTML] WARNING: Component "${this.component_name()}" added new properties in on_load().\n` +
`on_load() should ONLY modify this.data. New properties detected: ${newProperties.join(', ')}\n\n` +
`Fix: Store your data in this.data instead:\n` +
` ❌ this.${newProperties[0]} = value;\n` +
` ✅ this.data.${newProperties[0]} = value;`);
}
// Re-render the component
// Always re-render the component with full lifecycle
// This calls: _render() → _wait_for_children_ready() → on_ready() → trigger('ready')
await this.render();
// Call on_ready after re-render
await this.on_ready();
this._log_lifecycle('reload_data', 'complete');
this._log_lifecycle('reload', 'complete');
}
/**
* Destroy the component and cleanup
* Called automatically by MutationObserver when component is removed from DOM
* Can also be called manually for explicit cleanup
*/
destroy() {
// Guard: prevent double destroy() calls
if (this._destroyed)
/**
* Internal stop method - stops just this component (no children)
* Sets stopped flag, calls lifecycle hooks, but leaves DOM intact
* @private
*/
_stop() {
// Guard: prevent double _stop() calls
if (this._stopped)
return;
this._destroyed = true;
this._stopped = true;
// Early bailout: skip expensive cleanup if no handlers registered
// Only matters for aborting boot() lifecycle - minimal cleanup sufficient
const has_custom_destroy = this.on_destroy !== Jqhtml_Component.prototype.on_destroy;
const has_destroy_callbacks = this._on_registered('destroy');
if (!has_custom_destroy && !has_destroy_callbacks) {
// Fast path: no cleanup logic defined, just mark as destroyed
// Fast path: no cleanup logic defined, just mark as stopped
this._lifecycle_manager.unregister_component(this);
this._ready_state = 99;
return;
}
// Full cleanup path: component has custom destroy logic
this._log_lifecycle('destroy', 'start');
this.$.addClass('_Component_Destroyed');
this.$.addClass('_Component_Stopped');
// Unregister from lifecycle manager
this._lifecycle_manager.unregister_component(this);
// Call user's on_destroy() hook
@@ -1623,6 +1646,21 @@ class Jqhtml_Component {
this._update_debug_attrs();
this._log_lifecycle('destroy', 'complete');
}
/**
* Stop component lifecycle - stops all descendant components then self
* Leaves DOM intact, just stops lifecycle engine and fires cleanup hooks
*/
stop() {
// Stop all descendant components (flat iteration, no recursion needed)
this.$.find('.Jqhtml_Component').each(function () {
const child = $(this).data('_component');
if (child && !child._stopped) {
child._stop(); // Not recursive - we already have all descendants
}
});
// Then stop self
this._stop();
}
// -------------------------------------------------------------------------
// Overridable Lifecycle Hooks
// -------------------------------------------------------------------------
@@ -2767,8 +2805,9 @@ function init_jquery_plugin(jQuery) {
if (firstEl.length === 0)
return undefined;
const component = firstEl.data('_component');
if (component && typeof component.val === 'function') {
// Delegate to component's val() method
const tagName = firstEl.prop('tagName');
if (component && typeof component.val === 'function' && tagName !== 'INPUT' && tagName !== 'TEXTAREA') {
// Delegate to component's val() method (but not if the component IS an input/textarea)
return component.val();
}
// Fall back to original jQuery val()
@@ -2779,8 +2818,9 @@ function init_jquery_plugin(jQuery) {
this.each(function () {
const $el = jQuery(this);
const component = $el.data('_component');
if (component && typeof component.val === 'function') {
// Delegate to component's val() method
const tagName = $el.prop('tagName');
if (component && typeof component.val === 'function' && tagName !== 'INPUT' && tagName !== 'TEXTAREA') {
// Delegate to component's val() method (but not if the component IS an input/textarea)
component.val(value);
}
else {
@@ -2818,12 +2858,14 @@ function init_jquery_plugin(jQuery) {
// Look up by name (use default Component if not found)
componentName = componentOrName;
const found = get_component_class(componentOrName);
// Always pass _component_name when instantiating by string name
// This is critical for template resolution, especially when class is inherited via extends chain
// Example: 'Contacts_DataGrid' uses DataGrid_Abstract class but needs Contacts_DataGrid template
args = { ...args, _component_name: componentName };
if (!found) {
// Only warn if no template is defined either (truly undefined component)
// The get_template() call will handle the warning appropriately
// Use the base Jqhtml_Component class
// Pass the component name in args so it can find its template
args = { ...args, _component_name: componentName };
ComponentClass = Jqhtml_Component;
}
else {
@@ -2961,6 +3003,57 @@ function init_jquery_plugin(jQuery) {
// Return jQuery collection of results
return jQuery(results);
};
// Store original jQuery methods
const originalEmpty = jQuery.fn.empty;
const originalHtml = jQuery.fn.html;
const originalText = jQuery.fn.text;
/**
* Override jQuery.fn.empty() to stop child components before clearing DOM
* This ensures component cleanup hooks fire when DOM is cleared
*/
jQuery.fn.empty = function () {
return this.each(function () {
// Stop all child components before clearing DOM
jQuery(this).find('.Jqhtml_Component').each(function () {
const component = jQuery(this).data('_component');
if (component && !component._stopped) {
component._stop(); // Stop just the component, DOM will be cleared anyway
}
});
// Call original empty
originalEmpty.call(jQuery(this));
});
};
/**
* Override jQuery.fn.html() to stop child components when setting new content
* When used as setter, calls .empty() first to properly cleanup components
*/
jQuery.fn.html = function (value) {
// Getter - just pass through
if (arguments.length === 0) {
return originalHtml.call(this);
}
// Setter - empty first (which stops components), then set content
return this.each(function () {
jQuery(this).empty();
originalHtml.call(jQuery(this), value);
});
};
/**
* Override jQuery.fn.text() to stop child components when setting new content
* When used as setter, calls .empty() first to properly cleanup components
*/
jQuery.fn.text = function (value) {
// Getter - just pass through
if (arguments.length === 0) {
return originalText.call(this);
}
// Setter - empty first (which stops components), then set content
return this.each(function () {
jQuery(this).empty();
originalText.call(jQuery(this), value);
});
};
}
// Try to auto-initialize if global jQuery exists
if (typeof window !== 'undefined' && window.jQuery) {
@@ -2988,7 +3081,7 @@ function init(jQuery) {
}
}
// Version - will be replaced during build with actual version from package.json
const version = '2.2.176';
const version = '2.2.186';
// Default export with all functionality
const jqhtml = {
// Core

2
node_modules/@jqhtml/core/dist/jqhtml-core.esm.js.map generated vendored Executable file → Normal file

File diff suppressed because one or more lines are too long

0
node_modules/@jqhtml/core/dist/jqhtml-debug.esm.js generated vendored Executable file → Normal file
View File

0
node_modules/@jqhtml/core/dist/jqhtml-debug.esm.js.map generated vendored Executable file → Normal file
View File

2
node_modules/@jqhtml/core/dist/jquery-plugin.d.ts.map generated vendored Executable file → Normal file
View File

@@ -1 +1 @@
{"version":3,"file":"jquery-plugin.d.ts","sourceRoot":"","sources":["../src/jquery-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAQpE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd;;WAEG;QACH,SAAS,IAAI,gBAAgB,GAAG,IAAI,CAAC;QACrC,SAAS,CAAC,cAAc,EAAE,oBAAoB,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAAC;QAC9F,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAAC;QAE/E;;;;;;;WAOG;QACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;KACvC;CACF;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CA2SpD"}
{"version":3,"file":"jquery-plugin.d.ts","sourceRoot":"","sources":["../src/jquery-plugin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAQpE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd;;WAEG;QACH,SAAS,IAAI,gBAAgB,GAAG,IAAI,CAAC;QACrC,SAAS,CAAC,cAAc,EAAE,oBAAoB,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAAC;QAC9F,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,gBAAgB,CAAC;QAE/E;;;;;;;WAOG;QACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;KACvC;CACF;AAGD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,GAAG,GAAG,IAAI,CA2WpD"}

View File

@@ -15,14 +15,8 @@ export type LifecyclePhase = 'render' | 'create' | 'load' | 'ready';
export declare class LifecycleManager {
private static instance;
private active_components;
private observer;
static get_instance(): LifecycleManager;
constructor();
/**
* Handle node removal from DOM
* Destroys component and all descendant components
*/
private _handle_node_removal;
/**
* Boot a component - run its full lifecycle
* Called when component is created

2
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts.map generated vendored Executable file → Normal file
View File

@@ -1 +1 @@
{"version":3,"file":"lifecycle-manager.d.ts","sourceRoot":"","sources":["../src/lifecycle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmB;IAC1C,OAAO,CAAC,iBAAiB,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAiC;IAEjD,MAAM,CAAC,YAAY,IAAI,gBAAgB;;IAuCvC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAsB5B;;;OAGG;IACG,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8EhE;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIvD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAetC"}
{"version":3,"file":"lifecycle-manager.d.ts","sourceRoot":"","sources":["../src/lifecycle-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAEpE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAmB;IAC1C,OAAO,CAAC,iBAAiB,CAAoC;IAE7D,MAAM,CAAC,YAAY,IAAI,gBAAgB;;IAevC;;;OAGG;IACG,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyDhE;;OAEG;IACH,oBAAoB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI;IAIvD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CAetC"}

0
node_modules/@jqhtml/core/dist/template-renderer.d.ts.map generated vendored Executable file → Normal file
View File

2
node_modules/@jqhtml/core/package.json generated vendored Executable file → Normal file
View File

@@ -1,6 +1,6 @@
{
"name": "@jqhtml/core",
"version": "2.2.176",
"version": "2.2.186",
"description": "Core runtime library for JQHTML",
"type": "module",
"main": "./dist/index.js",