Fix detached action redirect loops, abstract Spa_Action detection, jqhtml update

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-10 23:46:50 +00:00
parent 2f2cf41139
commit 3c25b2ff80
87 changed files with 419 additions and 72 deletions

View File

@@ -1001,11 +1001,14 @@ class Spa {
const $detached = $('<div>'); const $detached = $('<div>');
// Instantiate the action on the detached element // Instantiate the action on the detached element
// This triggers the full component lifecycle: on_create -> render -> on_render -> on_load -> on_ready // Skip render and on_ready phases - detached actions are for data extraction only
$detached.component(action_name, args); // (e.g., getting title/breadcrumbs). Running on_ready() could trigger side effects
// like Spa.dispatch() which would cause redirect loops.
const options = { skip_render_and_ready: true };
$detached.component(action_name, args, options);
const action = $detached.component(); const action = $detached.component();
// Wait for the action to be fully ready (including on_load completion) // Wait for on_load to complete (data fetching)
await action.ready(); await action.ready();
console_debug('Spa', `load_detached_action: ${action_name} ready`); console_debug('Spa', `load_detached_action: ${action_name} ready`);

View File

@@ -78,4 +78,71 @@ class Spa_Action extends Component {
const url = this.url(params); const url = this.url(params);
Spa.dispatch(url); Spa.dispatch(url);
} }
// =========================================================================
// Page Title & Breadcrumb System
// =========================================================================
/**
* Page title displayed in the header/title area
*
* Override this in every action to provide a meaningful page title.
* For entity pages, include entity details (e.g., "Contact: John Smith C001")
*
* @returns {Promise<string>} The page title
*/
async page_title() {
return '(title not set)';
}
/**
* Breadcrumb label for this action
*
* Used when this action appears as a parent in another action's breadcrumb chain.
* For entity pages (viewing a specific user, contact, etc.), return the entity name.
* For list/index pages, return the section name.
*
* Default: Returns page_title()
*
* @returns {Promise<string>} The breadcrumb label
*/
async breadcrumb_label() {
return await this.page_title();
}
/**
* Breadcrumb label when this action is the active/last crumb
*
* Use this to show a descriptive action name instead of the entity name
* when the entity name is already visible in the page title above.
*
* Example: For a user profile view page:
* - page_title() = "User Profile: John Smith U001"
* - breadcrumb_label() = "John Smith" (for when it's a parent)
* - breadcrumb_label_active() = "View User Profile" (avoids redundancy with title)
*
* Default: Returns breadcrumb_label()
*
* @returns {Promise<string>} The active breadcrumb label
*/
async breadcrumb_label_active() {
return await this.breadcrumb_label();
}
/**
* Parent action URL for breadcrumb chain
*
* Return the URL of the parent action using Rsx.Route().
* Return null if this action is a root (no parent breadcrumb).
*
* Example:
* async breadcrumb_parent() {
* return Rsx.Route('Settings_Users_Action');
* }
*
* @returns {Promise<string|null>} Parent URL or null if root
*/
async breadcrumb_parent() {
return null;
}
} }

View File

@@ -350,6 +350,38 @@ PERFORMANCE CONSIDERATIONS
Excessive decorator usage can increase bundle size. Excessive decorator usage can increase bundle size.
Consider if simple function calls would be more appropriate. Consider if simple function calls would be more appropriate.
DECORATOR PARAMETER RESTRICTIONS
Class Name Identifiers Prohibited (JS-DECORATOR-IDENT-01):
Decorator parameters MUST NOT use class name identifiers (bare class
references). The framework cannot guarantee class definition order in
compiled bundle output, so referencing a class in a decorator parameter
may fail if that class hasn't been defined yet.
// WRONG - class identifier as parameter
@some_decorator(User_Model)
class My_Action extends Spa_Action { }
// CORRECT - use string literal instead
@some_decorator('User_Model')
class My_Action extends Spa_Action { }
Why This Matters:
Bundle compilation orders classes based on inheritance hierarchies
(extends chains) and other factors, but NOT based on decorator
parameter dependencies. If Class A's decorator references Class B,
there's no guarantee Class B will be defined before Class A's
decorator is evaluated.
Resolution:
Pass class names as strings and resolve at runtime if needed:
// In decorator implementation or consuming code
const cls = Manifest.get_class_by_name('User_Model');
Enforcement:
This restriction is enforced by the JS-DECORATOR-IDENT-01 code quality
rule, which runs at manifest build time for immediate feedback.
SEE ALSO SEE ALSO
bundle_api - Bundle loading and compilation bundle_api - Bundle loading and compilation
manifest_api - Build-time validation system manifest_api - Build-time validation system

View File

