@jqhtml/core 2.3.50 → 2.3.59

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.
Files changed (88) hide show
  1. package/README.md +3 -1
  2. package/dist/boot.d.ts +0 -0
  3. package/dist/boot.d.ts.map +0 -0
  4. package/dist/boot.js +130 -0
  5. package/dist/boot.js.map +1 -0
  6. package/dist/cache-key-serializer.d.ts +47 -0
  7. package/dist/cache-key-serializer.d.ts.map +1 -0
  8. package/dist/cache-key-serializer.js +140 -0
  9. package/dist/cache-key-serializer.js.map +1 -0
  10. package/dist/component-cache.d.ts +1 -0
  11. package/dist/component-cache.d.ts.map +1 -1
  12. package/dist/component-cache.js +270 -0
  13. package/dist/component-cache.js.map +1 -0
  14. package/dist/component-events.d.ts +0 -0
  15. package/dist/component-events.d.ts.map +1 -1
  16. package/dist/component-events.js +141 -0
  17. package/dist/component-events.js.map +1 -0
  18. package/dist/component-queue.d.ts +0 -0
  19. package/dist/component-queue.d.ts.map +0 -0
  20. package/dist/component-queue.js +118 -0
  21. package/dist/component-queue.js.map +1 -0
  22. package/dist/component-registry.d.ts +0 -0
  23. package/dist/component-registry.d.ts.map +0 -0
  24. package/dist/component-registry.js +298 -0
  25. package/dist/component-registry.js.map +1 -0
  26. package/dist/component.d.ts +26 -0
  27. package/dist/component.d.ts.map +1 -1
  28. package/dist/component.js +1892 -0
  29. package/dist/component.js.map +1 -0
  30. package/dist/config.d.ts +57 -0
  31. package/dist/config.d.ts.map +1 -0
  32. package/dist/config.js +64 -0
  33. package/dist/config.js.map +1 -0
  34. package/dist/data-proxy.d.ts +0 -0
  35. package/dist/data-proxy.d.ts.map +0 -0
  36. package/dist/data-proxy.js +223 -0
  37. package/dist/data-proxy.js.map +1 -0
  38. package/dist/debug-entry.d.ts +0 -0
  39. package/dist/debug-entry.d.ts.map +0 -0
  40. package/dist/debug-entry.js +8 -0
  41. package/dist/debug-entry.js.map +1 -0
  42. package/dist/debug.d.ts +0 -0
  43. package/dist/debug.d.ts.map +0 -0
  44. package/dist/debug.js +203 -0
  45. package/dist/debug.js.map +1 -0
  46. package/dist/index.cjs +520 -46
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.d.ts +7 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +519 -47
  51. package/dist/index.js.map +1 -1
  52. package/dist/instruction-processor.d.ts +0 -0
  53. package/dist/instruction-processor.d.ts.map +1 -1
  54. package/dist/instruction-processor.js +563 -0
  55. package/dist/instruction-processor.js.map +1 -0
  56. package/dist/jqhtml-core.esm.js +520 -48
  57. package/dist/jqhtml-core.esm.js.map +1 -1
  58. package/dist/jqhtml-debug.esm.js +0 -0
  59. package/dist/jqhtml-debug.esm.js.map +0 -0
  60. package/dist/jquery-plugin.d.ts +0 -0
  61. package/dist/jquery-plugin.d.ts.map +1 -1
  62. package/dist/jquery-plugin.js +472 -0
  63. package/dist/jquery-plugin.js.map +1 -0
  64. package/dist/lifecycle-manager.d.ts +0 -0
  65. package/dist/lifecycle-manager.d.ts.map +0 -0
  66. package/dist/lifecycle-manager.js +201 -0
  67. package/dist/lifecycle-manager.js.map +1 -0
  68. package/dist/load-coordinator.d.ts +18 -1
  69. package/dist/load-coordinator.d.ts.map +1 -1
  70. package/dist/load-coordinator.js +309 -0
  71. package/dist/load-coordinator.js.map +1 -0
  72. package/dist/local-storage.d.ts +2 -0
  73. package/dist/local-storage.d.ts.map +1 -1
  74. package/dist/local-storage.js +699 -0
  75. package/dist/local-storage.js.map +1 -0
  76. package/dist/preload-data.d.ts +0 -0
  77. package/dist/preload-data.d.ts.map +0 -0
  78. package/dist/preload-data.js +136 -0
  79. package/dist/preload-data.js.map +1 -0
  80. package/dist/template-renderer.d.ts +0 -0
  81. package/dist/template-renderer.d.ts.map +0 -0
  82. package/dist/template-renderer.js +278 -0
  83. package/dist/template-renderer.js.map +1 -0
  84. package/dist/viewport.d.ts +36 -0
  85. package/dist/viewport.d.ts.map +1 -0
  86. package/dist/viewport.js +69 -0
  87. package/dist/viewport.js.map +1 -0
  88. package/package.json +7 -2
package/dist/index.js CHANGED
@@ -496,6 +496,70 @@ function register(source) {
496
496
  'class name mangling, define static component_name or use register_component().');
497
497
  }
498
498
 
