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