@@ -779,6 +779,54 @@ COMMON PATTERNS
@layout('Frontend_Layout') @layout('Frontend_Layout')
class Frontend_Contacts_Action extends Spa_Action { } class Frontend_Contacts_Action extends Spa_Action { }
DETACHED ACTION LOADING
Spa.load_detached_action() loads an action without affecting the live SPA state.
The action is instantiated on a detached DOM element, runs its full lifecycle
(including on_load), and returns the component instance for inspection.
Use cases:
- Extracting action metadata (titles, breadcrumbs) for navigation UI
- Pre-fetching action data before navigation
- Inspecting action state without rendering it visibly
Basic Usage:
const action = await Spa.load_detached_action('/contacts/123');
if (action) {
const title = action.get_title?.() ?? action.constructor.name;
const breadcrumbs = action.get_breadcrumbs?.();
console.log('Page title:', title);
// IMPORTANT: Clean up when done to prevent memory leaks
action.stop();
}
With Cached Data:
// Skip network request if cached data available
const action = await Spa.load_detached_action('/contacts/123', {
use_cached_data: true
});
Extra Arguments:
// Pass additional args merged with URL-extracted params
const action = await Spa.load_detached_action('/contacts/123', {
some_option: true,
use_cached_data: true
});
What It Does NOT Affect:
- Spa.action (current live action remains unchanged)
- Spa.layout (current live layout remains unchanged)
- Spa.route / Spa.params (current route state unchanged)
- Browser history
- The visible DOM
Returns:
- Fully-initialized Spa_Action instance if route matches
- null if no route matches the URL
IMPORTANT: The caller MUST call action.stop() when done with the detached
action to clean up event listeners and prevent memory leaks.
SEE ALSO SEE ALSO
controller(3) - Controller patterns and Ajax endpoints controller(3) - Controller patterns and Ajax endpoints
jqhtml(3) - Component lifecycle and templates jqhtml(3) - Component lifecycle and templates

18
node_modules/.package-lock.json generated vendored
View File