499
+ /**
500
+ * JQHTML Runtime Configuration
501
+ *
502
+ * Integration-level settings supplied by the host application (RSpade, Laravel,
503
+ * a bundler entry point) when jqhtml is loaded. Distinct from `jqhtml.debug`,
504
+ * which is a developer's interactive tracing switch: config here describes the
505
+ * ENVIRONMENT the app is running in, and jqhtml derives behaviour from it.
506
+ *
507
+ * jqhtml.init($, { mode: 'production' });
508
+ * jqhtml.configure({ mode: 'development' });
509
+ *
510
+ * Default mode is 'development', so an integration that passes nothing keeps
511
+ * every diagnostic. Production is always opt-in.
512
+ */
513
+ /** Flags implied by each mode. Add future conventions here, not at call sites. */
514
+ const MODE_DEFAULTS = {
515
+ development: {
516
+ warn_uncacheable_args: true,
517
+ debug_attributes: true,
518
+ },
519
+ production: {
520
+ warn_uncacheable_args: false,
521
+ debug_attributes: false,
522
+ },
523
+ };
524
+ let _mode = 'development';
525
+ let _flags = { ...MODE_DEFAULTS.development };
526
+ /**
527
+ * Apply integration configuration.
528
+ *
529
+ * `mode` resets every flag to that mode's defaults; any explicit flag in the
530
+ * same call then overrides it. Calling with only flags leaves the mode alone.
531
+ * Repeated calls merge.
532
+ */
533
+ function configure(config) {
534
+ if (!config)
535
+ return;
536
+ if (config.mode) {
537
+ if (!MODE_DEFAULTS[config.mode]) {
538
+ throw new Error(`[JQHTML] Unknown mode '${config.mode}'. Expected 'development' or 'production'.`);
539
+ }
540
+ _mode = config.mode;
541
+ _flags = { ...MODE_DEFAULTS[config.mode] };
542
+ }
543
+ if (config.warn_uncacheable_args !== undefined) {
544
+ _flags.warn_uncacheable_args = !!config.warn_uncacheable_args;
545
+ }
546
+ if (config.debug_attributes !== undefined) {
547
+ _flags.debug_attributes = !!config.debug_attributes;
548
+ }
549
+ }
550
+ /** Current mode plus every resolved flag. */
551
+ function get_config() {
552
+ return { mode: _mode, ..._flags };
553
+ }
554
+ /** True when debug-only DOM attributes (data-sid) should be emitted. */
555
+ function debug_attributes_enabled() {
556
+ return _flags.debug_attributes;
557
+ }
558
+ /** True when uncacheable-args warnings should be printed. */
559
+ function warn_uncacheable_args_enabled() {
560
+ return _flags.warn_uncacheable_args;
561
+ }
562
+
499
563
  /**
500
564
  * JQHTML v2 Instruction Processor
501
565
  *
@@ -633,6 +697,9 @@ function process_tag_to_html(instruction, html, tagElements, components, context
633
697
  }
634
698
  // Add simple string/numeric attributes directly
635
699
  for (const [key, value] of Object.entries(attrs)) {
700
+ if (key === 'data-sid' && !debug_attributes_enabled()) {
701
+ continue; // debug-only attribute; the scoped id is what $sid() resolves
702
+ }
636
703
  if (!key.startsWith('$') && !key.startsWith('on') && !key.startsWith('@') &&
637
704
  !key.startsWith('data-bind-') && !key.startsWith('data-__-on-') &&
638
705
  (typeof value === 'string' || typeof value === 'number')) {
@@ -722,7 +789,10 @@ function process_component_to_html(instruction, html, components, context) {
722
789
  const baseId = props['data-sid'];
723
790
  // The compiled code ALWAYS sets props['id'] with the correct scoped value
724
791
  // Just use it directly - it already has the correct parent _cid baked in
725
- html.push(` id="${props['id']}" data-sid="${baseId}"`);
792
+ // data-sid is debug-only: $sid() resolves via the scoped id, never this.
793
+ html.push(debug_attributes_enabled()
794
+ ? ` id="${props['id']}" data-sid="${baseId}"`
795
+ : ` id="${props['id']}"`);
726
796
  }
727
797
  // Regular id passes through unchanged
728
798
  else if (props['id']) {
@@ -786,11 +856,22 @@ function process_rawtag_to_html(instruction, html) {
786
856
  }
787
857
  }
788
858
  html.push('>');
789
- // Add raw content - escape HTML entities but preserve whitespace
790
- const escaped_content = rawContent
791
- .replace(/&/g, '&')
792
- .replace(/</g, '&lt;')
793
- .replace(/>/g, '&gt;');
859
+ // Add raw content, preserving whitespace.
860
+ //
861
+ // <textarea> holds text and nothing else, so everything is escaped.
862
+ //
863
+ // <pre> holds phrasing content - <pre><code> is the standard code-block idiom - so
864
+ // markup the author wrote is passed through. A '<' that does not begin a tag is
865
+ // still escaped, which is what the HTML tokenizer itself does with a stray '<' and
866
+ // keeps code samples such as "if (a < b)" rendering as written. Interpolated
867
+ // values are escaped by the compiler before they reach this point.
868
+ const is_raw_html_content = tagName.toLowerCase() === 'pre';
869
+ const escaped_content = is_raw_html_content
870
+ ? rawContent.replace(/<(?![a-zA-Z/!?])/g, '&lt;')
871
+ : rawContent
872
+ .replace(/&/g, '&amp;')
873
+ .replace(/</g, '&lt;')
874
+ .replace(/>/g, '&gt;');
794
875
  html.push(escaped_content);
795
876
  // Close tag
796
877
  html.push(`</${tagName}>`);
@@ -864,6 +945,10 @@ function apply_attributes(element, attrs, context) {
864
945
  }
865
946
  }
866
947
  else if (key.startsWith('data-')) {
948
+ // data-sid is debug-only - suppressed entirely in production
949
+ if (key === 'data-sid' && !debug_attributes_enabled()) {
950
+ continue;
951
+ }
867
952
  // Data attributes - go into DOM
868
953
  const attrValue = typeof value === "string" ? value.trim() : value;
869
954
  element.attr(key, attrValue);
@@ -1226,6 +1311,146 @@ function handleComponentError(component, phase, error) {
1226
1311
  // 9. Performance Budgets - Warn when components exceed size/time limits
1227
1312
  // 10. Accessibility Auditing - Check for missing ARIA attributes
1228
1313
 
1314
+ /**
1315
+ * JQHTML Cache Key Serializer
1316
+ *
1317
+ * Produces a deterministic string for a plain-data argument value, so that two
1318
+ * structurally equal objects built on separate renders yield the SAME cache key.
1319
+ * Templates rebuild `{contact_id: 12}` on every render, so identity-based keying
1320
+ * can never match and permanently opts the receiving component out of caching.
1321
+ *
1322
+ * ---------------------------------------------------------------------------
1323
+ * THIS IS NOT `process_for_serialization()` IN local-storage.ts. DO NOT MERGE THEM.
1324
+ *
1325
+ * That serializer stores cache VALUES and deliberately degrades: a function, a
1326
+ * symbol or a circular reference is dropped and serialization continues, because
1327
+ * losing a method while keeping the data is the right trade for a payload.
1328
+ *
1329
+ * For a cache KEY that policy manufactures false hits. Two argument objects that
1330
+ * differ ONLY by a callback would serialize identically, and a component would
1331
+ * render with another component's cached content - strictly worse than not
1332
+ * caching at all. So this serializer DECLINES on the first value it cannot
1333
+ * express exactly. It never drops anything.
1334
+ * ---------------------------------------------------------------------------
1335
+ *
1336
+ * Allow-list, and nothing else: null, undefined, boolean, number, string, Date,
1337
+ * arrays, and plain objects (prototype Object.prototype or null), in any nesting.
1338
+ * Everything else declines with a reason that reaches `data-nocache`.
1339
+ */
1340
+ /**
1341
+ * Values larger than this are declined rather than keyed. The supported case is a
1342
+ * handful of key/value pairs or a list of ids - not a payload. Declining loudly
1343
+ * beats spending the budget on a key nobody benefits from.
1344
+ */
1345
+ const MAX_KEY_BYTES = 500;
1346
+ class Decline extends Error {
1347
+ constructor(reason) {
1348
+ super(reason);
1349
+ this.reason = reason;
1350
+ }
1351
+ }
1352
+ function push(state, text) {
1353
+ state.size += text.length;
1354
+ if (state.size > MAX_KEY_BYTES) {
1355
+ throw new Decline('too-large');
1356
+ }
1357
+ return text;
1358
+ }
1359
+ /**
1360
+ * Length-prefix strings so that no combination of separators can make two
1361
+ * different structures produce the same text.
1362
+ */
1363
+ function encode_string(state, prefix, value) {
1364
+ return push(state, `${prefix}${value.length}:${value}`);
1365
+ }
1366
+ function walk(value, state) {
1367
+ // --- primitives, each tagged so shapes cannot collide ---
1368
+ if (value === null)
1369
+ return push(state, 'n');
1370
+ if (value === undefined)
1371
+ return push(state, 'u');
1372
+ const type = typeof value;
1373
+ if (type === 'boolean')
1374
+ return push(state, value ? 'b1' : 'b0');
1375
+ if (type === 'number')
1376
+ return push(state, `d${String(value)}`);
1377
+ if (type === 'string')
1378
+ return encode_string(state, 's', value);
1379
+ if (type === 'function')
1380
+ throw new Decline('function');
1381
+ if (type === 'symbol')
1382
+ throw new Decline('symbol');
1383
+ if (type === 'bigint')
1384
+ throw new Decline('bigint');
1385
+ if (type !== 'object')
1386
+ throw new Decline(type);
1387
+ // --- objects: reject the impostors before treating anything as data ---
1388
+ if (typeof value.nodeType === 'number')
1389
+ throw new Decline('dom-node');
1390
+ if (value.jquery)
1391
+ throw new Decline('jquery');
1392
+ if (state.seen.has(value))
1393
+ throw new Decline('circular');
1394
+ state.seen.add(value);
1395
+ try {
1396
+ if (value instanceof Date) {
1397
+ const time = value.getTime();
1398
+ if (Number.isNaN(time))
1399
+ throw new Decline('invalid-date');
1400
+ return push(state, `t${value.toISOString()}`);
1401
+ }
1402
+ if (Array.isArray(value)) {
1403
+ // Order is meaningful - never sorted.
1404
+ const parts = [];
1405
+ push(state, 'a[');
1406
+ for (let i = 0; i < value.length; i++) {
1407
+ parts.push(walk(value[i], state));
1408
+ }
1409
+ push(state, ']');
1410
+ return `a[${parts.join(',')}]`;
1411
+ }
1412
+ // Plain object ONLY. A class instance, Map, Set, RegExp, Error, etc. is
1413
+ // declined: two different classes with identical fields would otherwise
1414
+ // serialize the same and collide.
1415
+ const proto = Object.getPrototypeOf(value);
1416
+ if (proto !== Object.prototype && proto !== null) {
1417
+ throw new Decline('non-plain-object');
1418
+ }
1419
+ // Recursive key sort - the existing top-level sort is not enough for nesting.
1420
+ const keys = Object.keys(value).sort();
1421
+ const parts = [];
1422
+ push(state, 'o{');
1423
+ for (const key of keys) {
1424
+ const encoded_key = encode_string(state, 'k', key);
1425
+ parts.push(`${encoded_key}=${walk(value[key], state)}`);
1426
+ }
1427
+ push(state, '}');
1428
+ return `o{${parts.join(',')}}`;
1429
+ }
1430
+ finally {
1431
+ state.seen.delete(value);
1432
+ }
1433
+ }
1434
+ /**
1435
+ * Serialize a value for use in a cache key.
1436
+ *
1437
+ * @returns `{ok: true, text}` when the value is exactly expressible, or
1438
+ * `{ok: false, reason}` naming why it is not. Never throws.
1439
+ */
1440
+ function serialize_for_cache_key(value) {
1441
+ const state = { seen: new WeakSet(), size: 0 };
1442
+ try {
1443
+ return { ok: true, text: walk(value, state) };
1444
+ }
1445
+ catch (error) {
1446
+ if (error instanceof Decline) {
1447
+ return { ok: false, reason: error.reason };
1448
+ }
1449
+ // A throwing getter or exotic proxy trap - decline rather than guess.
1450
+ return { ok: false, reason: 'unserializable' };
1451
+ }
1452
+ }
1453
+
1229
1454
  /**
1230
1455
  * Load Coordinator - Request deduplication for component on_load() calls
1231
1456
  *
@@ -1262,8 +1487,9 @@ class Load_Coordinator {
1262
1487
  * - key: Cache key string, or null if uncacheable
1263
1488
  * - uncacheable_property: Name of first property that prevented caching (for debugging)
1264
1489
  */
