@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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * JQHTML Core v2.2.222
2
+ * JQHTML Core v2.3.6
3
3
  * (c) 2025 JQHTML Team
4
4
  * Released under the MIT License
5
5
  */
@@ -884,7 +884,7 @@ function devWarn(message) {
884
884
  console.warn(`[JQHTML Dev Warning] ${message}`);
885
885
  }
886
886
  // Get global jqhtml object
887
- function getJqhtml$1() {
887
+ function getJqhtml() {
888
888
  if (typeof window !== 'undefined' && window.jqhtml) {
889
889
  return window.jqhtml;
890
890
  }
@@ -897,7 +897,7 @@ function getJqhtml$1() {
897
897
  }
898
898
  // Visual flash effect
899
899
  function flashComponent(component, eventType) {
900
- const jqhtml = getJqhtml$1();
900
+ const jqhtml = getJqhtml();
901
901
  if (!jqhtml?.debug?.flashComponents)
902
902
  return;
903
903
  const duration = jqhtml.debug.flashDuration || 500;
@@ -919,7 +919,7 @@ function flashComponent(component, eventType) {
919
919
  }
920
920
  // Log lifecycle event
921
921
  function logLifecycle(component, phase, status) {
922
- const jqhtml = getJqhtml$1();
922
+ const jqhtml = getJqhtml();
923
923
  if (!jqhtml?.debug)
924
924
  return;
925
925
  const shouldLog = jqhtml.debug.logFullLifecycle ||
@@ -965,7 +965,7 @@ function logLifecycle(component, phase, status) {
965
965
  }
966
966
  // Apply delays based on lifecycle phase
967
967
  function applyDebugDelay(phase) {
968
- const jqhtml = getJqhtml$1();
968
+ const jqhtml = getJqhtml();
969
969
  if (!jqhtml?.debug)
970
970
  return;
971
971
  let delayMs = 0;
@@ -986,14 +986,14 @@ function applyDebugDelay(phase) {
986
986
  }
987
987
  // Log instruction processing
988
988
  function logInstruction(type, data) {
989
- const jqhtml = getJqhtml$1();
989
+ const jqhtml = getJqhtml();
990
990
  if (!jqhtml?.debug?.logInstructionProcessing)
991
991
  return;
992
992
  console.log(`[JQHTML Instruction] ${type}:`, data);
993
993
  }
994
994
  // Log data changes
995
995
  function logDataChange(component, property, oldValue, newValue) {
996
- const jqhtml = getJqhtml$1();
996
+ const jqhtml = getJqhtml();
997
997
  if (!jqhtml?.debug?.traceDataFlow)
998
998
  return;
999
999
  console.log(`[JQHTML Data] ${component.constructor.name}#${component._cid}.data.${property}:`, { old: oldValue, new: newValue });
@@ -1006,7 +1006,7 @@ function updateComponentTree() {
1006
1006
  }
1007
1007
  // Router dispatch logging
1008
1008
  function logDispatch(url, route, params, verbose = false) {
1009
- const jqhtml = getJqhtml$1();
1009
+ const jqhtml = getJqhtml();
1010
1010
  if (!jqhtml?.debug)
1011
1011
  return;
1012
1012
  const shouldLog = jqhtml.debug.logDispatch || jqhtml.debug.logDispatchVerbose;
@@ -1028,12 +1028,12 @@ function logDispatch(url, route, params, verbose = false) {
1028
1028
  }
1029
1029
  // Check if sequential processing is enabled
1030
1030
  function isSequentialProcessing() {
1031
- const jqhtml = getJqhtml$1();
1031
+ const jqhtml = getJqhtml();
1032
1032
  return jqhtml?.debug?.sequentialProcessing || false;
1033
1033
  }
1034
1034
  // Error handling with break on error
1035
1035
  function handleComponentError(component, phase, error) {
1036
- const jqhtml = getJqhtml$1();
1036
+ const jqhtml = getJqhtml();
1037
1037
  console.error(`[JQHTML Error] ${component.constructor.name}#${component._cid} failed in ${phase}:`, error);
1038
1038
  if (jqhtml?.debug?.breakOnError) {
1039
1039
  debugger; // This will pause execution in dev tools
@@ -1061,6 +1061,9 @@ function handleComponentError(component, phase, error) {
1061
1061
  * - Scoped IDs using _cid pattern
1062
1062
  * - Event emission and CSS class hierarchy
1063
1063
  */
1064
+ // WeakMap storage for protected lifecycle method implementations (Option 2)
1065
+ // See docs/internal/lifecycle-method-protection.md for design details
1066
+ const lifecycle_impls = new WeakMap();
1064
1067
  class Jqhtml_Component {
1065
1068
  constructor(element, args = {}) {
1066
1069
  this._ready_state = 0; // 0=created, 1=init, 2=loaded, 3=rendered, 4=ready
@@ -1081,6 +1084,7 @@ class Jqhtml_Component {
1081
1084
  this.__initial_data_snapshot = null; // Snapshot of this.data after on_create() for restoration before on_load()
1082
1085
  this.__data_frozen = false; // Track if this.data is currently frozen
1083
1086
  this.next_reload_force_refresh = null; // State machine for reload()/refresh() debounce precedence
1087
+ this.__lifecycle_authorized = false; // Flag for lifecycle method protection
1084
1088
  this._cid = this._generate_cid();
1085
1089
  this._lifecycle_manager = LifecycleManager.get_instance();
1086
1090
  // Create or wrap element
@@ -1194,6 +1198,63 @@ class Jqhtml_Component {
1194
1198
  this.state = {};
1195
1199
  this._log_lifecycle('construct', 'complete');
1196
1200
  }
1201
+ /**
1202
+ * Protect lifecycle methods from manual invocation
1203
+ * Stores original implementations in WeakMap, replaces with guarded wrappers
1204
+ * @private
1205
+ */
1206
+ _protect_lifecycle_methods() {
1207
+ const methods = {
1208
+ on_create: 'Called automatically during creation.',
1209
+ on_render: 'Use render() to trigger a re-render.',
1210
+ on_load: 'Use reload() to refresh data.',
1211
+ on_ready: 'Called automatically when ready.',
1212
+ on_stop: 'Use stop() to stop the component.'
1213
+ };
1214
+ const impls = {};
1215
+ const self = this;
1216
+ for (const [name, help] of Object.entries(methods)) {
1217
+ const original = this[name];
1218
+ // Skip if using base class default (empty method)
1219
+ if (original === Jqhtml_Component.prototype[name])
1220
+ continue;
1221
+ impls[name] = original;
1222
+ // Create wrapper with same function name (for stack traces)
1223
+ this[name] = {
1224
+ [name](...args) {
1225
+ if (!self.__lifecycle_authorized) {
1226
+ throw new Error(`[JQHTML] ${name}() cannot be called manually. ${help}\n` +
1227
+ `Component: ${self.component_name()} (_cid: ${self._cid})`);
1228
+ }
1229
+ return lifecycle_impls.get(self)[name].apply(self, args);
1230
+ }
1231
+ }[name];
1232
+ }
1233
+ lifecycle_impls.set(this, impls);
1234
+ }
1235
+ /**
1236
+ * Call a lifecycle method with authorization (async)
1237
+ * Framework calls this to invoke lifecycle methods, bypassing the protection wrapper.
1238
+ * The flag is set momentarily for the wrapper check, then reset BEFORE user code runs.
1239
+ * This ensures any nested lifecycle calls from user code will fail the flag check.
1240
+ * @private
1241
+ */
1242
+ async _call_lifecycle(name, context) {
1243
+ // Get the original implementation (bypasses wrapper entirely)
1244
+ const impl = lifecycle_impls.get(this)?.[name] || this[name];
1245
+ // Call original directly - no flag needed since we bypass the wrapper
1246
+ return await impl.call(context || this);
1247
+ }
1248
+ /**
1249
+ * Call a lifecycle method with authorization (sync version for sync methods like on_stop)
1250
+ * @private
1251
+ */
1252
+ _call_lifecycle_sync(name) {
1253
+ // Get the original implementation (bypasses wrapper entirely)
1254
+ const impl = lifecycle_impls.get(this)?.[name] || this[name];
1255
+ // Call original directly - no flag needed since we bypass the wrapper
1256
+ return impl.call(this);
1257
+ }
1197
1258
  /**
1198
1259
  * Boot - Start the full component lifecycle
1199
1260
  * Called immediately after construction by instruction processor
@@ -1207,6 +1268,8 @@ class Jqhtml_Component {
1207
1268
  if (this._booted)
1208
1269
  return;
1209
1270
  this._booted = true;
1271
+ // Protect lifecycle methods from manual invocation (must happen after subclass constructor)
1272
+ this._protect_lifecycle_methods();
1210
1273
  await this._lifecycle_manager.boot_component(this);
1211
1274
  }
1212
1275
  // -------------------------------------------------------------------------
@@ -1417,9 +1480,9 @@ class Jqhtml_Component {
1417
1480
  // Don't update ready state here - let phases complete in order
1418
1481
  this._update_debug_attrs();
1419
1482
  this._log_lifecycle('render', 'complete');
1420
- // Call on_render() immediately after render completes
1483
+ // Call on_render() with authorization (sync) immediately after render completes
1421
1484
  // This ensures event handlers are always re-attached after DOM updates
1422
- const renderResult = this.on_render();
1485
+ const renderResult = this._call_lifecycle_sync('on_render');
1423
1486
  if (renderResult && typeof renderResult.then === 'function') {
1424
1487
  console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_render(). ` +
1425
1488
  `on_render() must be synchronous code. Remove 'async' from the function declaration.`);
@@ -1484,8 +1547,8 @@ class Jqhtml_Component {
1484
1547
  if (this._render_count !== render_id) {
1485
1548
  return; // Stale render, don't call on_ready
1486
1549
  }
1487
- // Call on_ready hook
1488
- await this.on_ready();
1550
+ // Call on_ready hook with authorization
1551
+ await this._call_lifecycle('on_ready');
1489
1552
  // Trigger ready event
1490
1553
  await this.trigger('ready');
1491
1554
  })();
@@ -1505,8 +1568,8 @@ class Jqhtml_Component {
1505
1568
  if (this._stopped || this._ready_state >= 1)
1506
1569
  return;
1507
1570
  this._log_lifecycle('create', 'start');
1508
- // Call on_create() and validate it's synchronous
1509
- const result = this.on_create();
1571
+ // Call on_create() with authorization and validate it's synchronous
1572
+ const result = await this._call_lifecycle('on_create');
1510
1573
  if (result && typeof result.then === 'function') {
1511
1574
  console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_create(). ` +
1512
1575
  `on_create() must be synchronous code. Remove 'async' from the function declaration.`);
@@ -1621,8 +1684,8 @@ class Jqhtml_Component {
1621
1684
  if (window.jqhtml?.debug?.verbose) {
1622
1685
  console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - load deduplication and caching disabled`, { uncacheable_property });
1623
1686
  }
1624
- // Execute on_load() without deduplication or caching
1625
- await this.on_load();
1687
+ // Execute on_load() with authorization, without deduplication or caching
1688
+ await this._call_lifecycle('on_load');
1626
1689
  this.__data_frozen = true;
1627
1690
  return;
1628
1691
  }
@@ -1731,7 +1794,7 @@ class Jqhtml_Component {
1731
1794
  // - Should errors reset state machine flags (next_reload_force_refresh)?
1732
1795
  // - Should partial data be preserved or rolled back?
1733
1796
  // - Should followers be notified differently based on error type?
1734
- await this.on_load.call(restricted_this);
1797
+ await this._call_lifecycle('on_load', restricted_this);
1735
1798
  }
1736
1799
  catch (error) {
1737
1800
  // Handle error and notify coordinator
@@ -1803,7 +1866,7 @@ class Jqhtml_Component {
1803
1866
  this._log_lifecycle('ready', 'start');
1804
1867
  // Wait for all children to reach ready state (bottom-up execution)
1805
1868
  await this._wait_for_children_ready();
1806
- await this.on_ready();
1869
+ await this._call_lifecycle('on_ready');
1807
1870
  this._ready_state = 4;
1808
1871
  this._update_debug_attrs();
1809
1872
  this._log_lifecycle('ready', 'complete');
@@ -1853,6 +1916,14 @@ class Jqhtml_Component {
1853
1916
  * @private
1854
1917
  */
1855
1918
  async _wait_for_children_ready() {
1919
+ // Server-rendered components (created via jqhtml.boot()) may have children
1920
+ // that were hydrated asynchronously during the 'render' event callback.
1921
+ // Those children couldn't register via _find_dom_parent() because they were
1922
+ // created after the parent's lifecycle started. Force DOM traversal fallback
1923
+ // to reliably discover all children, including boot-hydrated ones.
1924
+ if (this.args._inner_html !== undefined) {
1925
+ this._use_dom_fallback = true;
1926
+ }
1856
1927
  const children = this._get_dom_children();
1857
1928
  if (children.length === 0) {
1858
1929
  return; // No children, nothing to wait for
@@ -2011,7 +2082,7 @@ class Jqhtml_Component {
2011
2082
  this.data = JSON.parse(JSON.stringify(this.__initial_data_snapshot));
2012
2083
  }
2013
2084
  try {
2014
- await this.on_load();
2085
+ await this._call_lifecycle('on_load');
2015
2086
  }
2016
2087
  finally {
2017
2088
  // Freeze this.data after on_load() completes
@@ -2087,7 +2158,7 @@ class Jqhtml_Component {
2087
2158
  // STEP 3.5 & 4: Wait for children and call on_ready (only if we rendered)
2088
2159
  if (rendered_from_cache || should_render) {
2089
2160
  await this._wait_for_children_ready();
2090
- await this.on_ready();
2161
+ await this._call_lifecycle('on_ready');
2091
2162
  }
2092
2163
  this._log_lifecycle('reload', 'complete');
2093
2164
  }
@@ -2121,16 +2192,14 @@ class Jqhtml_Component {
2121
2192
  this.$.addClass('_Component_Stopped');
2122
2193
  // Unregister from lifecycle manager
2123
2194
  this._lifecycle_manager.unregister_component(this);
2124
- // Call user's on_stop() hook
2125
- const stopResult = this.on_stop();
2195
+ // Call user's on_stop() hook with authorization (sync)
2196
+ const stopResult = this._call_lifecycle_sync('on_stop');
2126
2197
  if (stopResult && typeof stopResult.then === 'function') {
2127
2198
  console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_stop(). ` +
2128
2199
  `on_stop() must be synchronous code. Remove 'async' from the function declaration.`);
2129
2200
  }
2130
- // Fire registered destroy callbacks
2131
- this.trigger('destroy');
2132
- // Trigger jQuery destroy event
2133
- this.$.trigger('destroy');
2201
+ // Fire registered stop callbacks
2202
+ this.trigger('stop');
2134
2203
  // Remove from DOM parent's children
2135
2204
  if (this._dom_parent) {
2136
2205
  this._dom_parent._dom_children.delete(this);
@@ -2959,384 +3028,132 @@ function escape_html(str) {
2959
3028
  }
2960
3029
 
2961
3030
  /**
2962
- * JQHTML Debug Overlay
3031
+ * JQHTML Boot - Component Hydration System
3032
+ *
3033
+ * Bridges server-rendered HTML and client-side jqhtml components.
2963
3034
  *
2964
- * Independent debug controls using pure jQuery DOM manipulation.
2965
- * Does NOT use JQHTML components so it works even when components are broken.
3035
+ * Server output: <div class="_Component_Init" data-component-init-name="User_Card" data-component-args='{"id":1}'></div>
3036
+ * After boot(): <div class="User_Card Component">...rendered template...</div>
3037
+ *
3038
+ * Usage:
3039
+ * await jqhtml.boot(); // Hydrate all placeholders, wait for ready
3040
+ * jqhtml.boot($('#container')); // Hydrate within scope
2966
3041
  */
2967
- // Get global jQuery
2968
- function getJQuery() {
2969
- if (typeof window !== 'undefined' && window.$) {
2970
- return window.$;
2971
- }
2972
- if (typeof window !== 'undefined' && window.jQuery) {
2973
- return window.jQuery;
2974
- }
2975
- throw new Error('FATAL: jQuery is not defined. jQuery must be loaded before using JQHTML. ' +
2976
- 'Add <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> before loading JQHTML.');
2977
- }
2978
- // Get global jqhtml object
2979
- function getJqhtml() {
2980
- if (typeof window !== 'undefined' && window.jqhtml) {
2981
- return window.jqhtml;
2982
- }
2983
- if (typeof globalThis !== 'undefined' && globalThis.jqhtml) {
2984
- return globalThis.jqhtml;
2985
- }
2986
- throw new Error('FATAL: window.jqhtml is not defined. The JQHTML runtime must be loaded before using JQHTML components. ' +
2987
- 'Ensure @jqhtml/core is imported and initialized before attempting to use debug features.');
2988
- }
2989
- class DebugOverlay {
2990
- constructor(options = {}) {
2991
- this.$container = null;
2992
- this.$statusIndicator = null;
2993
- this.$ = getJQuery();
2994
- if (!this.$) {
2995
- throw new Error('jQuery is required for DebugOverlay');
2996
- }
2997
- this.options = {
2998
- position: 'bottom',
2999
- theme: 'dark',
3000
- compact: false,
3001
- showStatus: true,
3002
- autoHide: false,
3003
- ...options
3004
- };
3005
- }
3006
- /**
3007
- * Static method to show debug overlay (singleton pattern)
3008
- */
3009
- static show(options) {
3010
- if (!DebugOverlay.instance) {
3011
- DebugOverlay.instance = new DebugOverlay(options);
3012
- }
3013
- DebugOverlay.instance.display();
3014
- return DebugOverlay.instance;
3015
- }
3016
- /**
3017
- * Static method to hide debug overlay
3018
- */
3019
- static hide() {
3020
- if (DebugOverlay.instance) {
3021
- DebugOverlay.instance.hide();
3022
- }
3023
- }
3024
- /**
3025
- * Static method to toggle debug overlay visibility
3026
- */
3027
- static toggle() {
3028
- if (DebugOverlay.instance && DebugOverlay.instance.$container) {
3029
- if (DebugOverlay.instance.$container.is(':visible')) {
3030
- DebugOverlay.hide();
3031
- }
3032
- else {
3033
- DebugOverlay.instance.display();
3034
- }
3035
- }
3036
- else {
3037
- DebugOverlay.show();
3038
- }
3039
- }
3040
- /**
3041
- * Static method to destroy debug overlay
3042
- */
3043
- static destroy() {
3044
- if (DebugOverlay.instance) {
3045
- DebugOverlay.instance.destroy();
3046
- DebugOverlay.instance = null;
3047
- }
3048
- }
3049
- /**
3050
- * Display the debug overlay
3051
- */
3052
- display() {
3053
- if (this.$container) {
3054
- this.$container.show();
3042
+ /**
3043
+ * Hydrate all _Component_Init placeholders within a scope.
3044
+ *
3045
+ * @param scope - jQuery object, HTMLElement, or undefined (defaults to body)
3046
+ * @returns Promise that resolves when all components (including nested) are ready
3047
+ */
3048
+ function boot(scope) {
3049
+ const jQ = typeof jQuery !== 'undefined' ? jQuery : $;
3050
+ if (!scope) {
3051
+ scope = jQ('body');
3052
+ }
3053
+ else if (!(scope instanceof jQ)) {
3054
+ scope = jQ(scope);
3055
+ }
3056
+ const readyPromises = [];
3057
+ // Find top-level placeholders only (skip those nested inside other placeholders)
3058
+ scope.find('._Component_Init').each(function () {
3059
+ const $element = jQ(this);
3060
+ // Skip if removed from DOM
3061
+ if (!document.contains($element[0])) {
3055
3062
  return;
3056
3063
  }
3057
- this.createOverlay();
3058
- if (this.options.showStatus) {
3059
- this.createStatusIndicator();
3060
- }
3061
- }
3062
- /**
3063
- * Hide the debug overlay
3064
- */
3065
- hide() {
3066
- if (this.$container) {
3067
- this.$container.hide();
3068
- }
3069
- if (this.$statusIndicator) {
3070
- this.$statusIndicator.hide();
3071
- }
3072
- }
3073
- /**
3074
- * Remove the debug overlay completely
3075
- */
3076
- destroy() {
3077
- if (this.$container) {
3078
- this.$container.remove();
3079
- this.$container = null;
3080
- }
3081
- if (this.$statusIndicator) {
3082
- this.$statusIndicator.remove();
3083
- this.$statusIndicator = null;
3064
+ // Skip nested placeholders - they'll be hydrated by parent's render callback
3065
+ let parent = $element[0].parentElement;
3066
+ while (parent) {
3067
+ if (parent.classList.contains('_Component_Init')) {
3068
+ return;
3069
+ }
3070
+ parent = parent.parentElement;
3084
3071
  }
3085
- }
3086
- /**
3087
- * Update the status indicator
3088
- */
3089
- updateStatus(mode) {
3090
- if (!this.$statusIndicator)
3072
+ // Hydrate this placeholder
3073
+ const component = hydrateElement($element);
3074
+ if (!component)
3091
3075
  return;
3092
- this.$statusIndicator.text('Debug: ' + mode);
3093
- this.$statusIndicator.attr('class', 'jqhtml-debug-status' + (mode !== 'Off' ? ' active' : ''));
3094
- }
3095
- createOverlay() {
3096
- // Add styles first
3097
- this.addStyles();
3098
- // Create container using jQuery
3099
- this.$container = this.$('<div>')
3100
- .addClass(`jqhtml-debug-overlay ${this.options.theme} ${this.options.position}`);
3101
- // Create content structure
3102
- const $content = this.$('<div>').addClass('jqhtml-debug-content');
3103
- const $controls = this.$('<div>').addClass('jqhtml-debug-controls');
3104
- // Add title
3105
- const $title = this.$('<span>')
3106
- .addClass('jqhtml-debug-title')
3107
- .html('<strong>🐛 JQHTML Debug:</strong>');
3108
- $controls.append($title);
3109
- // Create buttons
3110
- const buttons = [
3111
- { text: 'Slow Motion + Flash', action: 'enableSlowMotionDebug', class: 'success' },
3112
- { text: 'Basic Debug', action: 'enableBasicDebug', class: '' },
3113
- { text: 'Full Debug', action: 'enableFullDebug', class: '' },
3114
- { text: 'Sequential', action: 'enableSequentialMode', class: '' },
3115
- { text: 'Clear Debug', action: 'clearAllDebug', class: 'danger' },
3116
- { text: 'Settings', action: 'showDebugInfo', class: '' }
3117
- ];
3118
- buttons.forEach(btn => {
3119
- const $button = this.$('<button>')
3120
- .text(btn.text)
3121
- .addClass('jqhtml-debug-btn' + (btn.class ? ` ${btn.class}` : ''))
3122
- .on('click', () => this.executeAction(btn.action));
3123
- $controls.append($button);
3076
+ // On render, recursively hydrate any nested placeholders
3077
+ component.on('render', function () {
3078
+ hydrateNested(component.$, jQ);
3124
3079
  });
3125
- // Add minimize/close button
3126
- const $toggleBtn = this.$('<button>')
3127
- .text(this.options.compact ? '▼' : '▲')
3128
- .addClass('jqhtml-debug-toggle')
3129
- .on('click', () => this.toggle());
3130
- $controls.append($toggleBtn);
3131
- // Assemble and add to page
3132
- $content.append($controls);
3133
- this.$container.append($content);
3134
- this.$('body').append(this.$container);
3135
- }
3136
- createStatusIndicator() {
3137
- this.$statusIndicator = this.$('<div>')
3138
- .addClass('jqhtml-debug-status')
3139
- .text('Debug: Off')
3140
- .css({
3141
- position: 'fixed',
3142
- top: '10px',
3143
- right: '10px',
3144
- background: '#2c3e50',
3145
- color: 'white',
3146
- padding: '5px 10px',
3147
- borderRadius: '4px',
3148
- fontSize: '0.75rem',
3149
- zIndex: '10001',
3150
- opacity: '0.8',
3151
- fontFamily: 'monospace'
3152
- });
3153
- this.$('body').append(this.$statusIndicator);
3154
- }
3155
- addStyles() {
3156
- // Check if styles already exist
3157
- if (this.$('#jqhtml-debug-styles').length > 0)
3080
+ // Collect ready promise - parent.ready() waits for all children via _wait_for_children_ready()
3081
+ readyPromises.push(component.ready());
3082
+ });
3083
+ // Fire jqhtml:ready event when all top-level components are ready
3084
+ const result = Promise.all(readyPromises);
3085
+ result.then(() => {
3086
+ document.dispatchEvent(new CustomEvent('jqhtml:ready'));
3087
+ });
3088
+ return result;
3089
+ }
3090
+ /**
3091
+ * Hydrate nested _Component_Init placeholders within a component's DOM.
3092
+ * Called from parent's 'render' event.
3093
+ */
3094
+ function hydrateNested($scope, jQ) {
3095
+ $scope.find('._Component_Init').each(function () {
3096
+ const $element = jQ(this);
3097
+ if (!document.contains($element[0])) {
3158
3098
  return;
3159
- // Create and inject CSS using jQuery - concatenated strings for better minification
3160
- const $style = this.$('<style>')
3161
- .attr('id', 'jqhtml-debug-styles')
3162
- .text('.jqhtml-debug-overlay {' +
3163
- 'position: fixed;' +
3164
- 'left: 0;' +
3165
- 'right: 0;' +
3166
- 'z-index: 10000;' +
3167
- 'font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;' +
3168
- 'font-size: 0.8rem;' +
3169
- 'box-shadow: 0 2px 10px rgba(0,0,0,0.2);' +
3170
- '}' +
3171
- '.jqhtml-debug-overlay.top {' +
3172
- 'top: 0;' +
3173
- '}' +
3174
- '.jqhtml-debug-overlay.bottom {' +
3175
- 'bottom: 0;' +
3176
- '}' +
3177
- '.jqhtml-debug-overlay.dark {' +
3178
- 'background: #34495e;' +
3179
- 'color: #ecf0f1;' +
3180
- '}' +
3181
- '.jqhtml-debug-overlay.light {' +
3182
- 'background: #f8f9fa;' +
3183
- 'color: #333;' +
3184
- 'border-bottom: 1px solid #dee2e6;' +
3185
- '}' +
3186
- '.jqhtml-debug-content {' +
3187
- 'padding: 0.5rem 1rem;' +
3188
- '}' +
3189
- '.jqhtml-debug-controls {' +
3190
- 'display: flex;' +
3191
- 'flex-wrap: wrap;' +
3192
- 'gap: 8px;' +
3193
- 'align-items: center;' +
3194
- '}' +
3195
- '.jqhtml-debug-title {' +
3196
- 'margin-right: 10px;' +
3197
- 'font-weight: bold;' +
3198
- '}' +
3199
- '.jqhtml-debug-btn {' +
3200
- 'padding: 4px 8px;' +
3201
- 'border: none;' +
3202
- 'border-radius: 3px;' +
3203
- 'background: #3498db;' +
3204
- 'color: white;' +
3205
- 'cursor: pointer;' +
3206
- 'font-size: 0.75rem;' +
3207
- 'transition: background 0.2s;' +
3208
- '}' +
3209
- '.jqhtml-debug-btn:hover {' +
3210
- 'background: #2980b9;' +
3211
- '}' +
3212
- '.jqhtml-debug-btn.success {' +
3213
- 'background: #27ae60;' +
3214
- '}' +
3215
- '.jqhtml-debug-btn.success:hover {' +
3216
- 'background: #229954;' +
3217
- '}' +
3218
- '.jqhtml-debug-btn.danger {' +
3219
- 'background: #e74c3c;' +
3220
- '}' +
3221
- '.jqhtml-debug-btn.danger:hover {' +
3222
- 'background: #c0392b;' +
3223
- '}' +
3224
- '.jqhtml-debug-toggle {' +
3225
- 'padding: 4px 8px;' +
3226
- 'border: none;' +
3227
- 'border-radius: 3px;' +
3228
- 'background: #7f8c8d;' +
3229
- 'color: white;' +
3230
- 'cursor: pointer;' +
3231
- 'font-size: 0.75rem;' +
3232
- 'margin-left: auto;' +
3233
- '}' +
3234
- '.jqhtml-debug-toggle:hover {' +
3235
- 'background: #6c7b7d;' +
3236
- '}' +
3237
- '.jqhtml-debug-status.active {' +
3238
- 'background: #27ae60 !important;' +
3239
- '}' +
3240
- '@media (max-width: 768px) {' +
3241
- '.jqhtml-debug-controls {' +
3242
- 'flex-direction: column;' +
3243
- 'align-items: flex-start;' +
3244
- '}' +
3245
- '.jqhtml-debug-title {' +
3246
- 'margin-bottom: 5px;' +
3247
- '}' +
3248
- '}');
3249
- this.$('head').append($style);
3250
- }
3251
- toggle() {
3252
- // Toggle between compact and full view
3253
- this.options.compact = !this.options.compact;
3254
- const $toggleBtn = this.$container.find('.jqhtml-debug-toggle');
3255
- $toggleBtn.text(this.options.compact ? '▼' : '▲');
3256
- const $buttons = this.$container.find('.jqhtml-debug-btn');
3257
- if (this.options.compact) {
3258
- $buttons.hide();
3259
3099
  }
3260
- else {
3261
- $buttons.show();
3100
+ // Skip if nested inside another placeholder
3101
+ let parent = $element[0].parentElement;
3102
+ while (parent && parent !== $scope[0]) {
3103
+ if (parent.classList.contains('_Component_Init')) {
3104
+ return;
3105
+ }
3106
+ parent = parent.parentElement;
3262
3107
  }
3263
- }
3264
- executeAction(action) {
3265
- const jqhtml = getJqhtml();
3266
- if (!jqhtml) {
3267
- console.warn('JQHTML not available - make sure it\'s loaded and exposed globally');
3108
+ const component = hydrateElement($element);
3109
+ if (!component)
3268
3110
  return;
3111
+ // Recursively handle deeper nesting
3112
+ component.on('render', function () {
3113
+ hydrateNested(component.$, jQ);
3114
+ });
3115
+ });
3116
+ }
3117
+ /**
3118
+ * Hydrate a single _Component_Init element into a live component.
3119
+ */
3120
+ function hydrateElement($element, jQ) {
3121
+ const componentName = $element.attr('data-component-init-name');
3122
+ if (!componentName)
3123
+ return null;
3124
+ // Parse args
3125
+ const argsString = $element.attr('data-component-args');
3126
+ let args = {};
3127
+ if (argsString) {
3128
+ try {
3129
+ args = JSON.parse(argsString);
3269
3130
  }
3270
- switch (action) {
3271
- case 'enableSlowMotionDebug':
3272
- jqhtml.setDebugSettings({
3273
- logFullLifecycle: true,
3274
- sequentialProcessing: true,
3275
- delayAfterComponent: 150,
3276
- delayAfterRender: 200,
3277
- delayAfterRerender: 250,
3278
- flashComponents: true,
3279
- flashDuration: 800,
3280
- flashColors: {
3281
- create: '#3498db',
3282
- render: '#27ae60',
3283
- ready: '#9b59b6'
3284
- },
3285
- profilePerformance: true,
3286
- highlightSlowRenders: 30,
3287
- logDispatch: true
3288
- });
3289
- this.updateStatus('Slow Motion');
3290
- console.log('🐛 Slow Motion Debug Mode Enabled');
3291
- break;
3292
- case 'enableBasicDebug':
3293
- jqhtml.enableDebugMode('basic');
3294
- this.updateStatus('Basic');
3295
- console.log('🐛 Basic Debug Mode Enabled');
3296
- break;
3297
- case 'enableFullDebug':
3298
- jqhtml.enableDebugMode('full');
3299
- this.updateStatus('Full');
3300
- console.log('🐛 Full Debug Mode Enabled');
3301
- break;
3302
- case 'enableSequentialMode':
3303
- jqhtml.setDebugSettings({
3304
- logCreationReady: true,
3305
- sequentialProcessing: true,
3306
- flashComponents: true,
3307
- profilePerformance: true
3308
- });
3309
- this.updateStatus('Sequential');
3310
- console.log('🐛 Sequential Processing Mode Enabled');
3311
- break;
3312
- case 'clearAllDebug':
3313
- jqhtml.clearDebugSettings();
3314
- this.updateStatus('Off');
3315
- console.log('🐛 All Debug Modes Disabled');
3316
- break;
3317
- case 'showDebugInfo':
3318
- const settings = JSON.stringify(jqhtml.debug, null, 2);
3319
- console.log('🐛 Current Debug Settings:', settings);
3320
- alert('Debug settings logged to console:\n\n' + (Object.keys(jqhtml.debug).length > 0 ? settings : 'No debug settings active'));
3321
- break;
3322
- }
3131
+ catch (e) {
3132
+ console.error(`[jqhtml.boot] Failed to parse args for ${componentName}:`, e);
3133
+ }
3134
+ }
3135
+ // Strip data- prefix from args if present
3136
+ const filteredArgs = {};
3137
+ for (const [key, value] of Object.entries(args)) {
3138
+ filteredArgs[key.startsWith('data-') ? key.substring(5) : key] = value;
3139
+ }
3140
+ // Capture innerHTML for content() and mark as boot-created
3141
+ filteredArgs._inner_html = $element.html();
3142
+ filteredArgs._component_name = componentName;
3143
+ // Cleanup placeholder
3144
+ $element.removeAttr('data-component-init-name');
3145
+ $element.removeAttr('data-component-args');
3146
+ $element.removeData('component-init-name');
3147
+ $element.removeData('component-args');
3148
+ $element.removeClass('_Component_Init');
3149
+ $element.empty();
3150
+ // Create component
3151
+ try {
3152
+ return $element.component(componentName, filteredArgs).component();
3323
3153
  }
3324
- }
3325
- DebugOverlay.instance = null;
3326
- // Simplified global convenience functions that use static methods
3327
- function showDebugOverlay(options) {
3328
- return DebugOverlay.show(options);
3329
- }
3330
- function hideDebugOverlay() {
3331
- DebugOverlay.hide();
3332
- }
3333
- // Auto-initialize if debug query parameter is present
3334
- if (typeof window !== 'undefined') {
3335
- const urlParams = new URLSearchParams(window.location.search);
3336
- if (urlParams.get('debug') === 'true' || urlParams.get('jqhtml-debug') === 'true') {
3337
- document.addEventListener('DOMContentLoaded', () => {
3338
- DebugOverlay.show();
3339
- });
3154
+ catch (error) {
3155
+ console.error(`[jqhtml.boot] Failed to create ${componentName}:`, error);
3156
+ return null;
3340
3157
  }
3341
3158
  }
3342
3159
 
@@ -4238,7 +4055,7 @@ function init(jQuery) {
4238
4055
  }
4239
4056
  }
4240
4057
  // Version - will be replaced during build with actual version from package.json
4241
- const version = '2.2.222';
4058
+ const version = '2.3.6';
4242
4059
  // Default export with all functionality
4243
4060
  const jqhtml = {
4244
4061
  // Core
@@ -4262,6 +4079,8 @@ const jqhtml = {
4262
4079
  escape_html,
4263
4080
  // Version property - internal
4264
4081
  __version: version,
4082
+ // state facts
4083
+ tombstone: 'pepperoni and cheese',
4265
4084
  // Debug settings
4266
4085
  debug: {
4267
4086
  enabled: false,
@@ -4288,15 +4107,6 @@ const jqhtml = {
4288
4107
  clearDebugSettings() {
4289
4108
  this.debug = {};
4290
4109
  },
4291
- // Debug overlay methods
4292
- showDebugOverlay(options) {
4293
- return DebugOverlay.show(options);
4294
- },
4295
- hideDebugOverlay() {
4296
- return DebugOverlay.hide();
4297
- },
4298
- // Export DebugOverlay class for direct access
4299
- DebugOverlay,
4300
4110
  // Install globals function
4301
4111
  installGlobals() {
4302
4112
  if (typeof window !== 'undefined') {
@@ -4330,7 +4140,9 @@ const jqhtml = {
4330
4140
  // Cache key setter - enables component caching via local storage
4331
4141
  set_cache_key(cache_key) {
4332
4142
  Jqhtml_Local_Storage.set_cache_key(cache_key);
4333
- }
4143
+ },
4144
+ // Boot - hydrate server-rendered component placeholders
4145
+ boot
4334
4146
  };
4335
4147
  // Auto-register on window for browser environments
4336
4148
  // This is REQUIRED for compiled templates which use window.jqhtml.register_template()
@@ -4348,5 +4160,5 @@ if (typeof window !== 'undefined' && !window.jqhtml) {
4348
4160
  }
4349
4161
  }
4350
4162
 
4351
- export { DebugOverlay, Jqhtml_Component, LifecycleManager as Jqhtml_LifecycleManager, Jqhtml_Local_Storage, LifecycleManager, Load_Coordinator, applyDebugDelay, 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, hideDebugOverlay, init, init_jquery_plugin, isSequentialProcessing, list_components, logDataChange, logDispatch, logInstruction, logLifecycle, process_instructions, register_component, register_template, render_template, showDebugOverlay, version };
4163
+ 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 };
4352
4164
  //# sourceMappingURL=jqhtml-core.esm.js.map