@@ -2211,9 +2211,9 @@
} }
}, },
"node_modules/@jqhtml/core": { "node_modules/@jqhtml/core": {
"version": "2.3.14", "version": "2.3.15",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.14.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.15.tgz",
"integrity": "sha512-hJkCDrFhE1RnCCu0dG2wl+DqOzOZ92TRz93VlVjkgX+wu6muM0knbM5lsLnK9LD6n6nT13u5pvQEl1DVQVQRLg==", "integrity": "sha512-Up20LBvvrMro9R6vVM8qUJIZDjzpUdkBbh+etOnJaCMKQ87figdVr9OuqWT4ua2Rd9wM6fp/VLKX0/+wHQ0pEQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",
@@ -2237,9 +2237,9 @@
} }
}, },
"node_modules/@jqhtml/parser": { "node_modules/@jqhtml/parser": {
"version": "2.3.14", "version": "2.3.15",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.14.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.15.tgz",
"integrity": "sha512-WMpYG1pagvopbLg2dUAc94C62oiyQ2rPhAl4lPcCxL6VDOFeeCBzjdqx/40oie551y/yiCcrd2nr3YeXDh2bnw==", "integrity": "sha512-4Uyhp+5gtg/a73aucCgNCzop7AqS1OsCvuYrWs1ia+lLopyFeJNUUNVX3zcbUARq/49HdZ7Ima8/qdKOjTllAw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
@@ -2277,9 +2277,9 @@
} }
}, },
"node_modules/@jqhtml/vscode-extension": { "node_modules/@jqhtml/vscode-extension": {
"version": "2.3.14", "version": "2.3.15",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.14.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.15.tgz",
"integrity": "sha512-I0Z83JBB3b2RQYs/KQ8FNTpuvsvgH15MvByMvEsffGRCnEr3ehX4BCxWizjaIrpqPzakQjIZQSe1KW9mNbmGfw==", "integrity": "sha512-YmqKUcevVcHOchQHP5OGcmEaY9wlTdwjnrDXeiWLluMzikkgPllVMgWA7tw0hOWAPSWp/eGMGVr1k549G4ErBA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"vscode": "^1.74.0" "vscode": "^1.74.0"

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

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

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

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

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

@@ -51,6 +51,8 @@ export declare class Jqhtml_Component {
private _is_dynamic; private _is_dynamic;
private _on_render_complete; private _on_render_complete;
private _use_cached_data_hit; private _use_cached_data_hit;
private _skip_render_and_ready;
private _skip_ready;
constructor(element?: any, args?: Record<string, any>); constructor(element?: any, args?: Record<string, any>);
/** /**
* Protect lifecycle methods from manual invocation * Protect lifecycle methods from manual invocation

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

@@ -1 +1 @@
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"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"} {"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 generated vendored Normal file → Executable file
View File

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

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

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

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

@@ -32,6 +32,12 @@ class LifecycleManager {
/** /**
* Boot a component - run its full lifecycle * Boot a component - run its full lifecycle
* Called when component is created * Called when component is created
*
* Supports lifecycle skip flags:
* - skip_render_and_ready: Skip first render/on_render and skip on_ready entirely.
* Component reports ready after on_load completes.
* - skip_ready: Skip on_ready only.
* Component reports ready after on_render completes (after potential re-render from on_load).
*/ */
async boot_component(component) { async boot_component(component) {
this.active_components.add(component); this.active_components.add(component);
@@ -43,18 +49,39 @@ class LifecycleManager {
return; return;
// Trigger create event // Trigger create event
component.trigger('create'); component.trigger('create');
// Render phase - creates DOM and child components // Check for skip_render_and_ready flag
// Note: _render() now calls on_render() internally after DOM update const skip_render_and_ready = component._skip_render_and_ready;
// Capture render ID to detect if another render happens before ready const skip_ready = component._skip_ready;
let render_id = component._render(); let render_id;
// Check if stopped during render if (skip_render_and_ready) {
if (component._stopped) // Skip the first render/on_render before on_load
return; // Still need to set render_id to 0 for tracking
render_id = 0;
component._render_count = 0;
}
else {
// Render phase - creates DOM and child components
// Note: _render() now calls on_render() internally after DOM update
// Capture render ID to detect if another render happens before ready
render_id = component._render();
// Check if stopped during render
if (component._stopped)
return;
}
// Load phase - may modify this.data // Load phase - may modify this.data
await component._load(); await component._load();
// Check if stopped during load // Check if stopped during load
if (component._stopped) if (component._stopped)
return; return;
// If skip_render_and_ready, mark component as ready now and return
if (skip_render_and_ready) {
// Set ready state and trigger event
component._ready_state = 4;
component._update_debug_attrs();
component._log_lifecycle('ready', 'complete (skip_render_and_ready)');
component.trigger('ready');
return;
}
// If data changed during load, re-render // If data changed during load, re-render
// Note: _render() now calls on_render() internally after DOM update // Note: _render() now calls on_render() internally after DOM update
if (component._should_rerender()) { if (component._should_rerender()) {
@@ -68,6 +95,15 @@ class LifecycleManager {
if (component._render_count !== render_id) { if (component._render_count !== render_id) {
return; // Stale render, don't call ready return; // Stale render, don't call ready
} }
// If skip_ready, mark component as ready now without calling _ready()
if (skip_ready) {
// Set ready state and trigger event
component._ready_state = 4;
component._update_debug_attrs();
component._log_lifecycle('ready', 'complete (skip_ready)');
component.trigger('ready');
return;
}
// Ready phase - waits for children, then calls on_ready() // Ready phase - waits for children, then calls on_ready()
await component._ready(); await component._ready();
// Check if stopped during ready // Check if stopped during ready
@@ -568,12 +604,26 @@ function process_tag_to_html(instruction, html, tagElements, components, context
*/ */
function process_component_to_html(instruction, html, components, context) { function process_component_to_html(instruction, html, components, context) {
const [componentName, originalProps, contentOrSlots] = instruction.comp; const [componentName, originalProps, contentOrSlots] = instruction.comp;
// Propagate use_cached_data from parent to child if parent has it set // Propagate lifecycle flags from parent to child if parent has them set
// This allows a parent component with use_cached_data=true to automatically // This allows a parent component with these flags to automatically
// pass this behavior to all child components rendered in its template // pass this behavior to all child components rendered in its template
let props = originalProps; let props = originalProps;
if (context.args?.use_cached_data === true && props.use_cached_data === undefined) { const parentArgs = context.args;
props = { ...originalProps, use_cached_data: true }; // Check if we need to propagate any flags
const propagate_use_cached_data = parentArgs?.use_cached_data === true && props.use_cached_data === undefined;
const propagate_skip_render_and_ready = parentArgs?.skip_render_and_ready === true && props.skip_render_and_ready === undefined;
const propagate_skip_ready = parentArgs?.skip_ready === true && props.skip_ready === undefined;
if (propagate_use_cached_data || propagate_skip_render_and_ready || propagate_skip_ready) {
props = { ...originalProps };
if (propagate_use_cached_data) {
props.use_cached_data = true;
}
if (propagate_skip_render_and_ready) {
props.skip_render_and_ready = true;
}
if (propagate_skip_ready) {
props.skip_ready = true;
}
} }
// Determine if third parameter is a function (default content) or object (named slots) // Determine if third parameter is a function (default content) or object (named slots)
let contentFn; let contentFn;
@@ -1144,6 +1194,12 @@ class Jqhtml_Component {
this._on_render_complete = false; // True after on_render() has been called post-on_load this._on_render_complete = false; // True after on_render() has been called post-on_load
// use_cached_data feature - skip on_load() when cache hit occurs // use_cached_data feature - skip on_load() when cache hit occurs
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
// skip_render_and_ready feature - skip first render/on_render and on_ready entirely
// Component reports ready after on_load completes
this._skip_render_and_ready = false;
// skip_ready feature - skip on_ready only
// Component reports ready after on_render completes
this._skip_ready = false;
this._cid = this._generate_cid(); this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance(); this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element // Create or wrap element
@@ -1181,6 +1237,13 @@ class Jqhtml_Component {
// Merge in order: defineArgs (defaults from Define tag) < dataAttrs < args (invocation overrides) // Merge in order: defineArgs (defaults from Define tag) < dataAttrs < args (invocation overrides)
const defineArgs = template_for_args?.defineArgs || {}; const defineArgs = template_for_args?.defineArgs || {};
this.args = { ...defineArgs, ...dataAttrs, ...args }; this.args = { ...defineArgs, ...dataAttrs, ...args };
// Set lifecycle skip flags from args
if (this.args.skip_render_and_ready === true) {
this._skip_render_and_ready = true;
}
if (this.args.skip_ready === true) {
this._skip_ready = true;
}
// Attach component to element // Attach component to element
this.$.data('_component', this); this.$.data('_component', this);
// Apply CSS classes and attributes // Apply CSS classes and attributes
@@ -4485,7 +4548,7 @@ class Load_Coordinator {
continue; // Skip internal properties continue; // Skip internal properties
} }
// Skip framework properties that shouldn't affect cache identity // Skip framework properties that shouldn't affect cache identity
if (key === 'use_cached_data') { if (key === 'use_cached_data' || key === 'skip_render_and_ready' || key === 'skip_ready') {
continue; continue;
} }
const value = args[key]; const value = args[key];
@@ -4704,7 +4767,7 @@ function init(jQuery) {
} }
} }
// Version - will be replaced during build with actual version from package.json // Version - will be replaced during build with actual version from package.json
const version = '2.3.14'; const version = '2.3.15';
// Default export with all functionality // Default export with all functionality
const jqhtml = { const jqhtml = {
// Core // Core

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

File diff suppressed because one or more lines are too long

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

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

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

@@ -28,6 +28,12 @@ class LifecycleManager {
/** /**
* Boot a component - run its full lifecycle * Boot a component - run its full lifecycle
* Called when component is created * Called when component is created
*
* Supports lifecycle skip flags:
* - skip_render_and_ready: Skip first render/on_render and skip on_ready entirely.
* Component reports ready after on_load completes.
* - skip_ready: Skip on_ready only.
* Component reports ready after on_render completes (after potential re-render from on_load).
*/ */
async boot_component(component) { async boot_component(component) {
this.active_components.add(component); this.active_components.add(component);
@@ -39,18 +45,39 @@ class LifecycleManager {
return; return;
// Trigger create event // Trigger create event
component.trigger('create'); component.trigger('create');
// Render phase - creates DOM and child components // Check for skip_render_and_ready flag
// Note: _render() now calls on_render() internally after DOM update const skip_render_and_ready = component._skip_render_and_ready;
// Capture render ID to detect if another render happens before ready const skip_ready = component._skip_ready;
let render_id = component._render(); let render_id;
// Check if stopped during render if (skip_render_and_ready) {
if (component._stopped) // Skip the first render/on_render before on_load
return; // Still need to set render_id to 0 for tracking
render_id = 0;
component._render_count = 0;
}
else {
// Render phase - creates DOM and child components
// Note: _render() now calls on_render() internally after DOM update
// Capture render ID to detect if another render happens before ready
render_id = component._render();
// Check if stopped during render
if (component._stopped)
return;
}
// Load phase - may modify this.data // Load phase - may modify this.data
await component._load(); await component._load();
// Check if stopped during load // Check if stopped during load
if (component._stopped) if (component._stopped)
return; return;
// If skip_render_and_ready, mark component as ready now and return
if (skip_render_and_ready) {
// Set ready state and trigger event
component._ready_state = 4;
component._update_debug_attrs();
component._log_lifecycle('ready', 'complete (skip_render_and_ready)');
component.trigger('ready');
return;
}
// If data changed during load, re-render // If data changed during load, re-render
// Note: _render() now calls on_render() internally after DOM update // Note: _render() now calls on_render() internally after DOM update
if (component._should_rerender()) { if (component._should_rerender()) {
@@ -64,6 +91,15 @@ class LifecycleManager {
if (component._render_count !== render_id) { if (component._render_count !== render_id) {
return; // Stale render, don't call ready return; // Stale render, don't call ready
} }
// If skip_ready, mark component as ready now without calling _ready()
if (skip_ready) {
// Set ready state and trigger event
component._ready_state = 4;
component._update_debug_attrs();
component._log_lifecycle('ready', 'complete (skip_ready)');
component.trigger('ready');
return;
}
// Ready phase - waits for children, then calls on_ready() // Ready phase - waits for children, then calls on_ready()
await component._ready(); await component._ready();
// Check if stopped during ready // Check if stopped during ready
@@ -564,12 +600,26 @@ function process_tag_to_html(instruction, html, tagElements, components, context
*/ */
function process_component_to_html(instruction, html, components, context) { function process_component_to_html(instruction, html, components, context) {
const [componentName, originalProps, contentOrSlots] = instruction.comp; const [componentName, originalProps, contentOrSlots] = instruction.comp;
// Propagate use_cached_data from parent to child if parent has it set // Propagate lifecycle flags from parent to child if parent has them set
// This allows a parent component with use_cached_data=true to automatically // This allows a parent component with these flags to automatically
// pass this behavior to all child components rendered in its template // pass this behavior to all child components rendered in its template
let props = originalProps; let props = originalProps;
if (context.args?.use_cached_data === true && props.use_cached_data === undefined) { const parentArgs = context.args;
props = { ...originalProps, use_cached_data: true }; // Check if we need to propagate any flags
const propagate_use_cached_data = parentArgs?.use_cached_data === true && props.use_cached_data === undefined;
const propagate_skip_render_and_ready = parentArgs?.skip_render_and_ready === true && props.skip_render_and_ready === undefined;
const propagate_skip_ready = parentArgs?.skip_ready === true && props.skip_ready === undefined;
if (propagate_use_cached_data || propagate_skip_render_and_ready || propagate_skip_ready) {
props = { ...originalProps };
if (propagate_use_cached_data) {
props.use_cached_data = true;
}
if (propagate_skip_render_and_ready) {
props.skip_render_and_ready = true;
}
if (propagate_skip_ready) {
props.skip_ready = true;
}
} }
// Determine if third parameter is a function (default content) or object (named slots) // Determine if third parameter is a function (default content) or object (named slots)
let contentFn; let contentFn;
@@ -1140,6 +1190,12 @@ class Jqhtml_Component {
this._on_render_complete = false; // True after on_render() has been called post-on_load this._on_render_complete = false; // True after on_render() has been called post-on_load
// use_cached_data feature - skip on_load() when cache hit occurs // use_cached_data feature - skip on_load() when cache hit occurs
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
// skip_render_and_ready feature - skip first render/on_render and on_ready entirely
// Component reports ready after on_load completes
this._skip_render_and_ready = false;
// skip_ready feature - skip on_ready only
// Component reports ready after on_render completes
this._skip_ready = false;
this._cid = this._generate_cid(); this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance(); this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element // Create or wrap element
@@ -1177,6 +1233,13 @@ class Jqhtml_Component {
// Merge in order: defineArgs (defaults from Define tag) < dataAttrs < args (invocation overrides) // Merge in order: defineArgs (defaults from Define tag) < dataAttrs < args (invocation overrides)
const defineArgs = template_for_args?.defineArgs || {}; const defineArgs = template_for_args?.defineArgs || {};
this.args = { ...defineArgs, ...dataAttrs, ...args }; this.args = { ...defineArgs, ...dataAttrs, ...args };
// Set lifecycle skip flags from args
if (this.args.skip_render_and_ready === true) {
this._skip_render_and_ready = true;
}
if (this.args.skip_ready === true) {
this._skip_ready = true;
}
// Attach component to element // Attach component to element
this.$.data('_component', this); this.$.data('_component', this);
// Apply CSS classes and attributes // Apply CSS classes and attributes
@@ -4481,7 +4544,7 @@ class Load_Coordinator {
continue; // Skip internal properties continue; // Skip internal properties
} }
// Skip framework properties that shouldn't affect cache identity // Skip framework properties that shouldn't affect cache identity
if (key === 'use_cached_data') { if (key === 'use_cached_data' || key === 'skip_render_and_ready' || key === 'skip_ready') {
continue; continue;
} }
const value = args[key]; const value = args[key];
@@ -4700,7 +4763,7 @@ function init(jQuery) {
} }
} }
// Version - will be replaced during build with actual version from package.json // Version - will be replaced during build with actual version from package.json
const version = '2.3.14'; const version = '2.3.15';
// Default export with all functionality // Default export with all functionality
const jqhtml = { const jqhtml = {
// Core // Core

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

File diff suppressed because one or more lines are too long

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

2
node_modules/@jqhtml/core/dist/instruction-processor.d.ts.map generated vendored Normal file → Executable 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,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAClL;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;AAqB/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;AAsdD;;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,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC;CAClL;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;AAqB/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;AAseD;;GAEG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAW1F"}

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

@@ -1,5 +1,5 @@
/** /**
* JQHTML Core v2.3.14 * JQHTML Core v2.3.15
* (c) 2025 JQHTML Team * (c) 2025 JQHTML Team
* Released under the MIT License * Released under the MIT License
*/ */
@@ -33,6 +33,12 @@ class LifecycleManager {
/** /**
* Boot a component - run its full lifecycle * Boot a component - run its full lifecycle
* Called when component is created * Called when component is created
*
* Supports lifecycle skip flags:
* - skip_render_and_ready: Skip first render/on_render and skip on_ready entirely.
* Component reports ready after on_load completes.
* - skip_ready: Skip on_ready only.
* Component reports ready after on_render completes (after potential re-render from on_load).
*/ */
async boot_component(component) { async boot_component(component) {
this.active_components.add(component); this.active_components.add(component);
@@ -44,18 +50,39 @@ class LifecycleManager {
return; return;
// Trigger create event // Trigger create event
component.trigger('create'); component.trigger('create');
// Render phase - creates DOM and child components // Check for skip_render_and_ready flag
// Note: _render() now calls on_render() internally after DOM update const skip_render_and_ready = component._skip_render_and_ready;
// Capture render ID to detect if another render happens before ready const skip_ready = component._skip_ready;
let render_id = component._render(); let render_id;
// Check if stopped during render if (skip_render_and_ready) {
if (component._stopped) // Skip the first render/on_render before on_load
return; // Still need to set render_id to 0 for tracking
render_id = 0;
component._render_count = 0;
}
else {
// Render phase - creates DOM and child components
// Note: _render() now calls on_render() internally after DOM update
// Capture render ID to detect if another render happens before ready
render_id = component._render();
// Check if stopped during render
if (component._stopped)
return;
}
// Load phase - may modify this.data // Load phase - may modify this.data
await component._load(); await component._load();
// Check if stopped during load // Check if stopped during load
if (component._stopped) if (component._stopped)
return; return;
// If skip_render_and_ready, mark component as ready now and return
if (skip_render_and_ready) {
// Set ready state and trigger event
component._ready_state = 4;
component._update_debug_attrs();
component._log_lifecycle('ready', 'complete (skip_render_and_ready)');
component.trigger('ready');
return;
}
// If data changed during load, re-render // If data changed during load, re-render
// Note: _render() now calls on_render() internally after DOM update // Note: _render() now calls on_render() internally after DOM update
if (component._should_rerender()) { if (component._should_rerender()) {
@@ -69,6 +96,15 @@ class LifecycleManager {
if (component._render_count !== render_id) { if (component._render_count !== render_id) {
return; // Stale render, don't call ready return; // Stale render, don't call ready
} }
// If skip_ready, mark component as ready now without calling _ready()
if (skip_ready) {
// Set ready state and trigger event
component._ready_state = 4;
component._update_debug_attrs();
component._log_lifecycle('ready', 'complete (skip_ready)');
component.trigger('ready');
return;
}
// Ready phase - waits for children, then calls on_ready() // Ready phase - waits for children, then calls on_ready()
await component._ready(); await component._ready();
// Check if stopped during ready // Check if stopped during ready
@@ -569,12 +605,26 @@ function process_tag_to_html(instruction, html, tagElements, components, context
*/ */
function process_component_to_html(instruction, html, components, context) { function process_component_to_html(instruction, html, components, context) {
const [componentName, originalProps, contentOrSlots] = instruction.comp; const [componentName, originalProps, contentOrSlots] = instruction.comp;
// Propagate use_cached_data from parent to child if parent has it set // Propagate lifecycle flags from parent to child if parent has them set
// This allows a parent component with use_cached_data=true to automatically // This allows a parent component with these flags to automatically
// pass this behavior to all child components rendered in its template // pass this behavior to all child components rendered in its template
let props = originalProps; let props = originalProps;
if (context.args?.use_cached_data === true && props.use_cached_data === undefined) { const parentArgs = context.args;
props = { ...originalProps, use_cached_data: true }; // Check if we need to propagate any flags
const propagate_use_cached_data = parentArgs?.use_cached_data === true && props.use_cached_data === undefined;
const propagate_skip_render_and_ready = parentArgs?.skip_render_and_ready === true && props.skip_render_and_ready === undefined;
const propagate_skip_ready = parentArgs?.skip_ready === true && props.skip_ready === undefined;
if (propagate_use_cached_data || propagate_skip_render_and_ready || propagate_skip_ready) {
props = { ...originalProps };
if (propagate_use_cached_data) {
props.use_cached_data = true;
}
if (propagate_skip_render_and_ready) {
props.skip_render_and_ready = true;
}
if (propagate_skip_ready) {
props.skip_ready = true;
}
} }
// Determine if third parameter is a function (default content) or object (named slots) // Determine if third parameter is a function (default content) or object (named slots)
let contentFn; let contentFn;
@@ -1145,6 +1195,12 @@ class Jqhtml_Component {
this._on_render_complete = false; // True after on_render() has been called post-on_load this._on_render_complete = false; // True after on_render() has been called post-on_load
// use_cached_data feature - skip on_load() when cache hit occurs // use_cached_data feature - skip on_load() when cache hit occurs
this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used this._use_cached_data_hit = false; // True if use_cached_data=true AND cache was used
// skip_render_and_ready feature - skip first render/on_render and on_ready entirely
// Component reports ready after on_load completes
this._skip_render_and_ready = false;
// skip_ready feature - skip on_ready only
// Component reports ready after on_render completes
this._skip_ready = false;
this._cid = this._generate_cid(); this._cid = this._generate_cid();
this._lifecycle_manager = LifecycleManager.get_instance(); this._lifecycle_manager = LifecycleManager.get_instance();
// Create or wrap element // Create or wrap element
@@ -1182,6 +1238,13 @@ class Jqhtml_Component {
// Merge in order: defineArgs (defaults from Define tag) < dataAttrs < args (invocation overrides) // Merge in order: defineArgs (defaults from Define tag) < dataAttrs < args (invocation overrides)
const defineArgs = template_for_args?.defineArgs || {}; const defineArgs = template_for_args?.defineArgs || {};
this.args = { ...defineArgs, ...dataAttrs, ...args }; this.args = { ...defineArgs, ...dataAttrs, ...args };
// Set lifecycle skip flags from args
if (this.args.skip_render_and_ready === true) {
this._skip_render_and_ready = true;
}
if (this.args.skip_ready === true) {
this._skip_ready = true;
}
// Attach component to element // Attach component to element
this.$.data('_component', this); this.$.data('_component', this);
// Apply CSS classes and attributes // Apply CSS classes and attributes
@@ -4486,7 +4549,7 @@ class Load_Coordinator {
continue; // Skip internal properties continue; // Skip internal properties
} }
// Skip framework properties that shouldn't affect cache identity // Skip framework properties that shouldn't affect cache identity
if (key === 'use_cached_data') { if (key === 'use_cached_data' || key === 'skip_render_and_ready' || key === 'skip_ready') {
continue; continue;
} }
const value = args[key]; const value = args[key];
@@ -4705,7 +4768,7 @@ function init(jQuery) {
} }
} }
// Version - will be replaced during build with actual version from package.json // Version - will be replaced during build with actual version from package.json
const version = '2.3.14'; const version = '2.3.15';
// Default export with all functionality // Default export with all functionality
const jqhtml = { const jqhtml = {
// Core // Core

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

File diff suppressed because one or more lines are too long

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

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

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

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

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

@@ -20,6 +20,12 @@ export declare class LifecycleManager {
/** /**
* Boot a component - run its full lifecycle * Boot a component - run its full lifecycle
* Called when component is created * Called when component is created
*
* Supports lifecycle skip flags:
* - skip_render_and_ready: Skip first render/on_render and skip on_ready entirely.
* Component reports ready after on_load completes.
* - skip_ready: Skip on_ready only.
* Component reports ready after on_render completes (after potential re-render from on_load).
*/ */
boot_component(component: Jqhtml_Component): Promise<void>; boot_component(component: Jqhtml_Component): Promise<void>;
/** /**

2
node_modules/@jqhtml/core/dist/lifecycle-manager.d.ts.map generated vendored Normal file → Executable 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;IAE7D,MAAM,CAAC,YAAY,IAAI,gBAAgB;;IAevC;;;OAGG;IACG,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAsDhE;;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;;;;;;;;;OASG;IACG,cAAc,CAAC,SAAS,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuFhE;;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/load-coordinator.d.ts generated vendored Normal file → Executable file
View File

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

0
node_modules/@jqhtml/core/dist/local-storage.d.ts generated vendored Normal file → Executable file
View File

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

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

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

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

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

0
node_modules/@jqhtml/parser/dist/ast.d.ts generated vendored Normal file → Executable file
View File

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

0
node_modules/@jqhtml/parser/dist/ast.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/ast.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/codegen.d.ts generated vendored Normal file → Executable file
View File

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

2
node_modules/@jqhtml/parser/dist/codegen.js generated vendored Normal file → Executable file
View File

@@ -1377,7 +1377,7 @@ export class CodeGenerator {
for (const [name, component] of this.components) { for (const [name, component] of this.components) {
code += `// Component: ${name}\n`; code += `// Component: ${name}\n`;
code += `jqhtml_components.set('${name}', {\n`; code += `jqhtml_components.set('${name}', {\n`;
code += ` _jqhtml_version: '2.3.14',\n`; // Version will be replaced during build code += ` _jqhtml_version: '2.3.15',\n`; // Version will be replaced during build
code += ` name: '${name}',\n`; code += ` name: '${name}',\n`;
code += ` tag: '${component.tagName}',\n`; code += ` tag: '${component.tagName}',\n`;
code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`; code += ` defaultAttributes: ${this.serializeAttributeObject(component.defaultAttributes)},\n`;

0
node_modules/@jqhtml/parser/dist/codegen.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/compiler.d.ts generated vendored Normal file → Executable file
View File

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

0
node_modules/@jqhtml/parser/dist/compiler.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/compiler.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/errors.d.ts generated vendored Normal file → Executable file
View File

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

0
node_modules/@jqhtml/parser/dist/errors.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/errors.js.map generated vendored Normal file → Executable file
View File

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

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

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

0
node_modules/@jqhtml/parser/dist/index.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/integration.d.ts generated vendored Normal file → Executable file
View File

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

0
node_modules/@jqhtml/parser/dist/integration.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/integration.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/lexer.d.ts generated vendored Normal file → Executable file
View File

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

0
node_modules/@jqhtml/parser/dist/lexer.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/lexer.js.map generated vendored Normal file → Executable file
View File

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

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

0
node_modules/@jqhtml/parser/dist/parser.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/parser.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/runtime.d.ts generated vendored Normal file → Executable file
View File

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

0
node_modules/@jqhtml/parser/dist/runtime.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/parser/dist/runtime.js.map generated vendored Normal file → Executable file
View File

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

@@ -1,6 +1,6 @@
{ {
"name": "@jqhtml/parser", "name": "@jqhtml/parser",
"version": "2.3.14", "version": "2.3.15",
"description": "JQHTML template parser - converts templates to JavaScript", "description": "JQHTML template parser - converts templates to JavaScript",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",

View File

@@ -1 +1 @@
2.3.14 2.3.15

0
node_modules/@jqhtml/vscode-extension/blade-language-configuration.json generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_component_provider.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_component_provider.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_language_config.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_language_config.js.map generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_spacer.js generated vendored Normal file → Executable file
View File

0
node_modules/@jqhtml/vscode-extension/out/blade_spacer.js.map generated vendored Normal file → Executable file
View File

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

@@ -2,7 +2,7 @@
"name": "@jqhtml/vscode-extension", "name": "@jqhtml/vscode-extension",
"displayName": "JQHTML", "displayName": "JQHTML",
"description": "Syntax highlighting and language support for JQHTML template files", "description": "Syntax highlighting and language support for JQHTML template files",
"version": "2.3.14", "version": "2.3.15",
"publisher": "jqhtml", "publisher": "jqhtml",
"license": "MIT", "license": "MIT",
"publishConfig": { "publishConfig": {

0
node_modules/@jqhtml/vscode-extension/syntaxes/blade-jqhtml.tmLanguage.json generated vendored Normal file → Executable file
View File

18
package-lock.json generated
View File

@@ -2658,9 +2658,9 @@
} }
}, },
"node_modules/@jqhtml/core": { "node_modules/@jqhtml/core": {
"version": "2.3.14", "version": "2.3.15",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.14.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/core/-/core-2.3.15.tgz",
"integrity": "sha512-hJkCDrFhE1RnCCu0dG2wl+DqOzOZ92TRz93VlVjkgX+wu6muM0knbM5lsLnK9LD6n6nT13u5pvQEl1DVQVQRLg==", "integrity": "sha512-Up20LBvvrMro9R6vVM8qUJIZDjzpUdkBbh+etOnJaCMKQ87figdVr9OuqWT4ua2Rd9wM6fp/VLKX0/+wHQ0pEQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",
@@ -2684,9 +2684,9 @@
} }
}, },
"node_modules/@jqhtml/parser": { "node_modules/@jqhtml/parser": {
"version": "2.3.14", "version": "2.3.15",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.14.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/parser/-/parser-2.3.15.tgz",
"integrity": "sha512-WMpYG1pagvopbLg2dUAc94C62oiyQ2rPhAl4lPcCxL6VDOFeeCBzjdqx/40oie551y/yiCcrd2nr3YeXDh2bnw==", "integrity": "sha512-4Uyhp+5gtg/a73aucCgNCzop7AqS1OsCvuYrWs1ia+lLopyFeJNUUNVX3zcbUARq/49HdZ7Ima8/qdKOjTllAw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
@@ -2724,9 +2724,9 @@
} }
}, },
"node_modules/@jqhtml/vscode-extension": { "node_modules/@jqhtml/vscode-extension": {
"version": "2.3.14", "version": "2.3.15",
"resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.14.tgz", "resolved": "http://privatenpm.hanson.xyz/@jqhtml/vscode-extension/-/vscode-extension-2.3.15.tgz",
"integrity": "sha512-I0Z83JBB3b2RQYs/KQ8FNTpuvsvgH15MvByMvEsffGRCnEr3ehX4BCxWizjaIrpqPzakQjIZQSe1KW9mNbmGfw==", "integrity": "sha512-YmqKUcevVcHOchQHP5OGcmEaY9wlTdwjnrDXeiWLluMzikkgPllVMgWA7tw0hOWAPSWp/eGMGVr1k549G4ErBA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"vscode": "^1.74.0" "vscode": "^1.74.0"