1265
- static generate_invocation_key(component_name, args) {
1490
+ static generate_invocation_key(component_name, args, options = {}) {
1266
1491
  let uncacheable_property;
1492
+ let uncacheable_reason;
1267
1493
  // Filter out internal properties (starting with _) and serialize args
1268
1494
  const serializable_args = {};
1269
1495
  for (const key of Object.keys(args).sort()) {
@@ -1302,21 +1528,42 @@ class Load_Coordinator {
1302
1528
  // Method threw error - treat as uncacheable
1303
1529
  if (!uncacheable_property) {
1304
1530
  uncacheable_property = key;
1531
+ uncacheable_reason = 'cache-id-threw';
1305
1532
  }
1306
- return { key: null, uncacheable_property };
1533
+ return { key: null, uncacheable_property, uncacheable_reason };
1307
1534
  }
1308
1535
  }
1309
- // No cache ID available - this property is uncacheable
1536
+ // No author-supplied id. The cache may fall back to keying by
1537
+ // deterministic content; deduplication deliberately may not.
1538
+ if (options.allow_content_serialization) {
1539
+ const serialized = serialize_for_cache_key(value);
1540
+ if (serialized.ok) {
1541
+ serializable_args[key] = `__JQHTML_CONTENT__:${serialized.text}`;
1542
+ continue;
1543
+ }
1544
+ if (!uncacheable_property) {
1545
+ uncacheable_property = key;
1546
+ // This package compiles with strictNullChecks off, and without it
1547
+ // TypeScript will not narrow a discriminated union by its boolean
1548
+ // discriminant - so `serialized` is still the full Serialize_Result
1549
+ // here even though `ok` has been ruled out above. The cast states
1550
+ // what the control flow already guarantees.
1551
+ uncacheable_reason = serialized.reason;
1552
+ }
1553
+ return { key: null, uncacheable_property, uncacheable_reason };
1554
+ }
1310
1555
  if (!uncacheable_property) {
1311
1556
  uncacheable_property = key;
1557
+ uncacheable_reason = value_type === 'function' ? 'function' : 'object';
1312
1558
  }
1313
- return { key: null, uncacheable_property };
1559
+ return { key: null, uncacheable_property, uncacheable_reason };
1314
1560
  }
1315
1561
  // Unknown type (symbol, bigint, etc.) - uncacheable
1316
1562
  if (!uncacheable_property) {
1317
1563
  uncacheable_property = key;
1564
+ uncacheable_reason = value_type;
1318
1565
  }
1319
- return { key: null, uncacheable_property };
1566
+ return { key: null, uncacheable_property, uncacheable_reason };
1320
1567
  }
