@jqhtml/core 2.3.2 → 2.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boot.d.ts +20 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/component.d.ts +20 -0
- package/dist/component.d.ts.map +1 -1
- package/dist/debug-entry.d.ts +3 -31
- package/dist/debug-entry.d.ts.map +1 -1
- package/dist/index.cjs +216 -406
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +216 -404
- package/dist/index.js.map +1 -1
- package/dist/jqhtml-core.esm.js +217 -405
- package/dist/jqhtml-core.esm.js.map +1 -1
- package/dist/jqhtml-debug.esm.js +1 -383
- package/dist/jqhtml-debug.esm.js.map +1 -1
- package/package.json +1 -1
- package/dist/debug-overlay.d.ts +0 -61
- package/dist/debug-overlay.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -879,7 +879,7 @@ function devWarn(message) {
|
|
|
879
879
|
console.warn(`[JQHTML Dev Warning] ${message}`);
|
|
880
880
|
}
|
|
881
881
|
// Get global jqhtml object
|
|
882
|
-
function getJqhtml
|
|
882
|
+
function getJqhtml() {
|
|
883
883
|
if (typeof window !== 'undefined' && window.jqhtml) {
|
|
884
884
|
return window.jqhtml;
|
|
885
885
|
}
|
|
@@ -892,7 +892,7 @@ function getJqhtml$1() {
|
|
|
892
892
|
}
|
|
893
893
|
// Visual flash effect
|
|
894
894
|
function flashComponent(component, eventType) {
|
|
895
|
-
const jqhtml = getJqhtml
|
|
895
|
+
const jqhtml = getJqhtml();
|
|
896
896
|
if (!jqhtml?.debug?.flashComponents)
|
|
897
897
|
return;
|
|
898
898
|
const duration = jqhtml.debug.flashDuration || 500;
|
|
@@ -914,7 +914,7 @@ function flashComponent(component, eventType) {
|
|
|
914
914
|
}
|
|
915
915
|
// Log lifecycle event
|
|
916
916
|
function logLifecycle(component, phase, status) {
|
|
917
|
-
const jqhtml = getJqhtml
|
|
917
|
+
const jqhtml = getJqhtml();
|
|
918
918
|
if (!jqhtml?.debug)
|
|
919
919
|
return;
|
|
920
920
|
const shouldLog = jqhtml.debug.logFullLifecycle ||
|
|
@@ -960,7 +960,7 @@ function logLifecycle(component, phase, status) {
|
|
|
960
960
|
}
|
|
961
961
|
// Apply delays based on lifecycle phase
|
|
962
962
|
function applyDebugDelay(phase) {
|
|
963
|
-
const jqhtml = getJqhtml
|
|
963
|
+
const jqhtml = getJqhtml();
|
|
964
964
|
if (!jqhtml?.debug)
|
|
965
965
|
return;
|
|
966
966
|
let delayMs = 0;
|
|
@@ -981,14 +981,14 @@ function applyDebugDelay(phase) {
|
|
|
981
981
|
}
|
|
982
982
|
// Log instruction processing
|
|
983
983
|
function logInstruction(type, data) {
|
|
984
|
-
const jqhtml = getJqhtml
|
|
984
|
+
const jqhtml = getJqhtml();
|
|
985
985
|
if (!jqhtml?.debug?.logInstructionProcessing)
|
|
986
986
|
return;
|
|
987
987
|
console.log(`[JQHTML Instruction] ${type}:`, data);
|
|
988
988
|
}
|
|
989
989
|
// Log data changes
|
|
990
990
|
function logDataChange(component, property, oldValue, newValue) {
|
|
991
|
-
const jqhtml = getJqhtml
|
|
991
|
+
const jqhtml = getJqhtml();
|
|
992
992
|
if (!jqhtml?.debug?.traceDataFlow)
|
|
993
993
|
return;
|
|
994
994
|
console.log(`[JQHTML Data] ${component.constructor.name}#${component._cid}.data.${property}:`, { old: oldValue, new: newValue });
|
|
@@ -1001,7 +1001,7 @@ function updateComponentTree() {
|
|
|
1001
1001
|
}
|
|
1002
1002
|
// Router dispatch logging
|
|
1003
1003
|
function logDispatch(url, route, params, verbose = false) {
|
|
1004
|
-
const jqhtml = getJqhtml
|
|
1004
|
+
const jqhtml = getJqhtml();
|
|
1005
1005
|
if (!jqhtml?.debug)
|
|
1006
1006
|
return;
|
|
1007
1007
|
const shouldLog = jqhtml.debug.logDispatch || jqhtml.debug.logDispatchVerbose;
|
|
@@ -1023,12 +1023,12 @@ function logDispatch(url, route, params, verbose = false) {
|
|
|
1023
1023
|
}
|
|
1024
1024
|
// Check if sequential processing is enabled
|
|
1025
1025
|
function isSequentialProcessing() {
|
|
1026
|
-
const jqhtml = getJqhtml
|
|
1026
|
+
const jqhtml = getJqhtml();
|
|
1027
1027
|
return jqhtml?.debug?.sequentialProcessing || false;
|
|
1028
1028
|
}
|
|
1029
1029
|
// Error handling with break on error
|
|
1030
1030
|
function handleComponentError(component, phase, error) {
|
|
1031
|
-
const jqhtml = getJqhtml
|
|
1031
|
+
const jqhtml = getJqhtml();
|
|
1032
1032
|
console.error(`[JQHTML Error] ${component.constructor.name}#${component._cid} failed in ${phase}:`, error);
|
|
1033
1033
|
if (jqhtml?.debug?.breakOnError) {
|
|
1034
1034
|
debugger; // This will pause execution in dev tools
|
|
@@ -1056,6 +1056,9 @@ function handleComponentError(component, phase, error) {
|
|
|
1056
1056
|
* - Scoped IDs using _cid pattern
|
|
1057
1057
|
* - Event emission and CSS class hierarchy
|
|
1058
1058
|
*/
|
|
1059
|
+
// WeakMap storage for protected lifecycle method implementations (Option 2)
|
|
1060
|
+
// See docs/internal/lifecycle-method-protection.md for design details
|
|
1061
|
+
const lifecycle_impls = new WeakMap();
|
|
1059
1062
|
class Jqhtml_Component {
|
|
1060
1063
|
constructor(element, args = {}) {
|
|
1061
1064
|
this._ready_state = 0; // 0=created, 1=init, 2=loaded, 3=rendered, 4=ready
|
|
@@ -1076,6 +1079,7 @@ class Jqhtml_Component {
|
|
|
1076
1079
|
this.__initial_data_snapshot = null; // Snapshot of this.data after on_create() for restoration before on_load()
|
|
1077
1080
|
this.__data_frozen = false; // Track if this.data is currently frozen
|
|
1078
1081
|
this.next_reload_force_refresh = null; // State machine for reload()/refresh() debounce precedence
|
|
1082
|
+
this.__lifecycle_authorized = false; // Flag for lifecycle method protection
|
|
1079
1083
|
this._cid = this._generate_cid();
|
|
1080
1084
|
this._lifecycle_manager = LifecycleManager.get_instance();
|
|
1081
1085
|
// Create or wrap element
|
|
@@ -1189,6 +1193,63 @@ class Jqhtml_Component {
|
|
|
1189
1193
|
this.state = {};
|
|
1190
1194
|
this._log_lifecycle('construct', 'complete');
|
|
1191
1195
|
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Protect lifecycle methods from manual invocation
|
|
1198
|
+
* Stores original implementations in WeakMap, replaces with guarded wrappers
|
|
1199
|
+
* @private
|
|
1200
|
+
*/
|
|
1201
|
+
_protect_lifecycle_methods() {
|
|
1202
|
+
const methods = {
|
|
1203
|
+
on_create: 'Called automatically during creation.',
|
|
1204
|
+
on_render: 'Use render() to trigger a re-render.',
|
|
1205
|
+
on_load: 'Use reload() to refresh data.',
|
|
1206
|
+
on_ready: 'Called automatically when ready.',
|
|
1207
|
+
on_stop: 'Use stop() to stop the component.'
|
|
1208
|
+
};
|
|
1209
|
+
const impls = {};
|
|
1210
|
+
const self = this;
|
|
1211
|
+
for (const [name, help] of Object.entries(methods)) {
|
|
1212
|
+
const original = this[name];
|
|
1213
|
+
// Skip if using base class default (empty method)
|
|
1214
|
+
if (original === Jqhtml_Component.prototype[name])
|
|
1215
|
+
continue;
|
|
1216
|
+
impls[name] = original;
|
|
1217
|
+
// Create wrapper with same function name (for stack traces)
|
|
1218
|
+
this[name] = {
|
|
1219
|
+
[name](...args) {
|
|
1220
|
+
if (!self.__lifecycle_authorized) {
|
|
1221
|
+
throw new Error(`[JQHTML] ${name}() cannot be called manually. ${help}\n` +
|
|
1222
|
+
`Component: ${self.component_name()} (_cid: ${self._cid})`);
|
|
1223
|
+
}
|
|
1224
|
+
return lifecycle_impls.get(self)[name].apply(self, args);
|
|
1225
|
+
}
|
|
1226
|
+
}[name];
|
|
1227
|
+
}
|
|
1228
|
+
lifecycle_impls.set(this, impls);
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Call a lifecycle method with authorization (async)
|
|
1232
|
+
* Framework calls this to invoke lifecycle methods, bypassing the protection wrapper.
|
|
1233
|
+
* The flag is set momentarily for the wrapper check, then reset BEFORE user code runs.
|
|
1234
|
+
* This ensures any nested lifecycle calls from user code will fail the flag check.
|
|
1235
|
+
* @private
|
|
1236
|
+
*/
|
|
1237
|
+
async _call_lifecycle(name, context) {
|
|
1238
|
+
// Get the original implementation (bypasses wrapper entirely)
|
|
1239
|
+
const impl = lifecycle_impls.get(this)?.[name] || this[name];
|
|
1240
|
+
// Call original directly - no flag needed since we bypass the wrapper
|
|
1241
|
+
return await impl.call(context || this);
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Call a lifecycle method with authorization (sync version for sync methods like on_stop)
|
|
1245
|
+
* @private
|
|
1246
|
+
*/
|
|
1247
|
+
_call_lifecycle_sync(name) {
|
|
1248
|
+
// Get the original implementation (bypasses wrapper entirely)
|
|
1249
|
+
const impl = lifecycle_impls.get(this)?.[name] || this[name];
|
|
1250
|
+
// Call original directly - no flag needed since we bypass the wrapper
|
|
1251
|
+
return impl.call(this);
|
|
1252
|
+
}
|
|
1192
1253
|
/**
|
|
1193
1254
|
* Boot - Start the full component lifecycle
|
|
1194
1255
|
* Called immediately after construction by instruction processor
|
|
@@ -1202,6 +1263,8 @@ class Jqhtml_Component {
|
|
|
1202
1263
|
if (this._booted)
|
|
1203
1264
|
return;
|
|
1204
1265
|
this._booted = true;
|
|
1266
|
+
// Protect lifecycle methods from manual invocation (must happen after subclass constructor)
|
|
1267
|
+
this._protect_lifecycle_methods();
|
|
1205
1268
|
await this._lifecycle_manager.boot_component(this);
|
|
1206
1269
|
}
|
|
1207
1270
|
// -------------------------------------------------------------------------
|
|
@@ -1412,9 +1475,9 @@ class Jqhtml_Component {
|
|
|
1412
1475
|
// Don't update ready state here - let phases complete in order
|
|
1413
1476
|
this._update_debug_attrs();
|
|
1414
1477
|
this._log_lifecycle('render', 'complete');
|
|
1415
|
-
// Call on_render() immediately after render completes
|
|
1478
|
+
// Call on_render() with authorization (sync) immediately after render completes
|
|
1416
1479
|
// This ensures event handlers are always re-attached after DOM updates
|
|
1417
|
-
const renderResult = this.on_render
|
|
1480
|
+
const renderResult = this._call_lifecycle_sync('on_render');
|
|
1418
1481
|
if (renderResult && typeof renderResult.then === 'function') {
|
|
1419
1482
|
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_render(). ` +
|
|
1420
1483
|
`on_render() must be synchronous code. Remove 'async' from the function declaration.`);
|
|
@@ -1479,8 +1542,8 @@ class Jqhtml_Component {
|
|
|
1479
1542
|
if (this._render_count !== render_id) {
|
|
1480
1543
|
return; // Stale render, don't call on_ready
|
|
1481
1544
|
}
|
|
1482
|
-
// Call on_ready hook
|
|
1483
|
-
await this.on_ready
|
|
1545
|
+
// Call on_ready hook with authorization
|
|
1546
|
+
await this._call_lifecycle('on_ready');
|
|
1484
1547
|
// Trigger ready event
|
|
1485
1548
|
await this.trigger('ready');
|
|
1486
1549
|
})();
|
|
@@ -1500,8 +1563,8 @@ class Jqhtml_Component {
|
|
|
1500
1563
|
if (this._stopped || this._ready_state >= 1)
|
|
1501
1564
|
return;
|
|
1502
1565
|
this._log_lifecycle('create', 'start');
|
|
1503
|
-
// Call on_create() and validate it's synchronous
|
|
1504
|
-
const result = this.on_create
|
|
1566
|
+
// Call on_create() with authorization and validate it's synchronous
|
|
1567
|
+
const result = await this._call_lifecycle('on_create');
|
|
1505
1568
|
if (result && typeof result.then === 'function') {
|
|
1506
1569
|
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_create(). ` +
|
|
1507
1570
|
`on_create() must be synchronous code. Remove 'async' from the function declaration.`);
|
|
@@ -1616,8 +1679,8 @@ class Jqhtml_Component {
|
|
|
1616
1679
|
if (window.jqhtml?.debug?.verbose) {
|
|
1617
1680
|
console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - load deduplication and caching disabled`, { uncacheable_property });
|
|
1618
1681
|
}
|
|
1619
|
-
// Execute on_load() without deduplication or caching
|
|
1620
|
-
await this.on_load
|
|
1682
|
+
// Execute on_load() with authorization, without deduplication or caching
|
|
1683
|
+
await this._call_lifecycle('on_load');
|
|
1621
1684
|
this.__data_frozen = true;
|
|
1622
1685
|
return;
|
|
1623
1686
|
}
|
|
@@ -1726,7 +1789,7 @@ class Jqhtml_Component {
|
|
|
1726
1789
|
// - Should errors reset state machine flags (next_reload_force_refresh)?
|
|
1727
1790
|
// - Should partial data be preserved or rolled back?
|
|
1728
1791
|
// - Should followers be notified differently based on error type?
|
|
1729
|
-
await this.on_load
|
|
1792
|
+
await this._call_lifecycle('on_load', restricted_this);
|
|
1730
1793
|
}
|
|
1731
1794
|
catch (error) {
|
|
1732
1795
|
// Handle error and notify coordinator
|
|
@@ -1798,7 +1861,7 @@ class Jqhtml_Component {
|
|
|
1798
1861
|
this._log_lifecycle('ready', 'start');
|
|
1799
1862
|
// Wait for all children to reach ready state (bottom-up execution)
|
|
1800
1863
|
await this._wait_for_children_ready();
|
|
1801
|
-
await this.on_ready
|
|
1864
|
+
await this._call_lifecycle('on_ready');
|
|
1802
1865
|
this._ready_state = 4;
|
|
1803
1866
|
this._update_debug_attrs();
|
|
1804
1867
|
this._log_lifecycle('ready', 'complete');
|
|
@@ -1848,6 +1911,14 @@ class Jqhtml_Component {
|
|
|
1848
1911
|
* @private
|
|
1849
1912
|
*/
|
|
1850
1913
|
async _wait_for_children_ready() {
|
|
1914
|
+
// Server-rendered components (created via jqhtml.boot()) may have children
|
|
1915
|
+
// that were hydrated asynchronously during the 'render' event callback.
|
|
1916
|
+
// Those children couldn't register via _find_dom_parent() because they were
|
|
1917
|
+
// created after the parent's lifecycle started. Force DOM traversal fallback
|
|
1918
|
+
// to reliably discover all children, including boot-hydrated ones.
|
|
1919
|
+
if (this.args._inner_html !== undefined) {
|
|
1920
|
+
this._use_dom_fallback = true;
|
|
1921
|
+
}
|
|
1851
1922
|
const children = this._get_dom_children();
|
|
1852
1923
|
if (children.length === 0) {
|
|
1853
1924
|
return; // No children, nothing to wait for
|
|
@@ -2006,7 +2077,7 @@ class Jqhtml_Component {
|
|
|
2006
2077
|
this.data = JSON.parse(JSON.stringify(this.__initial_data_snapshot));
|
|
2007
2078
|
}
|
|
2008
2079
|
try {
|
|
2009
|
-
await this.on_load
|
|
2080
|
+
await this._call_lifecycle('on_load');
|
|
2010
2081
|
}
|
|
2011
2082
|
finally {
|
|
2012
2083
|
// Freeze this.data after on_load() completes
|
|
@@ -2082,7 +2153,7 @@ class Jqhtml_Component {
|
|
|
2082
2153
|
// STEP 3.5 & 4: Wait for children and call on_ready (only if we rendered)
|
|
2083
2154
|
if (rendered_from_cache || should_render) {
|
|
2084
2155
|
await this._wait_for_children_ready();
|
|
2085
|
-
await this.on_ready
|
|
2156
|
+
await this._call_lifecycle('on_ready');
|
|
2086
2157
|
}
|
|
2087
2158
|
this._log_lifecycle('reload', 'complete');
|
|
2088
2159
|
}
|
|
@@ -2116,16 +2187,14 @@ class Jqhtml_Component {
|
|
|
2116
2187
|
this.$.addClass('_Component_Stopped');
|
|
2117
2188
|
// Unregister from lifecycle manager
|
|
2118
2189
|
this._lifecycle_manager.unregister_component(this);
|
|
2119
|
-
// Call user's on_stop() hook
|
|
2120
|
-
const stopResult = this.on_stop
|
|
2190
|
+
// Call user's on_stop() hook with authorization (sync)
|
|
2191
|
+
const stopResult = this._call_lifecycle_sync('on_stop');
|
|
2121
2192
|
if (stopResult && typeof stopResult.then === 'function') {
|
|
2122
2193
|
console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_stop(). ` +
|
|
2123
2194
|
`on_stop() must be synchronous code. Remove 'async' from the function declaration.`);
|
|
2124
2195
|
}
|
|
2125
|
-
// Fire registered
|
|
2126
|
-
this.trigger('
|
|
2127
|
-
// Trigger jQuery destroy event
|
|
2128
|
-
this.$.trigger('destroy');
|
|
2196
|
+
// Fire registered stop callbacks
|
|
2197
|
+
this.trigger('stop');
|
|
2129
2198
|
// Remove from DOM parent's children
|
|
2130
2199
|
if (this._dom_parent) {
|
|
2131
2200
|
this._dom_parent._dom_children.delete(this);
|
|
@@ -2954,384 +3023,132 @@ function escape_html(str) {
|
|
|
2954
3023
|
}
|
|
2955
3024
|
|
|
2956
3025
|
/**
|
|
2957
|
-
* JQHTML
|
|
3026
|
+
* JQHTML Boot - Component Hydration System
|
|
3027
|
+
*
|
|
3028
|
+
* Bridges server-rendered HTML and client-side jqhtml components.
|
|
2958
3029
|
*
|
|
2959
|
-
*
|
|
2960
|
-
*
|
|
3030
|
+
* Server output: <div class="_Component_Init" data-component-init-name="User_Card" data-component-args='{"id":1}'></div>
|
|
3031
|
+
* After boot(): <div class="User_Card Component">...rendered template...</div>
|
|
3032
|
+
*
|
|
3033
|
+
* Usage:
|
|
3034
|
+
* await jqhtml.boot(); // Hydrate all placeholders, wait for ready
|
|
3035
|
+
* jqhtml.boot($('#container')); // Hydrate within scope
|
|
2961
3036
|
*/
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
}
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
'Ensure @jqhtml/core is imported and initialized before attempting to use debug features.');
|
|
2983
|
-
}
|
|
2984
|
-
class DebugOverlay {
|
|
2985
|
-
constructor(options = {}) {
|
|
2986
|
-
this.$container = null;
|
|
2987
|
-
this.$statusIndicator = null;
|
|
2988
|
-
this.$ = getJQuery();
|
|
2989
|
-
if (!this.$) {
|
|
2990
|
-
throw new Error('jQuery is required for DebugOverlay');
|
|
2991
|
-
}
|
|
2992
|
-
this.options = {
|
|
2993
|
-
position: 'bottom',
|
|
2994
|
-
theme: 'dark',
|
|
2995
|
-
compact: false,
|
|
2996
|
-
showStatus: true,
|
|
2997
|
-
autoHide: false,
|
|
2998
|
-
...options
|
|
2999
|
-
};
|
|
3000
|
-
}
|
|
3001
|
-
/**
|
|
3002
|
-
* Static method to show debug overlay (singleton pattern)
|
|
3003
|
-
*/
|
|
3004
|
-
static show(options) {
|
|
3005
|
-
if (!DebugOverlay.instance) {
|
|
3006
|
-
DebugOverlay.instance = new DebugOverlay(options);
|
|
3007
|
-
}
|
|
3008
|
-
DebugOverlay.instance.display();
|
|
3009
|
-
return DebugOverlay.instance;
|
|
3010
|
-
}
|
|
3011
|
-
/**
|
|
3012
|
-
* Static method to hide debug overlay
|
|
3013
|
-
*/
|
|
3014
|
-
static hide() {
|
|
3015
|
-
if (DebugOverlay.instance) {
|
|
3016
|
-
DebugOverlay.instance.hide();
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3019
|
-
/**
|
|
3020
|
-
* Static method to toggle debug overlay visibility
|
|
3021
|
-
*/
|
|
3022
|
-
static toggle() {
|
|
3023
|
-
if (DebugOverlay.instance && DebugOverlay.instance.$container) {
|
|
3024
|
-
if (DebugOverlay.instance.$container.is(':visible')) {
|
|
3025
|
-
DebugOverlay.hide();
|
|
3026
|
-
}
|
|
3027
|
-
else {
|
|
3028
|
-
DebugOverlay.instance.display();
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
else {
|
|
3032
|
-
DebugOverlay.show();
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
/**
|
|
3036
|
-
* Static method to destroy debug overlay
|
|
3037
|
-
*/
|
|
3038
|
-
static destroy() {
|
|
3039
|
-
if (DebugOverlay.instance) {
|
|
3040
|
-
DebugOverlay.instance.destroy();
|
|
3041
|
-
DebugOverlay.instance = null;
|
|
3042
|
-
}
|
|
3043
|
-
}
|
|
3044
|
-
/**
|
|
3045
|
-
* Display the debug overlay
|
|
3046
|
-
*/
|
|
3047
|
-
display() {
|
|
3048
|
-
if (this.$container) {
|
|
3049
|
-
this.$container.show();
|
|
3037
|
+
/**
|
|
3038
|
+
* Hydrate all _Component_Init placeholders within a scope.
|
|
3039
|
+
*
|
|
3040
|
+
* @param scope - jQuery object, HTMLElement, or undefined (defaults to body)
|
|
3041
|
+
* @returns Promise that resolves when all components (including nested) are ready
|
|
3042
|
+
*/
|
|
3043
|
+
function boot(scope) {
|
|
3044
|
+
const jQ = typeof jQuery !== 'undefined' ? jQuery : $;
|
|
3045
|
+
if (!scope) {
|
|
3046
|
+
scope = jQ('body');
|
|
3047
|
+
}
|
|
3048
|
+
else if (!(scope instanceof jQ)) {
|
|
3049
|
+
scope = jQ(scope);
|
|
3050
|
+
}
|
|
3051
|
+
const readyPromises = [];
|
|
3052
|
+
// Find top-level placeholders only (skip those nested inside other placeholders)
|
|
3053
|
+
scope.find('._Component_Init').each(function () {
|
|
3054
|
+
const $element = jQ(this);
|
|
3055
|
+
// Skip if removed from DOM
|
|
3056
|
+
if (!document.contains($element[0])) {
|
|
3050
3057
|
return;
|
|
3051
3058
|
}
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
*/
|
|
3060
|
-
hide() {
|
|
3061
|
-
if (this.$container) {
|
|
3062
|
-
this.$container.hide();
|
|
3063
|
-
}
|
|
3064
|
-
if (this.$statusIndicator) {
|
|
3065
|
-
this.$statusIndicator.hide();
|
|
3066
|
-
}
|
|
3067
|
-
}
|
|
3068
|
-
/**
|
|
3069
|
-
* Remove the debug overlay completely
|
|
3070
|
-
*/
|
|
3071
|
-
destroy() {
|
|
3072
|
-
if (this.$container) {
|
|
3073
|
-
this.$container.remove();
|
|
3074
|
-
this.$container = null;
|
|
3075
|
-
}
|
|
3076
|
-
if (this.$statusIndicator) {
|
|
3077
|
-
this.$statusIndicator.remove();
|
|
3078
|
-
this.$statusIndicator = null;
|
|
3059
|
+
// Skip nested placeholders - they'll be hydrated by parent's render callback
|
|
3060
|
+
let parent = $element[0].parentElement;
|
|
3061
|
+
while (parent) {
|
|
3062
|
+
if (parent.classList.contains('_Component_Init')) {
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
parent = parent.parentElement;
|
|
3079
3066
|
}
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
*/
|
|
3084
|
-
updateStatus(mode) {
|
|
3085
|
-
if (!this.$statusIndicator)
|
|
3067
|
+
// Hydrate this placeholder
|
|
3068
|
+
const component = hydrateElement($element);
|
|
3069
|
+
if (!component)
|
|
3086
3070
|
return;
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
createOverlay() {
|
|
3091
|
-
// Add styles first
|
|
3092
|
-
this.addStyles();
|
|
3093
|
-
// Create container using jQuery
|
|
3094
|
-
this.$container = this.$('<div>')
|
|
3095
|
-
.addClass(`jqhtml-debug-overlay ${this.options.theme} ${this.options.position}`);
|
|
3096
|
-
// Create content structure
|
|
3097
|
-
const $content = this.$('<div>').addClass('jqhtml-debug-content');
|
|
3098
|
-
const $controls = this.$('<div>').addClass('jqhtml-debug-controls');
|
|
3099
|
-
// Add title
|
|
3100
|
-
const $title = this.$('<span>')
|
|
3101
|
-
.addClass('jqhtml-debug-title')
|
|
3102
|
-
.html('<strong>🐛 JQHTML Debug:</strong>');
|
|
3103
|
-
$controls.append($title);
|
|
3104
|
-
// Create buttons
|
|
3105
|
-
const buttons = [
|
|
3106
|
-
{ text: 'Slow Motion + Flash', action: 'enableSlowMotionDebug', class: 'success' },
|
|
3107
|
-
{ text: 'Basic Debug', action: 'enableBasicDebug', class: '' },
|
|
3108
|
-
{ text: 'Full Debug', action: 'enableFullDebug', class: '' },
|
|
3109
|
-
{ text: 'Sequential', action: 'enableSequentialMode', class: '' },
|
|
3110
|
-
{ text: 'Clear Debug', action: 'clearAllDebug', class: 'danger' },
|
|
3111
|
-
{ text: 'Settings', action: 'showDebugInfo', class: '' }
|
|
3112
|
-
];
|
|
3113
|
-
buttons.forEach(btn => {
|
|
3114
|
-
const $button = this.$('<button>')
|
|
3115
|
-
.text(btn.text)
|
|
3116
|
-
.addClass('jqhtml-debug-btn' + (btn.class ? ` ${btn.class}` : ''))
|
|
3117
|
-
.on('click', () => this.executeAction(btn.action));
|
|
3118
|
-
$controls.append($button);
|
|
3071
|
+
// On render, recursively hydrate any nested placeholders
|
|
3072
|
+
component.on('render', function () {
|
|
3073
|
+
hydrateNested(component.$, jQ);
|
|
3119
3074
|
});
|
|
3120
|
-
//
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
right: '10px',
|
|
3139
|
-
background: '#2c3e50',
|
|
3140
|
-
color: 'white',
|
|
3141
|
-
padding: '5px 10px',
|
|
3142
|
-
borderRadius: '4px',
|
|
3143
|
-
fontSize: '0.75rem',
|
|
3144
|
-
zIndex: '10001',
|
|
3145
|
-
opacity: '0.8',
|
|
3146
|
-
fontFamily: 'monospace'
|
|
3147
|
-
});
|
|
3148
|
-
this.$('body').append(this.$statusIndicator);
|
|
3149
|
-
}
|
|
3150
|
-
addStyles() {
|
|
3151
|
-
// Check if styles already exist
|
|
3152
|
-
if (this.$('#jqhtml-debug-styles').length > 0)
|
|
3075
|
+
// Collect ready promise - parent.ready() waits for all children via _wait_for_children_ready()
|
|
3076
|
+
readyPromises.push(component.ready());
|
|
3077
|
+
});
|
|
3078
|
+
// Fire jqhtml:ready event when all top-level components are ready
|
|
3079
|
+
const result = Promise.all(readyPromises);
|
|
3080
|
+
result.then(() => {
|
|
3081
|
+
document.dispatchEvent(new CustomEvent('jqhtml:ready'));
|
|
3082
|
+
});
|
|
3083
|
+
return result;
|
|
3084
|
+
}
|
|
3085
|
+
/**
|
|
3086
|
+
* Hydrate nested _Component_Init placeholders within a component's DOM.
|
|
3087
|
+
* Called from parent's 'render' event.
|
|
3088
|
+
*/
|
|
3089
|
+
function hydrateNested($scope, jQ) {
|
|
3090
|
+
$scope.find('._Component_Init').each(function () {
|
|
3091
|
+
const $element = jQ(this);
|
|
3092
|
+
if (!document.contains($element[0])) {
|
|
3153
3093
|
return;
|
|
3154
|
-
// Create and inject CSS using jQuery - concatenated strings for better minification
|
|
3155
|
-
const $style = this.$('<style>')
|
|
3156
|
-
.attr('id', 'jqhtml-debug-styles')
|
|
3157
|
-
.text('.jqhtml-debug-overlay {' +
|
|
3158
|
-
'position: fixed;' +
|
|
3159
|
-
'left: 0;' +
|
|
3160
|
-
'right: 0;' +
|
|
3161
|
-
'z-index: 10000;' +
|
|
3162
|
-
'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;' +
|
|
3163
|
-
'font-size: 0.8rem;' +
|
|
3164
|
-
'box-shadow: 0 2px 10px rgba(0,0,0,0.2);' +
|
|
3165
|
-
'}' +
|
|
3166
|
-
'.jqhtml-debug-overlay.top {' +
|
|
3167
|
-
'top: 0;' +
|
|
3168
|
-
'}' +
|
|
3169
|
-
'.jqhtml-debug-overlay.bottom {' +
|
|
3170
|
-
'bottom: 0;' +
|
|
3171
|
-
'}' +
|
|
3172
|
-
'.jqhtml-debug-overlay.dark {' +
|
|
3173
|
-
'background: #34495e;' +
|
|
3174
|
-
'color: #ecf0f1;' +
|
|
3175
|
-
'}' +
|
|
3176
|
-
'.jqhtml-debug-overlay.light {' +
|
|
3177
|
-
'background: #f8f9fa;' +
|
|
3178
|
-
'color: #333;' +
|
|
3179
|
-
'border-bottom: 1px solid #dee2e6;' +
|
|
3180
|
-
'}' +
|
|
3181
|
-
'.jqhtml-debug-content {' +
|
|
3182
|
-
'padding: 0.5rem 1rem;' +
|
|
3183
|
-
'}' +
|
|
3184
|
-
'.jqhtml-debug-controls {' +
|
|
3185
|
-
'display: flex;' +
|
|
3186
|
-
'flex-wrap: wrap;' +
|
|
3187
|
-
'gap: 8px;' +
|
|
3188
|
-
'align-items: center;' +
|
|
3189
|
-
'}' +
|
|
3190
|
-
'.jqhtml-debug-title {' +
|
|
3191
|
-
'margin-right: 10px;' +
|
|
3192
|
-
'font-weight: bold;' +
|
|
3193
|
-
'}' +
|
|
3194
|
-
'.jqhtml-debug-btn {' +
|
|
3195
|
-
'padding: 4px 8px;' +
|
|
3196
|
-
'border: none;' +
|
|
3197
|
-
'border-radius: 3px;' +
|
|
3198
|
-
'background: #3498db;' +
|
|
3199
|
-
'color: white;' +
|
|
3200
|
-
'cursor: pointer;' +
|
|
3201
|
-
'font-size: 0.75rem;' +
|
|
3202
|
-
'transition: background 0.2s;' +
|
|
3203
|
-
'}' +
|
|
3204
|
-
'.jqhtml-debug-btn:hover {' +
|
|
3205
|
-
'background: #2980b9;' +
|
|
3206
|
-
'}' +
|
|
3207
|
-
'.jqhtml-debug-btn.success {' +
|
|
3208
|
-
'background: #27ae60;' +
|
|
3209
|
-
'}' +
|
|
3210
|
-
'.jqhtml-debug-btn.success:hover {' +
|
|
3211
|
-
'background: #229954;' +
|
|
3212
|
-
'}' +
|
|
3213
|
-
'.jqhtml-debug-btn.danger {' +
|
|
3214
|
-
'background: #e74c3c;' +
|
|
3215
|
-
'}' +
|
|
3216
|
-
'.jqhtml-debug-btn.danger:hover {' +
|
|
3217
|
-
'background: #c0392b;' +
|
|
3218
|
-
'}' +
|
|
3219
|
-
'.jqhtml-debug-toggle {' +
|
|
3220
|
-
'padding: 4px 8px;' +
|
|
3221
|
-
'border: none;' +
|
|
3222
|
-
'border-radius: 3px;' +
|
|
3223
|
-
'background: #7f8c8d;' +
|
|
3224
|
-
'color: white;' +
|
|
3225
|
-
'cursor: pointer;' +
|
|
3226
|
-
'font-size: 0.75rem;' +
|
|
3227
|
-
'margin-left: auto;' +
|
|
3228
|
-
'}' +
|
|
3229
|
-
'.jqhtml-debug-toggle:hover {' +
|
|
3230
|
-
'background: #6c7b7d;' +
|
|
3231
|
-
'}' +
|
|
3232
|
-
'.jqhtml-debug-status.active {' +
|
|
3233
|
-
'background: #27ae60 !important;' +
|
|
3234
|
-
'}' +
|
|
3235
|
-
'@media (max-width: 768px) {' +
|
|
3236
|
-
'.jqhtml-debug-controls {' +
|
|
3237
|
-
'flex-direction: column;' +
|
|
3238
|
-
'align-items: flex-start;' +
|
|
3239
|
-
'}' +
|
|
3240
|
-
'.jqhtml-debug-title {' +
|
|
3241
|
-
'margin-bottom: 5px;' +
|
|
3242
|
-
'}' +
|
|
3243
|
-
'}');
|
|
3244
|
-
this.$('head').append($style);
|
|
3245
|
-
}
|
|
3246
|
-
toggle() {
|
|
3247
|
-
// Toggle between compact and full view
|
|
3248
|
-
this.options.compact = !this.options.compact;
|
|
3249
|
-
const $toggleBtn = this.$container.find('.jqhtml-debug-toggle');
|
|
3250
|
-
$toggleBtn.text(this.options.compact ? '▼' : '▲');
|
|
3251
|
-
const $buttons = this.$container.find('.jqhtml-debug-btn');
|
|
3252
|
-
if (this.options.compact) {
|
|
3253
|
-
$buttons.hide();
|
|
3254
3094
|
}
|
|
3255
|
-
|
|
3256
|
-
|
|
3095
|
+
// Skip if nested inside another placeholder
|
|
3096
|
+
let parent = $element[0].parentElement;
|
|
3097
|
+
while (parent && parent !== $scope[0]) {
|
|
3098
|
+
if (parent.classList.contains('_Component_Init')) {
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
parent = parent.parentElement;
|
|
3257
3102
|
}
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
const jqhtml = getJqhtml();
|
|
3261
|
-
if (!jqhtml) {
|
|
3262
|
-
console.warn('JQHTML not available - make sure it\'s loaded and exposed globally');
|
|
3103
|
+
const component = hydrateElement($element);
|
|
3104
|
+
if (!component)
|
|
3263
3105
|
return;
|
|
3106
|
+
// Recursively handle deeper nesting
|
|
3107
|
+
component.on('render', function () {
|
|
3108
|
+
hydrateNested(component.$, jQ);
|
|
3109
|
+
});
|
|
3110
|
+
});
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Hydrate a single _Component_Init element into a live component.
|
|
3114
|
+
*/
|
|
3115
|
+
function hydrateElement($element, jQ) {
|
|
3116
|
+
const componentName = $element.attr('data-component-init-name');
|
|
3117
|
+
if (!componentName)
|
|
3118
|
+
return null;
|
|
3119
|
+
// Parse args
|
|
3120
|
+
const argsString = $element.attr('data-component-args');
|
|
3121
|
+
let args = {};
|
|
3122
|
+
if (argsString) {
|
|
3123
|
+
try {
|
|
3124
|
+
args = JSON.parse(argsString);
|
|
3264
3125
|
}
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
case 'enableBasicDebug':
|
|
3288
|
-
jqhtml.enableDebugMode('basic');
|
|
3289
|
-
this.updateStatus('Basic');
|
|
3290
|
-
console.log('🐛 Basic Debug Mode Enabled');
|
|
3291
|
-
break;
|
|
3292
|
-
case 'enableFullDebug':
|
|
3293
|
-
jqhtml.enableDebugMode('full');
|
|
3294
|
-
this.updateStatus('Full');
|
|
3295
|
-
console.log('🐛 Full Debug Mode Enabled');
|
|
3296
|
-
break;
|
|
3297
|
-
case 'enableSequentialMode':
|
|
3298
|
-
jqhtml.setDebugSettings({
|
|
3299
|
-
logCreationReady: true,
|
|
3300
|
-
sequentialProcessing: true,
|
|
3301
|
-
flashComponents: true,
|
|
3302
|
-
profilePerformance: true
|
|
3303
|
-
});
|
|
3304
|
-
this.updateStatus('Sequential');
|
|
3305
|
-
console.log('🐛 Sequential Processing Mode Enabled');
|
|
3306
|
-
break;
|
|
3307
|
-
case 'clearAllDebug':
|
|
3308
|
-
jqhtml.clearDebugSettings();
|
|
3309
|
-
this.updateStatus('Off');
|
|
3310
|
-
console.log('🐛 All Debug Modes Disabled');
|
|
3311
|
-
break;
|
|
3312
|
-
case 'showDebugInfo':
|
|
3313
|
-
const settings = JSON.stringify(jqhtml.debug, null, 2);
|
|
3314
|
-
console.log('🐛 Current Debug Settings:', settings);
|
|
3315
|
-
alert('Debug settings logged to console:\n\n' + (Object.keys(jqhtml.debug).length > 0 ? settings : 'No debug settings active'));
|
|
3316
|
-
break;
|
|
3317
|
-
}
|
|
3126
|
+
catch (e) {
|
|
3127
|
+
console.error(`[jqhtml.boot] Failed to parse args for ${componentName}:`, e);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
// Strip data- prefix from args if present
|
|
3131
|
+
const filteredArgs = {};
|
|
3132
|
+
for (const [key, value] of Object.entries(args)) {
|
|
3133
|
+
filteredArgs[key.startsWith('data-') ? key.substring(5) : key] = value;
|
|
3134
|
+
}
|
|
3135
|
+
// Capture innerHTML for content() and mark as boot-created
|
|
3136
|
+
filteredArgs._inner_html = $element.html();
|
|
3137
|
+
filteredArgs._component_name = componentName;
|
|
3138
|
+
// Cleanup placeholder
|
|
3139
|
+
$element.removeAttr('data-component-init-name');
|
|
3140
|
+
$element.removeAttr('data-component-args');
|
|
3141
|
+
$element.removeData('component-init-name');
|
|
3142
|
+
$element.removeData('component-args');
|
|
3143
|
+
$element.removeClass('_Component_Init');
|
|
3144
|
+
$element.empty();
|
|
3145
|
+
// Create component
|
|
3146
|
+
try {
|
|
3147
|
+
return $element.component(componentName, filteredArgs).component();
|
|
3318
3148
|
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
function showDebugOverlay(options) {
|
|
3323
|
-
return DebugOverlay.show(options);
|
|
3324
|
-
}
|
|
3325
|
-
function hideDebugOverlay() {
|
|
3326
|
-
DebugOverlay.hide();
|
|
3327
|
-
}
|
|
3328
|
-
// Auto-initialize if debug query parameter is present
|
|
3329
|
-
if (typeof window !== 'undefined') {
|
|
3330
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
3331
|
-
if (urlParams.get('debug') === 'true' || urlParams.get('jqhtml-debug') === 'true') {
|
|
3332
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
3333
|
-
DebugOverlay.show();
|
|
3334
|
-
});
|
|
3149
|
+
catch (error) {
|
|
3150
|
+
console.error(`[jqhtml.boot] Failed to create ${componentName}:`, error);
|
|
3151
|
+
return null;
|
|
3335
3152
|
}
|
|
3336
3153
|
}
|
|
3337
3154
|
|
|
@@ -4233,7 +4050,7 @@ function init(jQuery) {
|
|
|
4233
4050
|
}
|
|
4234
4051
|
}
|
|
4235
4052
|
// Version - will be replaced during build with actual version from package.json
|
|
4236
|
-
const version = '2.
|
|
4053
|
+
const version = '2.3.6';
|
|
4237
4054
|
// Default export with all functionality
|
|
4238
4055
|
const jqhtml = {
|
|
4239
4056
|
// Core
|
|
@@ -4257,6 +4074,8 @@ const jqhtml = {
|
|
|
4257
4074
|
escape_html,
|
|
4258
4075
|
// Version property - internal
|
|
4259
4076
|
__version: version,
|
|
4077
|
+
// state facts
|
|
4078
|
+
tombstone: 'pepperoni and cheese',
|
|
4260
4079
|
// Debug settings
|
|
4261
4080
|
debug: {
|
|
4262
4081
|
enabled: false,
|
|
@@ -4283,15 +4102,6 @@ const jqhtml = {
|
|
|
4283
4102
|
clearDebugSettings() {
|
|
4284
4103
|
this.debug = {};
|
|
4285
4104
|
},
|
|
4286
|
-
// Debug overlay methods
|
|
4287
|
-
showDebugOverlay(options) {
|
|
4288
|
-
return DebugOverlay.show(options);
|
|
4289
|
-
},
|
|
4290
|
-
hideDebugOverlay() {
|
|
4291
|
-
return DebugOverlay.hide();
|
|
4292
|
-
},
|
|
4293
|
-
// Export DebugOverlay class for direct access
|
|
4294
|
-
DebugOverlay,
|
|
4295
4105
|
// Install globals function
|
|
4296
4106
|
installGlobals() {
|
|
4297
4107
|
if (typeof window !== 'undefined') {
|
|
@@ -4325,7 +4135,9 @@ const jqhtml = {
|
|
|
4325
4135
|
// Cache key setter - enables component caching via local storage
|
|
4326
4136
|
set_cache_key(cache_key) {
|
|
4327
4137
|
Jqhtml_Local_Storage.set_cache_key(cache_key);
|
|
4328
|
-
}
|
|
4138
|
+
},
|
|
4139
|
+
// Boot - hydrate server-rendered component placeholders
|
|
4140
|
+
boot
|
|
4329
4141
|
};
|
|
4330
4142
|
// Auto-register on window for browser environments
|
|
4331
4143
|
// This is REQUIRED for compiled templates which use window.jqhtml.register_template()
|
|
@@ -4343,5 +4155,5 @@ if (typeof window !== 'undefined' && !window.jqhtml) {
|
|
|
4343
4155
|
}
|
|
4344
4156
|
}
|
|
4345
4157
|
|
|
4346
|
-
export {
|
|
4158
|
+
export { Jqhtml_Component, LifecycleManager as Jqhtml_LifecycleManager, Jqhtml_Local_Storage, LifecycleManager, Load_Coordinator, applyDebugDelay, boot, create_component, jqhtml as default, devWarn, escape_html, extract_slots, get_component_class, get_component_names, get_registered_templates, get_template, get_template_by_class, handleComponentError, has_component, init, init_jquery_plugin, isSequentialProcessing, list_components, logDataChange, logDispatch, logInstruction, logLifecycle, process_instructions, register_component, register_template, render_template, version };
|
|
4347
4159
|
//# sourceMappingURL=index.js.map
|