1321
1568
  // Try to serialize - if it fails (shouldn't happen now, but safety net), return null
1322
1569
  try {
@@ -1334,6 +1581,12 @@ class Load_Coordinator {
1334
1581
  */
1335
1582
  static should_execute_on_load(component) {
1336
1583
  const { key } = this.generate_invocation_key(component.component_name(), component.args);
1584
+ // No identity means no coordination. Without this, every un-keyable component
1585
+ // would share the single `null` registry entry and adopt an unrelated
1586
+ // component's data - the worst failure this system can produce.
1587
+ if (key === null) {
1588
+ return true;
1589
+ }
1337
1590
  const entry = this._registry.get(key);
1338
1591
  if (!entry) {
1339
1592
  // No existing request - this component becomes the leader
@@ -1356,6 +1609,10 @@ class Load_Coordinator {
1356
1609
  */
1357
1610
  static register_leader(component, on_load_promise) {
1358
1611
  const { key } = this.generate_invocation_key(component.component_name(), component.args);
1612
+ // Un-keyable component: run alone, register nothing. See should_execute_on_load().
1613
+ if (key === null) {
1614
+ return () => { };
1615
+ }
1359
1616
  // Create a promise that we control - it resolves when complete_coordination is called
1360
1617
  let resolve_promise;
1361
1618
  const coordination_promise = new Promise((resolve) => {
@@ -1577,6 +1834,8 @@ Load_Coordinator._registry = new Map();
1577
1834
  // Registry mapping class names to constructors
1578
1835
  const class_registry = Object.create(null);
1579
1836
  // Markers for serialized class instances (unique to avoid collisions with user data)
1837
+ // Replaced at build time by rollup (see rollup.config.js).
1838
+ const CORE_VERSION = '2.3.59';
1580
1839
  const CLASS_MARKER = '__jqhtml_class__';
1581
1840
  const PROPS_MARKER = '__jqhtml_props__';
1582
1841
  /**
@@ -1866,6 +2125,12 @@ class Jqhtml_Local_Storage {
1866
2125
  */
1867
2126
  static set_cache_key(cache_key, cache_mode = 'data') {
1868
2127
  this._cache_key = cache_key;
2128
+ // Key SHAPE is a function of the library (see cache-key-serializer.ts), so an
2129
+ // upgrade that changes it must invalidate everything persisted under the old shape.
2130
+ // Version the SCOPE MARKER rather than the key itself: _validate_scope() already
2131
+ // clears every jqhtml key when this value changes, and leaving the per-entry key
2132
+ // format alone keeps stored keys readable and avoids repeating the version in each.
2133
+ this._scope_marker = `${CORE_VERSION}::${cache_key}`;
1869
2134
  this._cache_mode = cache_mode;
1870
2135
  this._init();
1871
2136
  }
@@ -1935,21 +2200,21 @@ class Jqhtml_Local_Storage {
1935
2200
  }
1936
2201
  try {
1937
2202
  const stored_key = localStorage.getItem('_jqhtml_cache_key');
1938
- // If cache key exists and has changed, clear only JQHTML keys
1939
- if (stored_key !== null && stored_key !== this._cache_key) {
1940
- console.log('[JQHTML Local Storage] Cache key changed, clearing JQHTML keys:', {
2203
+ // If the scope marker (core version + developer key) changed, clear only JQHTML keys
2204
+ if (stored_key !== null && stored_key !== this._scope_marker) {
2205
+ console.log('[JQHTML Local Storage] Cache scope changed, clearing JQHTML keys:', {
1941
2206
  old_key: stored_key,
1942
- new_key: this._cache_key,
2207
+ new_key: this._scope_marker,
1943
2208
  });
1944
2209
  this._clear_jqhtml_keys();
1945
- localStorage.setItem('_jqhtml_cache_key', this._cache_key);
2210
+ localStorage.setItem('_jqhtml_cache_key', this._scope_marker);
1946
2211
  }
1947
2212
  else if (stored_key === null) {
1948
2213
  // First time JQHTML is using this storage - just set the key, don't clear
1949
- console.log('[JQHTML Local Storage] Initializing cache key (first use):', {
1950
- new_key: this._cache_key,
2214
+ console.log('[JQHTML Local Storage] Initializing cache scope (first use):', {
2215
+ new_key: this._scope_marker,
1951
2216
  });
1952
- localStorage.setItem('_jqhtml_cache_key', this._cache_key);
2217
+ localStorage.setItem('_jqhtml_cache_key', this._scope_marker);
1953
2218
  }
1954
2219
  }
1955
2220
  catch (e) {
@@ -2181,6 +2446,8 @@ class Jqhtml_Local_Storage {
2181
2446
  }
2182
2447
  }
2183
2448
  Jqhtml_Local_Storage._cache_key = null;
2449
+ /** Versioned scope identity - compared in _validate_scope() to clear on a core upgrade. */
2450
+ Jqhtml_Local_Storage._scope_marker = null;
2184
2451
  Jqhtml_Local_Storage._cache_mode = 'data';
2185
2452
  Jqhtml_Local_Storage._storage_available = null;
2186
2453
  Jqhtml_Local_Storage._initialized = false;
@@ -2278,10 +2545,15 @@ function event_once(component, event_name, callback) {
2278
2545
  function event_trigger(component, event_name, data) {
2279
2546
  // Mark this event as occurred and store the data for late subscribers
2280
2547
  component._lifecycle_states.set(event_name, data);
2281
- // Fire all registered callbacks for this event
2548
+ // Fire all registered callbacks for this event.
2549
+ // Iterate a SNAPSHOT: handlers registered via once() splice themselves out of the
2550
+ // live array while it is being iterated, which would shift later handlers into
2551
+ // already-visited slots and skip them. The snapshot also means a handler
2552
+ // registered during dispatch does not fire in this dispatch (event_on's sticky
2553
+ // immediate-fire already delivered it).
2282
2554
  const callbacks = component._lifecycle_callbacks.get(event_name);
2283
2555
  if (callbacks) {
2284
- for (const callback of callbacks) {
2556
+ for (const callback of [...callbacks]) {
2285
2557
  try {
2286
2558
  callback.bind(component)(component, data);
2287
2559
  }
@@ -2566,12 +2838,73 @@ function generate_cache_key(component) {
2566
2838
  return { cache_key: `${component.component_name()}::${String(custom_cache_id)}` };
2567
2839
  }
2568
2840
  catch (error) {
2569
- return { cache_key: null, uncacheable_property: 'cache_id()' };
2841
+ return { cache_key: null, uncacheable_property: 'cache_id()', uncacheable_reason: 'cache-id-threw' };
2570
2842
  }
2571
2843
  }
2572
- // Use standard args-based cache key generation
2573
- const result = Load_Coordinator.generate_invocation_key(component.component_name(), component.args);
2574
- return { cache_key: result.key, uncacheable_property: result.uncacheable_property };
2844
+ // Standard args-based key. The CACHE opts into content serialization for plain-data
2845
+ // object args; deduplication does not - see InvocationKeyOptions for why.
2846
+ const result = Load_Coordinator.generate_invocation_key(component.component_name(), component.args, { allow_content_serialization: true });
2847
+ return {
2848
+ cache_key: result.key,
2849
+ uncacheable_property: result.uncacheable_property,
2850
+ uncacheable_reason: result.uncacheable_reason,
2851
+ };
2852
+ }
2853
+ // One warning per component+property. A list of 50 rows must not print 50 warnings.
2854
+ const _warned_uncacheable = new Set();
2855
+ /**
2856
+ * Development warning: this component fetches data but cannot be cached, because
2857
+ * its args do not reduce to a stable key and it defines no cache_id() to say what
2858
+ * its identity is. Nothing is broken - the component works - but it silently opts
2859
+ * out of cache reuse and of load deduplication.
2860
+ *
2861
+ * Controlled by the `warn_uncacheable_args` integration flag (see config.ts).
2862
+ */
2863
+ function warn_uncacheable_component(component, uncacheable_property, uncacheable_reason) {
2864
+ if (!warn_uncacheable_args_enabled())
2865
+ return;
2866
+ let name = 'unknown';
2867
+ try {
2868
+ name = component.component_name();
2869
+ }
2870
+ catch { /* name unavailable */ }
2871
+ const dedupe_key = `${name}::${uncacheable_property || '?'}::${uncacheable_reason || '?'}`;
2872
+ if (_warned_uncacheable.has(dedupe_key))
2873
+ return;
2874
+ _warned_uncacheable.add(dedupe_key);
2875
+ // cache_id() exists but threw - a different problem with a different fix
2876
+ if (uncacheable_property === 'cache_id()' || uncacheable_reason === 'cache-id-threw') {
2877
+ console.warn(`[JQHTML] <${name}> defines cache_id() but it threw, so this component is not cached ` +
2878
+ `and its loads are not deduplicated. Fix cache_id() so it returns a stable string.`, { component: name, cid: component._cid });
2879
+ return;
2880
+ }
2881
+ const REASONS = {
2882
+ 'function': 'it contains a function - a callback is real identity that content cannot express',
2883
+ 'symbol': 'it contains a symbol',
2884
+ 'bigint': 'it contains a bigint',
2885
+ 'dom-node': 'it contains a DOM node',
2886
+ 'jquery': 'it contains a jQuery object',
2887
+ 'circular': 'it contains a circular reference',
2888
+ 'non-plain-object': 'it contains a class instance, Map, Set or similar - only plain objects and arrays can be keyed by content',
2889
+ 'too-large': 'it serializes to more than 500 bytes - args are meant to identify data, not carry it',
2890
+ 'invalid-date': 'it contains an invalid Date',
2891
+ };
2892
+ const why = REASONS[uncacheable_reason || ''] || 'it cannot be serialized deterministically';
2893
+ console.warn(`[JQHTML] <${name}> cannot be cached: arg $${uncacheable_property} is not keyable because ` +
2894
+ `${why}.\n` +
2895
+ ` The component works, but it will not be restored from cache when reused.\n` +
2896
+ ` Fix: define cache_id() on the component to state its identity explicitly, e.g.\n` +
2897
+ ` cache_id() { return \`${name.toLowerCase()}_\${this.args.some_id}\`; }\n` +
2898
+ ` Or pass plain data (objects/arrays of primitives are keyed automatically) instead of ` +
2899
+ `$${uncacheable_property}.\n` +
2900
+ ` Find it in DevTools with the selector ` +
2901
+ `[data-nocache="${uncacheable_property}:${uncacheable_reason || ''}"].`, {
2902
+ component: name,
2903
+ cid: component._cid,
2904
+ uncacheable_arg: uncacheable_property,
2905
+ reason: uncacheable_reason,
2906
+ all_args: component.args ? Object.keys(component.args) : [],
2907
+ });
2575
2908
  }
2576
2909
  /**
2577
2910
  * Read cache during create() phase.
@@ -2580,13 +2913,15 @@ function generate_cache_key(component) {
2580
2913
  * @param component - The component instance
2581
2914
  */
2582
2915
  function read_cache_in_create(component) {
2583
- const { cache_key, uncacheable_property } = generate_cache_key(component);
2916
+ const { cache_key, uncacheable_property, uncacheable_reason } = generate_cache_key(component);
2584
2917
  // If cache_key is null, caching disabled
2585
2918
  if (cache_key === null) {
2586
2919
  // Set data-nocache attribute for debugging
2587
2920
  if (uncacheable_property) {
2588
- component.$.attr('data-nocache', uncacheable_property);
2921
+ // "<arg>:<reason>" - a bare property name left authors reverse-engineering why
2922
+ component.$.attr('data-nocache', uncacheable_reason ? `${uncacheable_property}:${uncacheable_reason}` : uncacheable_property);
2589
2923
  }
2924
+ warn_uncacheable_component(component, uncacheable_property, uncacheable_reason);
2590
2925
  if (window.jqhtml?.debug?.verbose) {
2591
2926
  console.log(`[Cache] Component ${component._cid} (${component.component_name()}) has non-serializable args - caching disabled`, { uncacheable_property });
2592
2927
  }
@@ -3000,6 +3335,75 @@ function has_preload_data() {
3000
3335
  return _preload_cache.size > 0;
3001
3336
  }
3002
3337
 
3338
+ /**
3339
+ * JQHTML Viewport Resize Dispatch
3340
+ *
3341
+ * One framework-owned, debounced window 'resize' listener fans out to every live
3342
+ * component in the document by calling on_viewport_resize(viewport_width).
3343
+ *
3344
+ * Components never bind their own window listener, so there is nothing to unbind
3345
+ * in on_stop() - a component that is stopped or removed from the document simply
3346
+ * stops being found by the dispatcher.
3347
+ */
3348
+ /** Trailing-edge debounce applied to the window resize listener. */
3349
+ const VIEWPORT_RESIZE_DEBOUNCE_MS = 30;
3350
+ let debounce_timer = null;
3351
+ let listener_installed = false;
3352
+ /**
3353
+ * Current viewport width in CSS pixels.
3354
+ *
3355
+ * window.innerWidth is used (not $(window).width()) because it matches the width
3356
+ * CSS media queries resolve against. It includes the scrollbar gutter.
3357
+ *
3358
+ * Returns 0 outside a browser (SSR).
3359
+ */
3360
+ function get_viewport_width() {
3361
+ return typeof window !== 'undefined' ? window.innerWidth : 0;
3362
+ }
3363
+ /**
3364
+ * Call on_viewport_resize(width) on every component currently in the document.
3365
+ *
3366
+ * Walks $('.Component') in document order (parents before children). Elements
3367
+ * with no component instance and stopped components are skipped. A component
3368
+ * that throws is logged and the walk continues - one bad handler must not stop
3369
+ * the rest of the page from resizing.
3370
+ */
3371
+ function dispatch_viewport_resize(jQuery) {
3372
+ const viewport_width = get_viewport_width();
3373
+ jQuery('.Component').each(function () {
3374
+ const component = jQuery(this).data('_component');
3375
+ if (!component || component._stopped)
3376
+ return;
3377
+ if (typeof component.on_viewport_resize !== 'function')
3378
+ return;
3379
+ try {
3380
+ component.on_viewport_resize(viewport_width);
3381
+ }
3382
+ catch (error) {
3383
+ console.error(`[JQHTML] Error in on_viewport_resize() for ${component.component_name?.() || 'component'}:`, error);
3384
+ }
3385
+ });
3386
+ }
3387
+ /**
3388
+ * Install the single window resize listener. Idempotent - repeated init() calls
3389
+ * and non-browser environments are no-ops.
3390
+ */
3391
+ function init_viewport_resize(jQuery) {
3392
+ if (typeof window === 'undefined')
3393
+ return;
3394
+ if (listener_installed)
3395
+ return;
3396
+ listener_installed = true;
3397
+ jQuery(window).on('resize.jqhtml_viewport', function () {
3398
+ if (debounce_timer !== null)
3399
+ clearTimeout(debounce_timer);
3400
+ debounce_timer = setTimeout(function () {
3401
+ debounce_timer = null;
3402
+ dispatch_viewport_resize(jQuery);
3403
+ }, VIEWPORT_RESIZE_DEBOUNCE_MS);
3404
+ });
3405
+ }
3406
+
3003
3407
  /**
3004
3408
  * JQHTML v2 Component Base Class
3005
3409
  *
@@ -3194,6 +3598,21 @@ class Jqhtml_Component {
3194
3598
  // Call original directly - no flag needed since we bypass the wrapper
3195
3599
  return impl.call(this);
3196
3600
  }
3601
+ /**
3602
+ * Fire on_viewport_resize() with the current viewport width.
3603
+ * Called after every on_render() and on_ready(); the debounced window
3604
+ * listener in viewport.ts drives it from then on. Errors are isolated so a
3605
+ * bad handler cannot break the lifecycle.
3606
+ * @private
3607
+ */
3608
+ _fire_viewport_resize() {
3609
+ try {
3610
+ this.on_viewport_resize(get_viewport_width());
3611
+ }
3612
+ catch (error) {
3613
+ console.error(`[JQHTML] Error in on_viewport_resize() for ${this.component_name()}:`, error);
3614
+ }
3615
+ }
3197
3616
  /**
3198
3617
  * Check if on_load() is overridden in a subclass
3199
3618
  * Used to skip the load phase entirely for components that don't fetch data
@@ -3278,6 +3697,7 @@ class Jqhtml_Component {
3278
3697
  console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_render(). ` +
3279
3698
  `on_render() must be synchronous code. Remove 'async' from the function declaration.`);
3280
3699
  }
3700
+ this._fire_viewport_resize();
3281
3701
  }
3282
3702
  // Emit lifecycle event
3283
3703
  this.trigger('render');
@@ -3469,6 +3889,7 @@ class Jqhtml_Component {
3469
3889
  console.warn(`[JQHTML] Component "${this.component_name()}" returned a Promise from on_render(). ` +
3470
3890
  `on_render() must be synchronous code. Remove 'async' from the function declaration.`);
3471
3891
  }
3892
+ this._fire_viewport_resize();
3472
3893
  }
3473
3894
  // Emit lifecycle event
3474
3895
  this.trigger('render');
@@ -3543,6 +3964,7 @@ class Jqhtml_Component {
3543
3964
  }
3544
3965
  // Call on_ready hook with authorization
3545
3966
  await this._call_lifecycle('on_ready');
3967
+ this._fire_viewport_resize();
3546
3968
  // Trigger ready event
3547
3969
  this.trigger('ready');
3548
3970
  });
@@ -3769,9 +4191,18 @@ class Jqhtml_Component {
3769
4191
  }
3770
4192
  return;
3771
4193
  }
3772
- // Generate cache key (needed for both preload check and load deduplication)
4194
+ // TWO identities, deliberately computed separately.
4195
+ //
4196
+ // cache_key - may key a plain-data object arg by deterministic CONTENT, and may come
4197
+ // from the author's cache_id(). A wrong key here is corrected by
4198
+ // stale-while-revalidate.
4199
+ // dedup_key - args only, no content serialization, no cache_id(). A deduplicated
4200
+ // follower never runs on_load() and adopts the leader's data with NO
4201
+ // revalidation, so a wrong key here is permanently wrong data. Redundant
4202
+ // concurrent requests are the cheaper failure.
3773
4203
  let cache_key = null;
3774
4204
  let uncacheable_property;
4205
+ let uncacheable_reason;
3775
4206
  if (typeof this.cache_id === 'function') {
3776
4207
  try {
3777
4208
  const custom_cache_id = this.cache_id();
@@ -3780,14 +4211,16 @@ class Jqhtml_Component {
3780
4211
  catch (error) {
3781
4212
  // cache_id() threw error - disable caching
3782
4213
  uncacheable_property = 'cache_id()';
4214
+ uncacheable_reason = 'cache-id-threw';
3783
4215
  }
3784
4216
  }
3785
4217
  else {
3786
- // Use standard args-based cache key generation
3787
- const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args);
4218
+ const result = Load_Coordinator.generate_invocation_key(this.component_name(), this.args, { allow_content_serialization: true });
3788
4219
  cache_key = result.key;
3789
4220
  uncacheable_property = result.uncacheable_property;
4221
+ uncacheable_reason = result.uncacheable_reason;
3790
4222
  }
4223
+ const dedup_key = Load_Coordinator.generate_invocation_key(this.component_name(), this.args).key;
3791
4224
  // SSR preload check: if preloaded data exists for this component+args, use it
3792
4225
  if (cache_key !== null && has_preload_data()) {
3793
4226
  const preloaded_data = consume_preload_data(cache_key);
@@ -3803,23 +4236,24 @@ class Jqhtml_Component {
3803
4236
  }
3804
4237
  // Store cache key for later use
3805
4238
  this._cache_key = cache_key;
3806
- // If cache_key is null, args are not serializable - skip load deduplication and caching
3807
- if (cache_key === null) {
3808
- // Set data-nocache attribute for debugging (shows which property prevented caching)
3809
- if (uncacheable_property) {
3810
- this.$.attr('data-nocache', uncacheable_property);
4239
+ // Caching disabled - mark the element with the offending arg AND why
4240
+ if (cache_key === null && uncacheable_property) {
4241
+ this.$.attr('data-nocache', uncacheable_reason ? `${uncacheable_property}:${uncacheable_reason}` : uncacheable_property);
4242
+ if (window.jqhtml?.debug?.verbose) {
4243
+ console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-keyable args - caching disabled`, { uncacheable_property, uncacheable_reason });
3811
4244
  }
4245
+ }
4246
+ // Store "before" snapshot for comparison after on_load()
4247
+ const data_before_load = JSON.stringify(this.data);
4248
+ // No dedup identity - run on_load() alone rather than risk sharing a load
4249
+ if (dedup_key === null) {
3812
4250
  if (window.jqhtml?.debug?.verbose) {
3813
- console.log(`[Cache] Component ${this._cid} (${this.component_name()}) has non-serializable args - load deduplication and caching disabled`, { uncacheable_property });
4251
+ console.log(`[Load Deduplication] Component ${this._cid} (${this.component_name()}) has no dedup key - loading independently`);
3814
4252
  }
3815
- // Execute on_load on detached proxy without deduplication
3816
4253
  const { data: result_data } = await this._execute_on_load_detached();
3817
- // Apply result via sequential queue
3818
- await this._apply_load_result(result_data, null);
4254
+ await this._apply_load_result(result_data, data_before_load);
3819
4255
  return;
3820
4256
  }
3821
- // Store "before" snapshot for comparison after on_load()
3822
- const data_before_load = JSON.stringify(this.data);
3823
4257
  // Check if this component should execute on_load() or wait for existing request
3824
4258
  const should_execute = Load_Coordinator.should_execute_on_load(this);
3825
4259
  if (!should_execute) {
@@ -3968,6 +4402,7 @@ class Jqhtml_Component {
3968
4402
  // Wait for all children to reach ready state (bottom-up execution)
3969
4403
  await this._wait_for_children_ready();
3970
4404
  await this._call_lifecycle('on_ready');
4405
+ this._fire_viewport_resize();
3971
4406
  this._ready_state = 4;
3972
4407
  this._update_debug_attrs();
3973
4408
  this._log_lifecycle('ready', 'complete');
@@ -4212,6 +4647,7 @@ class Jqhtml_Component {
4212
4647
  this._render();
4213
4648
  await this._wait_for_children_ready();
4214
4649
  await this._call_lifecycle('on_ready');
4650
+ this._fire_viewport_resize();
4215
4651
  this.trigger('ready');
4216
4652
  this._log_lifecycle('reload', 'complete (no on_load)');
4217
4653
  return;
@@ -4286,6 +4722,7 @@ class Jqhtml_Component {
4286
4722
  if (rendered_from_cache || should_render) {
4287
4723
  await this._wait_for_children_ready();
4288
4724
  await this._call_lifecycle('on_ready');
4725
+ this._fire_viewport_resize();
4289
4726
  // Trigger ready event so parent callbacks fire on subsequent reloads
4290
4727
  this.trigger('ready');
4291
4728
  }
@@ -4368,6 +4805,16 @@ class Jqhtml_Component {
4368
4805
  on_loaded() { } // Override to process loaded data (e.g., clone this.data to this.state)
4369
4806
  async on_ready() { }
4370
4807
  on_stop() { }
4808
+ /**
4809
+ * Called with the viewport width in CSS pixels:
4810
+ * - after every on_render()
4811
+ * - after every on_ready()
4812
+ * - on window resize, debounced 30ms, for every component in the document
4813
+ *
4814
+ * MUST be synchronous. Prefer CSS media/container queries for pure styling -
4815
+ * override this only when layout requires imperative measurement.
4816
+ */
4817
+ on_viewport_resize(viewport_width) { }
4371
4818
  /**
4372
4819
  * Should component re-render after load?
4373
4820
  * By default, only re-renders if data has changed
@@ -4522,11 +4969,21 @@ class Jqhtml_Component {
4522
4969
  }
4523
4970
  /**
4524
4971
  * Find closest ancestor component matching selector
4972
+ *
4973
+ * A bare component name - a plain identifier starting with a capital letter,
4974
+ * such as 'Parent_Dashboard' - is treated as the component-name class
4975
+ * selector '.Parent_Dashboard'. Components carry a CSS class for every name
4976
+ * in their prototype chain, so a base class name matches its subclasses too.
4977
+ * Every other selector ('.Foo', '#id', '[attr]', 'div > .Component') is
4978
+ * passed to jQuery unchanged.
4525
4979
  */
4526
4980
  closest(selector) {
4981
+ const search = Jqhtml_Component.COMPONENT_NAME_PATTERN.test(selector)
4982
+ ? `.${selector}`
4983
+ : selector;
4527
4984
  let current = this.$.parent();
4528
4985
  while (current.length > 0) {
4529
- if (current.is(selector)) {
4986
+ if (current.is(search)) {
4530
4987
  const comp = current.data('_component');
4531
4988
  if (comp instanceof Jqhtml_Component) {
4532
4989
  return comp;
@@ -4742,8 +5199,13 @@ class Jqhtml_Component {
4742
5199
  }
4743
5200
  }
4744
5201
  _set_attributes() {
4745
- // Always set data-cid
4746
- this.$.attr('data-cid', this._cid);
5202
+ // data-cid on the rendered element is debug-only: it mirrors this._cid so component
5203
+ // boundaries are legible in DevTools. The runtime never reads it - scoping uses the
5204
+ // _cid property, and the transient data-cid placeholder the instruction processor
5205
+ // matches on is written and removed before this ever runs.
5206
+ if (debug_attributes_enabled()) {
5207
+ this.$.attr('data-cid', this._cid);
5208
+ }
4747
5209
  // Only set lifecycle state attribute if verbose mode enabled
4748
5210
  if (window.jqhtml?.debug?.verbose) {
4749
5211
  this.$.attr('data-_lifecycle-state', this._ready_state.toString());
@@ -4818,6 +5280,8 @@ class Jqhtml_Component {
4818
5280
  }
4819
5281
  // Static properties
4820
5282
  Jqhtml_Component.__jqhtml_component = true; // Marker for unified register() detection
5283
+ // A bare component name, as accepted by closest() in place of a class selector
5284
+ Jqhtml_Component.COMPONENT_NAME_PATTERN = /^[A-Z][A-Za-z0-9_]*$/;
4821
5285
 
4822
5286
  /**
4823
5287
  * JQHTML v2 Template Renderer
@@ -5682,6 +6146,9 @@ function init_jquery_plugin(jQuery) {
5682
6146
  PatchedInit.prototype = jQuery.fn;
5683
6147
  jQuery.fn.init = PatchedInit;
5684
6148
  }
6149
+ // Install the single debounced window resize listener that drives
6150
+ // on_viewport_resize() on every component. Idempotent.
6151
+ init_viewport_resize(jQuery);
5685
6152
  }
5686
6153
  // Try to auto-initialize if global jQuery exists
5687
6154
  if (typeof window !== 'undefined' && window.jQuery) {
@@ -5695,7 +6162,9 @@ if (typeof window !== 'undefined' && window.jQuery) {
5695
6162
  */
5696
6163
  // Core exports
5697
6164
  // Main initialization function
5698
- function init(jQuery) {
6165
+ function init(jQuery, config) {
6166
+ // Integration config first - it governs behaviour during the very first render
6167
+ configure(config);
5699
6168
  // If jQuery is provided, initialize the plugin
5700
6169
  if (jQuery) {
5701
6170
  init_jquery_plugin(jQuery);
@@ -5709,7 +6178,7 @@ function init(jQuery) {
5709
6178
  }
5710
6179
  }
5711
6180
  // Version - will be replaced during build with actual version from package.json
5712
- const version = '2.3.50';
6181
+ const version = '2.3.59';
5713
6182
  // Default export with all functionality
5714
6183
  const jqhtml = {
5715
6184
  // Core
@@ -5737,6 +6206,9 @@ const jqhtml = {
5737
6206
  __version: version,
5738
6207
  // state facts
5739
6208
  tombstone: 'pepperoni and cheese',
6209
+ // Integration configuration - see config.ts
6210
+ configure,
6211
+ get_config,
5740
6212
  // Debug settings
5741
6213
  debug: {
5742
6214
  enabled: false,
@@ -5842,5 +6314,5 @@ if (typeof window !== 'undefined' && !window.jqhtml) {
5842
6314
  }
5843
6315
  }
5844
6316
 
5845
- export { Jqhtml_Component, LifecycleManager as Jqhtml_LifecycleManager, Jqhtml_Local_Storage, LifecycleManager, Load_Coordinator, applyDebugDelay, boot, clear_preload_data, create_component, jqhtml as default, devWarn, escape_html, escape_html_nl2br, extract_slots, get_captured_data, 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, register_cache_class, register_component, register_template, render_template, set_preload_data, start_data_capture, stop_data_capture, version };
6317
+ export { Jqhtml_Component, LifecycleManager as Jqhtml_LifecycleManager, Jqhtml_Local_Storage, LifecycleManager, Load_Coordinator, applyDebugDelay, boot, clear_preload_data, configure, create_component, jqhtml as default, devWarn, escape_html, escape_html_nl2br, extract_slots, get_captured_data, get_component_class, get_component_names, get_config, 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, register_cache_class, register_component, register_template, render_template, set_preload_data, start_data_capture, stop_data_capture, version };
5846
6318
  //# sourceMappingURL=index.js.map