bitwrench 2.0.16 → 2.0.17

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 (42) hide show
  1. package/dist/bitwrench-bccl.cjs.js +6 -2
  2. package/dist/bitwrench-bccl.cjs.min.js +3 -3
  3. package/dist/bitwrench-bccl.esm.js +6 -2
  4. package/dist/bitwrench-bccl.esm.min.js +3 -3
  5. package/dist/bitwrench-bccl.umd.js +6 -2
  6. package/dist/bitwrench-bccl.umd.min.js +2 -2
  7. package/dist/bitwrench-code-edit.cjs.js +1 -1
  8. package/dist/bitwrench-code-edit.cjs.min.js +1 -1
  9. package/dist/bitwrench-code-edit.es5.js +1 -1
  10. package/dist/bitwrench-code-edit.es5.min.js +1 -1
  11. package/dist/bitwrench-code-edit.esm.js +1 -1
  12. package/dist/bitwrench-code-edit.esm.min.js +1 -1
  13. package/dist/bitwrench-code-edit.umd.js +1 -1
  14. package/dist/bitwrench-code-edit.umd.min.js +1 -1
  15. package/dist/bitwrench-lean.cjs.js +506 -154
  16. package/dist/bitwrench-lean.cjs.min.js +7 -7
  17. package/dist/bitwrench-lean.es5.js +517 -155
  18. package/dist/bitwrench-lean.es5.min.js +5 -5
  19. package/dist/bitwrench-lean.esm.js +505 -154
  20. package/dist/bitwrench-lean.esm.min.js +6 -6
  21. package/dist/bitwrench-lean.umd.js +506 -154
  22. package/dist/bitwrench-lean.umd.min.js +7 -7
  23. package/dist/bitwrench.cjs.js +511 -155
  24. package/dist/bitwrench.cjs.min.js +8 -8
  25. package/dist/bitwrench.es5.js +525 -156
  26. package/dist/bitwrench.es5.min.js +6 -6
  27. package/dist/bitwrench.esm.js +510 -155
  28. package/dist/bitwrench.esm.min.js +8 -8
  29. package/dist/bitwrench.umd.js +511 -155
  30. package/dist/bitwrench.umd.min.js +8 -8
  31. package/dist/builds.json +82 -82
  32. package/dist/bwserve.cjs.js +16 -2
  33. package/dist/bwserve.esm.js +16 -2
  34. package/dist/sri.json +34 -34
  35. package/package.json +4 -2
  36. package/readme.html +1 -1
  37. package/src/bitwrench-bccl.js +5 -1
  38. package/src/bitwrench.js +502 -151
  39. package/src/bwserve/index.js +12 -1
  40. package/src/bwserve/shell.js +3 -0
  41. package/src/cli/layout-default.js +47 -32
  42. package/src/version.js +3 -3
@@ -1,20 +1,21 @@
1
- /*! bitwrench v2.0.16 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench v2.0.17 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  'use strict';
3
3
 
4
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
4
5
  /**
5
6
  * Auto-generated version file from package.json
6
7
  * DO NOT EDIT DIRECTLY - Use npm run generate-version
7
8
  */
8
9
 
9
10
  const VERSION_INFO = {
10
- version: '2.0.16',
11
+ version: '2.0.17',
11
12
  name: 'bitwrench',
12
13
  description: 'A library for javascript UI functions.',
13
14
  license: 'BSD-2-Clause',
14
15
  homepage: 'https://deftio.github.com/bitwrench/pages',
15
16
  repository: 'git+https://github.com/deftio/bitwrench.git',
16
17
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
17
- buildDate: '2026-03-12T08:05:52.043Z'
18
+ buildDate: '2026-03-13T23:15:10.823Z'
18
19
  };
19
20
 
20
21
  /**
@@ -6876,7 +6877,11 @@ var BCCL = {
6876
6877
  function make(type, props) {
6877
6878
  var def = BCCL[type];
6878
6879
  if (!def) throw new Error('bw.make: unknown component type "' + type + '". Available: ' + Object.keys(BCCL).join(', '));
6879
- return def.make(props || {});
6880
+ var taco = def.make(props || {});
6881
+ if (taco && typeof taco === 'object') {
6882
+ taco._bwFactory = { type: type, props: props || {} };
6883
+ }
6884
+ return taco;
6880
6885
  }
6881
6886
 
6882
6887
  var components = /*#__PURE__*/Object.freeze({
@@ -6997,7 +7002,7 @@ const bw = {
6997
7002
  __monkey_patch_is_nodejs__: {
6998
7003
  _value: 'ignore',
6999
7004
  set: function(x) {
7000
- this._value = (typeof x === 'boolean') ? x : 'ignore';
7005
+ this._value = _is(x, 'boolean') ? x : 'ignore';
7001
7006
  },
7002
7007
  get: function() {
7003
7008
  return this._value;
@@ -7045,6 +7050,67 @@ Object.defineProperty(bw, '_isBrowser', {
7045
7050
  configurable: true
7046
7051
  });
7047
7052
 
7053
+ // ── Internal aliases ─────────────────────────────────────────────────────
7054
+ // Short names for frequently-used builtins and internal methods.
7055
+ // Same pattern as v1 (_to = bw.typeOf, etc.).
7056
+ //
7057
+ // Why: Terser can't shorten global property chains (console.warn,
7058
+ // Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
7059
+ // because it can't prove they're side-effect-free. We can, so we alias
7060
+ // them here. Each alias saves bytes in the minified output, and the short
7061
+ // names also reduce visual noise in the hot paths (binding pipeline,
7062
+ // createDOM, etc.).
7063
+ //
7064
+ // Alias Target Sites
7065
+ // ───────── ────────────────────────────────────── ─────
7066
+ // _hop Object.prototype.hasOwnProperty 15
7067
+ // _isA Array.isArray 25
7068
+ // _keys Object.keys 7
7069
+ // _to bw.typeOf (type string) 26
7070
+ // _is type check boolean: _is(x,'string') ~50
7071
+ // _cw console.warn 8
7072
+ // _cl console.log 11
7073
+ // _ce console.error 4
7074
+ // _chp ComponentHandle.prototype 28 (defined after constructor)
7075
+ //
7076
+ // Note: document.createElement etc. are NOT aliased because they require
7077
+ // `this === document` and .bind() would add overhead on every call.
7078
+ // Console aliases use thin wrappers (not direct refs) so test monkey-
7079
+ // patching of console.warn/log/error continues to work.
7080
+ //
7081
+ // `typeof x` for UNDECLARED globals (window, document, process, require,
7082
+ // EventSource, navigator, Promise, __filename, import.meta) MUST stay as
7083
+ // raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
7084
+ //
7085
+ // ── v1 functional type helpers (kept for reference, not currently used) ──
7086
+ // _toa(x, type, trueVal, falseVal) — bw.typeAssign:
7087
+ // returns trueVal if _to(x)===type, else falseVal.
7088
+ // Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
7089
+ // _toc(x, type, trueVal, falseVal) — bw.typeConvert:
7090
+ // same as _toa but if trueVal/falseVal are functions, calls them with x.
7091
+ // Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
7092
+ // Uncomment if pattern frequency justifies them:
7093
+ // var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
7094
+ // var _toc = function(x, t, y, n) { var r = _to(x)===t; return r ? (_to(y)==='function'?y(x):y) : (_to(n)==='function'?n(x):n); };
7095
+ // ─────────────────────────────────────────────────────────────────────────
7096
+ var _hop = Object.prototype.hasOwnProperty;
7097
+ var _isA = Array.isArray;
7098
+ var _keys = Object.keys;
7099
+ var _to = typeOf; // imported from bitwrench-utils.js
7100
+ var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
7101
+ // Console aliases use thin wrappers (not direct references) so that test
7102
+ // code can monkey-patch console.warn/log/error and the patches take effect.
7103
+ var _cw = function() { console.warn.apply(console, arguments); };
7104
+ var _cl = function() { console.log.apply(console, arguments); };
7105
+ var _ce = function() { console.error.apply(console, arguments); };
7106
+
7107
+ /**
7108
+ * Debug flag. When true, emits console.warn for silent binding failures
7109
+ * (missing paths, null refs, auto-created intermediate objects).
7110
+ * @type {boolean}
7111
+ */
7112
+ bw.debug = false;
7113
+
7048
7114
  /**
7049
7115
  * Lazy-resolve Node.js `fs` module.
7050
7116
  * Tries require('fs') first (available in CJS/UMD Node.js builds),
@@ -7192,7 +7258,7 @@ bw.uuid = function(prefix) {
7192
7258
  */
7193
7259
  bw._el = function(id) {
7194
7260
  // Pass-through for DOM elements
7195
- if (typeof id !== 'string') return id || null;
7261
+ if (!_is(id, 'string')) return id || null;
7196
7262
  if (!id) return null;
7197
7263
  if (!bw._isBrowser) return null;
7198
7264
 
@@ -7288,7 +7354,7 @@ bw._deregisterNode = function(el, bwId) {
7288
7354
  * // => '&lt;b&gt;Hello&lt;&#x2F;b&gt; &amp; &quot;world&quot;'
7289
7355
  */
7290
7356
  bw.escapeHTML = function(str) {
7291
- if (typeof str !== 'string') return '';
7357
+ if (!_is(str, 'string')) return '';
7292
7358
 
7293
7359
  const escapeMap = {
7294
7360
  '&': '&amp;',
@@ -7361,7 +7427,7 @@ bw.html = function(taco, options = {}) {
7361
7427
  }
7362
7428
 
7363
7429
  // Handle arrays of TACOs
7364
- if (Array.isArray(taco)) {
7430
+ if (_isA(taco)) {
7365
7431
  return taco.map(t => bw.html(t, options)).join('');
7366
7432
  }
7367
7433
 
@@ -7384,15 +7450,15 @@ bw.html = function(taco, options = {}) {
7384
7450
  if (taco && taco._bwEach && options.state) {
7385
7451
  var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
7386
7452
  var arr = bw._evaluatePath(options.state, eachExpr);
7387
- if (!Array.isArray(arr)) return '';
7453
+ if (!_isA(arr)) return '';
7388
7454
  return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
7389
7455
  }
7390
7456
 
7391
7457
  // Handle primitives and non-TACO objects
7392
- if (typeof taco !== 'object' || !taco.t) {
7458
+ if (!_is(taco, 'object') || !taco.t) {
7393
7459
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
7394
7460
  // Resolve template bindings if state provided
7395
- if (options.state && typeof str === 'string' && str.indexOf('${') >= 0) {
7461
+ if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
7396
7462
  str = bw._resolveTemplate(str, options.state, !!options.compile);
7397
7463
  }
7398
7464
  return str;
@@ -7412,10 +7478,18 @@ bw.html = function(taco, options = {}) {
7412
7478
  // Skip null, undefined, false
7413
7479
  if (value == null || value === false) continue;
7414
7480
 
7415
- // Skip event handlers (they're for DOM only)
7416
- if (key.startsWith('on')) continue;
7481
+ // Serialize event handlers via funcRegister
7482
+ if (key.startsWith('on')) {
7483
+ if (_is(value, 'function')) {
7484
+ var fnId = bw.funcRegister(value);
7485
+ attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
7486
+ } else if (_is(value, 'string')) {
7487
+ attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
7488
+ }
7489
+ continue;
7490
+ }
7417
7491
 
7418
- if (key === 'style' && typeof value === 'object') {
7492
+ if (key === 'style' && _is(value, 'object')) {
7419
7493
  // Convert style object to string
7420
7494
  const styleStr = Object.entries(value)
7421
7495
  .filter(([, v]) => v != null)
@@ -7426,7 +7500,7 @@ bw.html = function(taco, options = {}) {
7426
7500
  }
7427
7501
  } else if (key === 'class') {
7428
7502
  // Handle class as array or string
7429
- const classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
7503
+ const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
7430
7504
  if (classStr) {
7431
7505
  attrStr += ` class="${bw.escapeHTML(classStr)}"`;
7432
7506
  }
@@ -7462,13 +7536,184 @@ bw.html = function(taco, options = {}) {
7462
7536
  // Process content recursively
7463
7537
  let contentStr = content != null ? bw.html(content, options) : '';
7464
7538
  // Resolve template bindings in content if state provided
7465
- if (options.state && typeof contentStr === 'string' && contentStr.indexOf('${') >= 0) {
7539
+ if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
7466
7540
  contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
7467
7541
  }
7468
7542
 
7469
7543
  return `<${tag}${attrStr}>${contentStr}</${tag}>`;
7470
7544
  };
7471
7545
 
7546
+ /**
7547
+ * Generate a complete, self-contained HTML document from TACO content.
7548
+ *
7549
+ * Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
7550
+ * func registry emission (so serialized event handlers work), optional theme,
7551
+ * and extra head elements. Designed for static site generation, offline/airgapped
7552
+ * use, and the "static site that isn't static" workflow.
7553
+ *
7554
+ * @param {Object} [opts={}] - Page options
7555
+ * @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
7556
+ * @param {string} [opts.title='bitwrench'] - Page title
7557
+ * @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
7558
+ * @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
7559
+ * @param {string} [opts.css=''] - Additional CSS for <style> block
7560
+ * @param {string|Object} [opts.theme=null] - Theme preset name or config object
7561
+ * @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
7562
+ * @param {string} [opts.favicon=''] - Favicon URL
7563
+ * @param {string} [opts.lang='en'] - HTML lang attribute
7564
+ * @returns {string} Complete HTML document string
7565
+ * @category DOM Generation
7566
+ * @see bw.html
7567
+ * @example
7568
+ * bw.htmlPage({
7569
+ * title: 'My App',
7570
+ * body: { t: 'h1', c: 'Hello World' },
7571
+ * runtime: 'shim'
7572
+ * })
7573
+ */
7574
+ bw.htmlPage = function(opts) {
7575
+ opts = opts || {};
7576
+ var title = opts.title || 'bitwrench';
7577
+ var body = opts.body || '';
7578
+ var state = opts.state || undefined;
7579
+ var runtime = opts.runtime || 'shim';
7580
+ var css = opts.css || '';
7581
+ var theme = opts.theme || null;
7582
+ var headExtra = opts.head || [];
7583
+ var favicon = opts.favicon || '';
7584
+ var lang = opts.lang || 'en';
7585
+
7586
+ // Snapshot funcRegistry counter before rendering
7587
+ var fnCounterBefore = bw._fnIDCounter;
7588
+
7589
+ // Render body content
7590
+ var bodyHTML = '';
7591
+ if (_is(body, 'string')) {
7592
+ bodyHTML = body;
7593
+ } else {
7594
+ var htmlOpts = {};
7595
+ if (state) htmlOpts.state = state;
7596
+ bodyHTML = bw.html(body, htmlOpts);
7597
+ }
7598
+
7599
+ // Collect functions registered during this render
7600
+ var fnCounterAfter = bw._fnIDCounter;
7601
+ var registryEntries = '';
7602
+ for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
7603
+ var fnKey = 'bw_fn_' + i;
7604
+ if (bw._fnRegistry[fnKey]) {
7605
+ registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
7606
+ bw._fnRegistry[fnKey].toString() + ';\n';
7607
+ }
7608
+ }
7609
+
7610
+ // Build runtime script for <head>
7611
+ var runtimeHead = '';
7612
+ if (runtime === 'inline') {
7613
+ // Read UMD bundle synchronously if in Node.js
7614
+ var umdSource = null;
7615
+ if (bw._isNode) {
7616
+ try {
7617
+ var fs = (typeof require === 'function') ? require('fs') : null;
7618
+ var pathMod = (typeof require === 'function') ? require('path') : null;
7619
+ if (fs && pathMod) {
7620
+ // Resolve dist/ relative to this source file
7621
+ var srcDir = '';
7622
+ try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
7623
+ catch(e2) { /* ESM: __filename not available */ }
7624
+ if (!srcDir && typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench.cjs.js', document.baseURI).href)) }) !== 'undefined' && (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench.cjs.js', document.baseURI).href))) {
7625
+ var url = (typeof require === 'function') ? require('url') : null;
7626
+ if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench.cjs.js', document.baseURI).href))));
7627
+ }
7628
+ if (srcDir) {
7629
+ var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
7630
+ umdSource = fs.readFileSync(distPath, 'utf8');
7631
+ }
7632
+ }
7633
+ } catch(e) { /* fall through */ }
7634
+ }
7635
+ if (umdSource) {
7636
+ runtimeHead = '<script>' + umdSource + '</script>';
7637
+ } else {
7638
+ // Fallback to shim in browser or if dist not available
7639
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
7640
+ }
7641
+ } else if (runtime === 'cdn') {
7642
+ runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
7643
+ } else if (runtime === 'shim') {
7644
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
7645
+ }
7646
+ // runtime === 'none' → empty
7647
+
7648
+ // Theme CSS
7649
+ var themeCSS = '';
7650
+ if (theme) {
7651
+ var themeConfig = _is(theme, 'string')
7652
+ ? (THEME_PRESETS[theme.toLowerCase()] || null)
7653
+ : theme;
7654
+ if (themeConfig) {
7655
+ var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, { inject: false }));
7656
+ themeCSS = themeResult.css;
7657
+ }
7658
+ }
7659
+
7660
+ // Extra <head> elements
7661
+ var headHTML = '';
7662
+ if (_isA(headExtra) && headExtra.length > 0) {
7663
+ headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
7664
+ }
7665
+
7666
+ // Favicon
7667
+ var faviconTag = '';
7668
+ if (favicon) {
7669
+ var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
7670
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c];
7671
+ });
7672
+ faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
7673
+ }
7674
+
7675
+ // Escaped title
7676
+ var safeTitle = bw.escapeHTML(title);
7677
+
7678
+ // Combine all CSS
7679
+ var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
7680
+
7681
+ // Body-end script: registry entries + optional loadDefaultStyles
7682
+ var bodyEndScript = '';
7683
+ var bodyEndParts = [];
7684
+ if (registryEntries) {
7685
+ bodyEndParts.push(registryEntries);
7686
+ }
7687
+ if (runtime === 'inline' || runtime === 'cdn') {
7688
+ bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
7689
+ }
7690
+ if (bodyEndParts.length > 0) {
7691
+ bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
7692
+ }
7693
+
7694
+ // Assemble document
7695
+ var parts = [
7696
+ '<!DOCTYPE html>',
7697
+ '<html lang="' + lang + '">',
7698
+ '<head>',
7699
+ '<meta charset="UTF-8">',
7700
+ '<meta name="viewport" content="width=device-width, initial-scale=1">'
7701
+ ];
7702
+ parts.push('<title>' + safeTitle + '</title>');
7703
+ if (faviconTag) parts.push(faviconTag);
7704
+ if (runtimeHead) parts.push(runtimeHead);
7705
+ if (headHTML) parts.push(headHTML);
7706
+ if (allCSS) parts.push('<style>' + allCSS + '</style>');
7707
+ parts.push('</head>');
7708
+ parts.push('<body>');
7709
+ parts.push(bodyHTML);
7710
+ if (bodyEndScript) parts.push(bodyEndScript);
7711
+ parts.push('</body>');
7712
+ parts.push('</html>');
7713
+
7714
+ return parts.join('\n');
7715
+ };
7716
+
7472
7717
  /**
7473
7718
  * Create a live DOM element from a TACO object (browser only).
7474
7719
  *
@@ -7513,7 +7758,7 @@ bw.createDOM = function(taco, options = {}) {
7513
7758
  }
7514
7759
 
7515
7760
  // Handle text nodes
7516
- if (typeof taco !== 'object' || !taco.t) {
7761
+ if (!_is(taco, 'object') || !taco.t) {
7517
7762
  return document.createTextNode(String(taco));
7518
7763
  }
7519
7764
 
@@ -7526,16 +7771,16 @@ bw.createDOM = function(taco, options = {}) {
7526
7771
  for (const [key, value] of Object.entries(attrs)) {
7527
7772
  if (value == null || value === false) continue;
7528
7773
 
7529
- if (key === 'style' && typeof value === 'object') {
7774
+ if (key === 'style' && _is(value, 'object')) {
7530
7775
  // Apply styles directly
7531
7776
  Object.assign(el.style, value);
7532
7777
  } else if (key === 'class') {
7533
7778
  // Handle class as array or string
7534
- const classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
7779
+ const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
7535
7780
  if (classStr) {
7536
7781
  el.className = classStr;
7537
7782
  }
7538
- } else if (key.startsWith('on') && typeof value === 'function') {
7783
+ } else if (key.startsWith('on') && _is(value, 'function')) {
7539
7784
  // Event handlers
7540
7785
  const eventName = key.slice(2).toLowerCase();
7541
7786
  el.addEventListener(eventName, value);
@@ -7555,7 +7800,7 @@ bw.createDOM = function(taco, options = {}) {
7555
7800
  // Children with data-bw_id or id attributes get local refs on the parent,
7556
7801
  // so o.render functions can access them without any DOM lookup.
7557
7802
  if (content != null) {
7558
- if (Array.isArray(content)) {
7803
+ if (_isA(content)) {
7559
7804
  content.forEach(child => {
7560
7805
  if (child != null) {
7561
7806
  // Handle ComponentHandle in content arrays (Level 2 children)
@@ -7575,20 +7820,20 @@ bw.createDOM = function(taco, options = {}) {
7575
7820
  if (childEl._bw_refs) {
7576
7821
  if (!el._bw_refs) el._bw_refs = {};
7577
7822
  for (var rk in childEl._bw_refs) {
7578
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
7823
+ if (_hop.call(childEl._bw_refs, rk)) {
7579
7824
  el._bw_refs[rk] = childEl._bw_refs[rk];
7580
7825
  }
7581
7826
  }
7582
7827
  }
7583
7828
  }
7584
7829
  });
7585
- } else if (typeof content === 'object' && content.__bw_raw) {
7830
+ } else if (_is(content, 'object') && content.__bw_raw) {
7586
7831
  // Raw HTML content — inject via innerHTML
7587
7832
  el.innerHTML = content.v;
7588
7833
  } else if (content._bwComponent === true) {
7589
7834
  // Single ComponentHandle as content
7590
7835
  content.mount(el);
7591
- } else if (typeof content === 'object' && content.t) {
7836
+ } else if (_is(content, 'object') && content.t) {
7592
7837
  var childEl = bw.createDOM(content, options);
7593
7838
  el.appendChild(childEl);
7594
7839
  var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
@@ -7599,7 +7844,7 @@ bw.createDOM = function(taco, options = {}) {
7599
7844
  if (childEl._bw_refs) {
7600
7845
  if (!el._bw_refs) el._bw_refs = {};
7601
7846
  for (var rk in childEl._bw_refs) {
7602
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
7847
+ if (_hop.call(childEl._bw_refs, rk)) {
7603
7848
  el._bw_refs[rk] = childEl._bw_refs[rk];
7604
7849
  }
7605
7850
  }
@@ -7632,7 +7877,7 @@ bw.createDOM = function(taco, options = {}) {
7632
7877
  el._bw_render = opts.render;
7633
7878
 
7634
7879
  if (opts.mounted) {
7635
- console.warn('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
7880
+ _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
7636
7881
  }
7637
7882
 
7638
7883
  // Queue initial render (same timing as mounted)
@@ -7705,7 +7950,7 @@ bw.DOM = function(target, taco, options = {}) {
7705
7950
  const targetEl = bw._el(target);
7706
7951
 
7707
7952
  if (!targetEl) {
7708
- console.error('bw.DOM: Target element not found:', target);
7953
+ _ce('bw.DOM: Target element not found:', target);
7709
7954
  return null;
7710
7955
  }
7711
7956
 
@@ -7745,7 +7990,7 @@ bw.DOM = function(target, taco, options = {}) {
7745
7990
  targetEl.appendChild(taco.element);
7746
7991
  }
7747
7992
  // Handle arrays
7748
- else if (Array.isArray(taco)) {
7993
+ else if (_isA(taco)) {
7749
7994
  taco.forEach(t => {
7750
7995
  if (t != null) {
7751
7996
  if (t._bwComponent === true) {
@@ -7781,7 +8026,7 @@ bw.DOM = function(target, taco, options = {}) {
7781
8026
  bw.compileProps = function(handle, props = {}) {
7782
8027
  const compiledProps = {};
7783
8028
 
7784
- Object.keys(props).forEach(key => {
8029
+ _keys(props).forEach(key => {
7785
8030
  // Create getter/setter for each prop
7786
8031
  Object.defineProperty(compiledProps, key, {
7787
8032
  get() {
@@ -8099,17 +8344,17 @@ bw.patch = function(id, content, attr) {
8099
8344
  if (attr) {
8100
8345
  // Patch an attribute
8101
8346
  el.setAttribute(attr, String(content));
8102
- } else if (Array.isArray(content)) {
8347
+ } else if (_isA(content)) {
8103
8348
  // Patch with array of children (strings and/or TACOs)
8104
8349
  el.innerHTML = '';
8105
8350
  content.forEach(function(item) {
8106
- if (typeof item === 'string' || typeof item === 'number') {
8351
+ if (_is(item, 'string') || _is(item, 'number')) {
8107
8352
  el.appendChild(document.createTextNode(String(item)));
8108
8353
  } else if (item && item.t) {
8109
8354
  el.appendChild(bw.createDOM(item));
8110
8355
  }
8111
8356
  });
8112
- } else if (typeof content === 'object' && content !== null && content.t) {
8357
+ } else if (_is(content, 'object') && content.t) {
8113
8358
  // Patch with a TACO — replace children
8114
8359
  el.innerHTML = '';
8115
8360
  el.appendChild(bw.createDOM(content));
@@ -8140,7 +8385,7 @@ bw.patch = function(id, content, attr) {
8140
8385
  bw.patchAll = function(patches) {
8141
8386
  var results = {};
8142
8387
  for (var id in patches) {
8143
- if (Object.prototype.hasOwnProperty.call(patches, id)) {
8388
+ if (_hop.call(patches, id)) {
8144
8389
  results[id] = bw.patch(id, patches[id]);
8145
8390
  }
8146
8391
  }
@@ -8237,7 +8482,7 @@ bw.pub = function(topic, detail) {
8237
8482
  snapshot[i].handler(detail);
8238
8483
  called++;
8239
8484
  } catch (err) {
8240
- console.warn('bw.pub: subscriber error on topic "' + topic + '":', err);
8485
+ _cw('bw.pub: subscriber error on topic "' + topic + '":', err);
8241
8486
  }
8242
8487
  }
8243
8488
  return called;
@@ -8333,8 +8578,8 @@ bw._fnIDCounter = 0;
8333
8578
  * @see bw.funcGetDispatchStr
8334
8579
  */
8335
8580
  bw.funcRegister = function(fn, name) {
8336
- if (typeof fn !== 'function') return '';
8337
- var fnID = (typeof name === 'string' && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
8581
+ if (!_is(fn, 'function')) return '';
8582
+ var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
8338
8583
  bw._fnRegistry[fnID] = fn;
8339
8584
  return fnID;
8340
8585
  };
@@ -8353,7 +8598,7 @@ bw.funcRegister = function(fn, name) {
8353
8598
  bw.funcGetById = function(name, errFn) {
8354
8599
  name = String(name);
8355
8600
  if (name in bw._fnRegistry) return bw._fnRegistry[name];
8356
- return (typeof errFn === 'function') ? errFn : function() { console.warn('bw.funcGetById: unregistered fn "' + name + '"'); };
8601
+ return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
8357
8602
  };
8358
8603
 
8359
8604
  /**
@@ -8394,13 +8639,30 @@ bw.funcUnregister = function(name) {
8394
8639
  bw.funcGetRegistry = function() {
8395
8640
  var copy = {};
8396
8641
  for (var k in bw._fnRegistry) {
8397
- if (Object.prototype.hasOwnProperty.call(bw._fnRegistry, k)) {
8642
+ if (_hop.call(bw._fnRegistry, k)) {
8398
8643
  copy[k] = bw._fnRegistry[k];
8399
8644
  }
8400
8645
  }
8401
8646
  return copy;
8402
8647
  };
8403
8648
 
8649
+ /**
8650
+ * Minimal runtime shim for funcRegister dispatch in static HTML.
8651
+ * When embedded in a `<script>` tag, provides just enough infrastructure
8652
+ * for `bw.funcGetById()` calls to resolve. The actual function bodies
8653
+ * are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
8654
+ * @type {string}
8655
+ * @category Function Registry
8656
+ */
8657
+ bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
8658
+ 'if(!bw._fnRegistry)bw._fnRegistry={};' +
8659
+ 'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
8660
+ 'console.warn("bw: unregistered fn "+n)};};' +
8661
+ 'bw.funcRegister=function(fn,name){' +
8662
+ 'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
8663
+ 'bw._fnRegistry[id]=fn;return id;};' +
8664
+ 'window.bw=bw;})();';
8665
+
8404
8666
  // ===================================================================================
8405
8667
  // Template Binding Utilities
8406
8668
  // ===================================================================================
@@ -8428,7 +8690,10 @@ bw._evaluatePath = function(state, path) {
8428
8690
  var parts = path.split('.');
8429
8691
  var val = state;
8430
8692
  for (var i = 0; i < parts.length; i++) {
8431
- if (val == null) return '';
8693
+ if (val == null) {
8694
+ if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
8695
+ return '';
8696
+ }
8432
8697
  val = val[parts[i]];
8433
8698
  }
8434
8699
  return (val == null) ? '' : val;
@@ -8448,7 +8713,7 @@ bw._evaluatePath = function(state, path) {
8448
8713
  */
8449
8714
  bw._compiledExprs = {};
8450
8715
  bw._resolveTemplate = function(str, state, compile) {
8451
- if (typeof str !== 'string' || str.indexOf('${') < 0) return str;
8716
+ if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
8452
8717
  var bindings = bw._parseBindings(str);
8453
8718
  if (bindings.length === 0) return str;
8454
8719
 
@@ -8470,6 +8735,7 @@ bw._resolveTemplate = function(str, state, compile) {
8470
8735
  try {
8471
8736
  val = bw._compiledExprs[b.expr](state);
8472
8737
  } catch (e) {
8738
+ if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
8473
8739
  val = '';
8474
8740
  }
8475
8741
  } else {
@@ -8578,7 +8844,7 @@ function ComponentHandle(taco) {
8578
8844
  this._state = {};
8579
8845
  if (o.state) {
8580
8846
  for (var k in o.state) {
8581
- if (Object.prototype.hasOwnProperty.call(o.state, k)) {
8847
+ if (_hop.call(o.state, k)) {
8582
8848
  this._state[k] = o.state[k];
8583
8849
  }
8584
8850
  }
@@ -8587,7 +8853,7 @@ function ComponentHandle(taco) {
8587
8853
  this._actions = {};
8588
8854
  if (o.actions) {
8589
8855
  for (var k2 in o.actions) {
8590
- if (Object.prototype.hasOwnProperty.call(o.actions, k2)) {
8856
+ if (_hop.call(o.actions, k2)) {
8591
8857
  this._actions[k2] = o.actions[k2];
8592
8858
  }
8593
8859
  }
@@ -8597,7 +8863,7 @@ function ComponentHandle(taco) {
8597
8863
  if (o.methods) {
8598
8864
  var self = this;
8599
8865
  for (var k3 in o.methods) {
8600
- if (Object.prototype.hasOwnProperty.call(o.methods, k3)) {
8866
+ if (_hop.call(o.methods, k3)) {
8601
8867
  this._methods[k3] = o.methods[k3];
8602
8868
  (function(methodName, methodFn) {
8603
8869
  self[methodName] = function() {
@@ -8630,14 +8896,23 @@ function ComponentHandle(taco) {
8630
8896
  this._compile = !!o.compile;
8631
8897
  this._bw_refs = {};
8632
8898
  this._refCounter = 0;
8899
+ // Child component ownership (Bug #5)
8900
+ this._children = [];
8901
+ this._parent = null;
8902
+ // Factory metadata for BCCL rebuild (Bug #6)
8903
+ this._factory = taco._bwFactory || null;
8633
8904
  }
8634
8905
 
8906
+ // Short alias for ComponentHandle.prototype (see alias block at top of file).
8907
+ // 28 method definitions × 25 chars = ~700B raw savings in minified output.
8908
+ var _chp = ComponentHandle.prototype;
8909
+
8635
8910
  // ── State Methods ──
8636
8911
 
8637
8912
  /**
8638
8913
  * Get a state value. Dot-path supported: `get('user.name')`
8639
8914
  */
8640
- ComponentHandle.prototype.get = function(key) {
8915
+ _chp.get = function(key) {
8641
8916
  return bw._evaluatePath(this._state, key);
8642
8917
  };
8643
8918
 
@@ -8647,12 +8922,13 @@ ComponentHandle.prototype.get = function(key) {
8647
8922
  * @param {*} value - New value
8648
8923
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
8649
8924
  */
8650
- ComponentHandle.prototype.set = function(key, value, opts) {
8925
+ _chp.set = function(key, value, opts) {
8651
8926
  // Dot-path set
8652
8927
  var parts = key.split('.');
8653
8928
  var obj = this._state;
8654
8929
  for (var i = 0; i < parts.length - 1; i++) {
8655
- if (obj[parts[i]] == null || typeof obj[parts[i]] !== 'object') {
8930
+ if (!_is(obj[parts[i]], 'object')) {
8931
+ if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
8656
8932
  obj[parts[i]] = {};
8657
8933
  }
8658
8934
  obj = obj[parts[i]];
@@ -8672,10 +8948,10 @@ ComponentHandle.prototype.set = function(key, value, opts) {
8672
8948
  /**
8673
8949
  * Get a shallow clone of the full state.
8674
8950
  */
8675
- ComponentHandle.prototype.getState = function() {
8951
+ _chp.getState = function() {
8676
8952
  var clone = {};
8677
8953
  for (var k in this._state) {
8678
- if (Object.prototype.hasOwnProperty.call(this._state, k)) {
8954
+ if (_hop.call(this._state, k)) {
8679
8955
  clone[k] = this._state[k];
8680
8956
  }
8681
8957
  }
@@ -8687,9 +8963,9 @@ ComponentHandle.prototype.getState = function() {
8687
8963
  * @param {Object} updates - Key-value pairs to merge
8688
8964
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
8689
8965
  */
8690
- ComponentHandle.prototype.setState = function(updates, opts) {
8966
+ _chp.setState = function(updates, opts) {
8691
8967
  for (var k in updates) {
8692
- if (Object.prototype.hasOwnProperty.call(updates, k)) {
8968
+ if (_hop.call(updates, k)) {
8693
8969
  this._state[k] = updates[k];
8694
8970
  this._dirtyKeys[k] = true;
8695
8971
  }
@@ -8706,9 +8982,9 @@ ComponentHandle.prototype.setState = function(updates, opts) {
8706
8982
  /**
8707
8983
  * Push a value onto an array in state. Clones the array.
8708
8984
  */
8709
- ComponentHandle.prototype.push = function(key, val) {
8985
+ _chp.push = function(key, val) {
8710
8986
  var arr = this.get(key);
8711
- var newArr = Array.isArray(arr) ? arr.slice() : [];
8987
+ var newArr = _isA(arr) ? arr.slice() : [];
8712
8988
  newArr.push(val);
8713
8989
  this.set(key, newArr);
8714
8990
  };
@@ -8716,9 +8992,9 @@ ComponentHandle.prototype.push = function(key, val) {
8716
8992
  /**
8717
8993
  * Splice an array in state. Clones the array.
8718
8994
  */
8719
- ComponentHandle.prototype.splice = function(key, start, deleteCount) {
8995
+ _chp.splice = function(key, start, deleteCount) {
8720
8996
  var arr = this.get(key);
8721
- var newArr = Array.isArray(arr) ? arr.slice() : [];
8997
+ var newArr = _isA(arr) ? arr.slice() : [];
8722
8998
  var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
8723
8999
  Array.prototype.splice.apply(newArr, args);
8724
9000
  this.set(key, newArr);
@@ -8726,7 +9002,7 @@ ComponentHandle.prototype.splice = function(key, start, deleteCount) {
8726
9002
 
8727
9003
  // ── Scheduling ──
8728
9004
 
8729
- ComponentHandle.prototype._scheduleDirty = function() {
9005
+ _chp._scheduleDirty = function() {
8730
9006
  if (!this._scheduled) {
8731
9007
  this._scheduled = true;
8732
9008
  bw._dirtyComponents.push(this);
@@ -8741,17 +9017,17 @@ ComponentHandle.prototype._scheduleDirty = function() {
8741
9017
  * Creates binding descriptors with refIds for targeted DOM updates.
8742
9018
  * @private
8743
9019
  */
8744
- ComponentHandle.prototype._compileBindings = function() {
9020
+ _chp._compileBindings = function() {
8745
9021
  this._bindings = [];
8746
9022
  this._refCounter = 0;
8747
- var stateKeys = Object.keys(this._state);
9023
+ var stateKeys = _keys(this._state);
8748
9024
  var self = this;
8749
9025
 
8750
9026
  function walkTaco(taco, path) {
8751
- if (taco == null || typeof taco !== 'object' || !taco.t) return taco;
9027
+ if (!_is(taco, 'object') || !taco.t) return taco;
8752
9028
 
8753
9029
  // Check content for bindings
8754
- if (typeof taco.c === 'string' && taco.c.indexOf('${') >= 0) {
9030
+ if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
8755
9031
  var refId = 'bw_ref_' + self._refCounter++;
8756
9032
  var parsed = bw._parseBindings(taco.c);
8757
9033
  var deps = [];
@@ -8773,10 +9049,10 @@ ComponentHandle.prototype._compileBindings = function() {
8773
9049
  // Check attributes for bindings
8774
9050
  if (taco.a) {
8775
9051
  for (var attrName in taco.a) {
8776
- if (!Object.prototype.hasOwnProperty.call(taco.a, attrName)) continue;
9052
+ if (!_hop.call(taco.a, attrName)) continue;
8777
9053
  if (attrName === 'data-bw_ref') continue;
8778
9054
  var attrVal = taco.a[attrName];
8779
- if (typeof attrVal === 'string' && attrVal.indexOf('${') >= 0) {
9055
+ if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
8780
9056
  var refId2 = 'bw_ref_' + self._refCounter++;
8781
9057
  var parsed2 = bw._parseBindings(attrVal);
8782
9058
  var deps2 = [];
@@ -8802,9 +9078,27 @@ ComponentHandle.prototype._compileBindings = function() {
8802
9078
  }
8803
9079
 
8804
9080
  // Recurse into children
8805
- if (Array.isArray(taco.c)) {
9081
+ if (_isA(taco.c)) {
8806
9082
  for (var i = 0; i < taco.c.length; i++) {
8807
- if (taco.c[i] && typeof taco.c[i] === 'object' && taco.c[i].t) {
9083
+ // Wrap string children with ${expr} in a span so patches target the span, not the parent
9084
+ if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
9085
+ var mixedRefId = 'bw_ref_' + self._refCounter++;
9086
+ var mixedParsed = bw._parseBindings(taco.c[i]);
9087
+ var mixedDeps = [];
9088
+ for (var mi = 0; mi < mixedParsed.length; mi++) {
9089
+ mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
9090
+ }
9091
+ self._bindings.push({
9092
+ expr: taco.c[i],
9093
+ type: 'content',
9094
+ refId: mixedRefId,
9095
+ deps: mixedDeps,
9096
+ template: taco.c[i]
9097
+ });
9098
+ // Replace string with a span wrapper so textContent targets the span only
9099
+ taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
9100
+ }
9101
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
8808
9102
  walkTaco(taco.c[i], path.concat(i));
8809
9103
  }
8810
9104
  // Handle bw.when/bw.each markers
@@ -8839,7 +9133,7 @@ ComponentHandle.prototype._compileBindings = function() {
8839
9133
  taco.c[i]._refId = eachRefId;
8840
9134
  }
8841
9135
  }
8842
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
9136
+ } else if (_is(taco.c, 'object') && taco.c.t) {
8843
9137
  walkTaco(taco.c, path.concat(0));
8844
9138
  }
8845
9139
 
@@ -8855,7 +9149,7 @@ ComponentHandle.prototype._compileBindings = function() {
8855
9149
  * Build ref map from the live DOM after createDOM.
8856
9150
  * @private
8857
9151
  */
8858
- ComponentHandle.prototype._collectRefs = function() {
9152
+ _chp._collectRefs = function() {
8859
9153
  this._bw_refs = {};
8860
9154
  if (!this.element) return;
8861
9155
  var els = this.element.querySelectorAll('[data-bw_ref]');
@@ -8876,7 +9170,7 @@ ComponentHandle.prototype._collectRefs = function() {
8876
9170
  * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
8877
9171
  * @param {Element} parentEl - DOM element to mount into
8878
9172
  */
8879
- ComponentHandle.prototype.mount = function(parentEl) {
9173
+ _chp.mount = function(parentEl) {
8880
9174
  // willMount hook
8881
9175
  if (this._hooks.willMount) this._hooks.willMount(this);
8882
9176
 
@@ -8898,7 +9192,7 @@ ComponentHandle.prototype.mount = function(parentEl) {
8898
9192
  // Register named actions in function registry
8899
9193
  var self = this;
8900
9194
  for (var actionName in this._actions) {
8901
- if (Object.prototype.hasOwnProperty.call(this._actions, actionName)) {
9195
+ if (_hop.call(this._actions, actionName)) {
8902
9196
  var registeredName = this._bwId + '_' + actionName;
8903
9197
  (function(aName) {
8904
9198
  bw.funcRegister(function(evt) {
@@ -8917,6 +9211,11 @@ ComponentHandle.prototype.mount = function(parentEl) {
8917
9211
  this.element = bw.createDOM(tacoForDOM);
8918
9212
  this.element._bwComponentHandle = this;
8919
9213
  this.element.setAttribute('data-bw_comp_id', this._bwId);
9214
+
9215
+ // Restore o.render from original TACO (stripped by _tacoForDOM)
9216
+ if (this.taco.o && this.taco.o.render) {
9217
+ this.element._bw_render = this.taco.o.render;
9218
+ }
8920
9219
  if (this._userTag) {
8921
9220
  this.element.classList.add(this._userTag);
8922
9221
  }
@@ -8932,6 +9231,16 @@ ComponentHandle.prototype.mount = function(parentEl) {
8932
9231
 
8933
9232
  this.mounted = true;
8934
9233
 
9234
+ // Scan for child ComponentHandles and link parent/child (Bug #5)
9235
+ var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
9236
+ for (var ci = 0; ci < childEls.length; ci++) {
9237
+ var ch = childEls[ci]._bwComponentHandle;
9238
+ if (ch && ch !== this && !ch._parent) {
9239
+ ch._parent = this;
9240
+ this._children.push(ch);
9241
+ }
9242
+ }
9243
+
8935
9244
  // mounted hook (backward compat: fn.length === 2 wraps (el, state))
8936
9245
  if (this._hooks.mounted) {
8937
9246
  if (this._hooks.mounted.length === 2) {
@@ -8940,16 +9249,21 @@ ComponentHandle.prototype.mount = function(parentEl) {
8940
9249
  this._hooks.mounted(this);
8941
9250
  }
8942
9251
  }
9252
+
9253
+ // Invoke o.render on initial mount (if present)
9254
+ if (this.element._bw_render) {
9255
+ this.element._bw_render(this.element, this._state);
9256
+ }
8943
9257
  };
8944
9258
 
8945
9259
  /**
8946
9260
  * Prepare TACO for initial render: resolve when/each markers.
8947
9261
  * @private
8948
9262
  */
8949
- ComponentHandle.prototype._prepareTaco = function(taco) {
8950
- if (!taco || typeof taco !== 'object') return;
9263
+ _chp._prepareTaco = function(taco) {
9264
+ if (!_is(taco, 'object')) return;
8951
9265
 
8952
- if (Array.isArray(taco.c)) {
9266
+ if (_isA(taco.c)) {
8953
9267
  for (var i = taco.c.length - 1; i >= 0; i--) {
8954
9268
  var child = taco.c[i];
8955
9269
  if (child && child._bwWhen) {
@@ -8974,18 +9288,18 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
8974
9288
  var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
8975
9289
  var arr = bw._evaluatePath(this._state, eachExprStr);
8976
9290
  var items = [];
8977
- if (Array.isArray(arr)) {
9291
+ if (_isA(arr)) {
8978
9292
  for (var j = 0; j < arr.length; j++) {
8979
9293
  items.push(child.factory(arr[j], j));
8980
9294
  }
8981
9295
  }
8982
9296
  taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
8983
9297
  }
8984
- if (taco.c[i] && typeof taco.c[i] === 'object' && taco.c[i].t) {
9298
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
8985
9299
  this._prepareTaco(taco.c[i]);
8986
9300
  }
8987
9301
  }
8988
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
9302
+ } else if (_is(taco.c, 'object') && taco.c.t) {
8989
9303
  this._prepareTaco(taco.c);
8990
9304
  }
8991
9305
  };
@@ -8994,12 +9308,12 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
8994
9308
  * Wire action name strings (in onclick etc.) to dispatch function calls.
8995
9309
  * @private
8996
9310
  */
8997
- ComponentHandle.prototype._wireActions = function(taco) {
8998
- if (!taco || typeof taco !== 'object' || !taco.t) return;
9311
+ _chp._wireActions = function(taco) {
9312
+ if (!_is(taco, 'object') || !taco.t) return;
8999
9313
  if (taco.a) {
9000
9314
  for (var key in taco.a) {
9001
- if (!Object.prototype.hasOwnProperty.call(taco.a, key)) continue;
9002
- if (key.startsWith('on') && typeof taco.a[key] === 'string') {
9315
+ if (!_hop.call(taco.a, key)) continue;
9316
+ if (key.startsWith('on') && _is(taco.a[key], 'string')) {
9003
9317
  var actionName = taco.a[key];
9004
9318
  if (actionName in this._actions) {
9005
9319
  var registeredName = this._bwId + '_' + actionName;
@@ -9013,11 +9327,11 @@ ComponentHandle.prototype._wireActions = function(taco) {
9013
9327
  }
9014
9328
  }
9015
9329
  }
9016
- if (Array.isArray(taco.c)) {
9330
+ if (_isA(taco.c)) {
9017
9331
  for (var i = 0; i < taco.c.length; i++) {
9018
9332
  this._wireActions(taco.c[i]);
9019
9333
  }
9020
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
9334
+ } else if (_is(taco.c, 'object') && taco.c.t) {
9021
9335
  this._wireActions(taco.c);
9022
9336
  }
9023
9337
  };
@@ -9026,7 +9340,7 @@ ComponentHandle.prototype._wireActions = function(taco) {
9026
9340
  * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
9027
9341
  * @private
9028
9342
  */
9029
- ComponentHandle.prototype._deepCloneTaco = function(taco) {
9343
+ _chp._deepCloneTaco = function(taco) {
9030
9344
  if (taco == null) return taco;
9031
9345
  // Preserve _bwWhen / _bwEach markers (contain functions)
9032
9346
  if (taco._bwWhen) {
@@ -9038,18 +9352,18 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
9038
9352
  if (taco._bwEach) {
9039
9353
  return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
9040
9354
  }
9041
- if (typeof taco !== 'object' || !taco.t) return taco;
9355
+ if (!_is(taco, 'object') || !taco.t) return taco;
9042
9356
  var result = { t: taco.t };
9043
9357
  if (taco.a) {
9044
9358
  result.a = {};
9045
9359
  for (var k in taco.a) {
9046
- if (Object.prototype.hasOwnProperty.call(taco.a, k)) result.a[k] = taco.a[k];
9360
+ if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
9047
9361
  }
9048
9362
  }
9049
9363
  if (taco.c != null) {
9050
- if (Array.isArray(taco.c)) {
9364
+ if (_isA(taco.c)) {
9051
9365
  result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
9052
- } else if (typeof taco.c === 'object') {
9366
+ } else if (_is(taco.c, 'object')) {
9053
9367
  result.c = this._deepCloneTaco(taco.c);
9054
9368
  } else {
9055
9369
  result.c = taco.c;
@@ -9063,27 +9377,31 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
9063
9377
  * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
9064
9378
  * @private
9065
9379
  */
9066
- ComponentHandle.prototype._tacoForDOM = function(taco) {
9067
- if (!taco || typeof taco !== 'object' || !taco.t) return taco;
9380
+ _chp._tacoForDOM = function(taco) {
9381
+ if (!_is(taco, 'object') || !taco.t) return taco;
9068
9382
  var result = { t: taco.t };
9069
9383
  if (taco.a) result.a = taco.a;
9070
9384
  if (taco.c != null) {
9071
- if (Array.isArray(taco.c)) {
9385
+ if (_isA(taco.c)) {
9072
9386
  result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
9073
- } else if (typeof taco.c === 'object' && taco.c.t) {
9387
+ } else if (_is(taco.c, 'object') && taco.c.t) {
9074
9388
  result.c = this._tacoForDOM(taco.c);
9075
9389
  } else {
9076
9390
  result.c = taco.c;
9077
9391
  }
9078
9392
  }
9079
9393
  // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
9394
+ if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
9395
+ _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
9396
+ '>. Use onclick attribute or bw.component() for child interactivity.');
9397
+ }
9080
9398
  return result;
9081
9399
  };
9082
9400
 
9083
9401
  /**
9084
9402
  * Unmount: remove from DOM, deactivate, preserve state for re-mount.
9085
9403
  */
9086
- ComponentHandle.prototype.unmount = function() {
9404
+ _chp.unmount = function() {
9087
9405
  if (!this.mounted) return;
9088
9406
 
9089
9407
  // unmount hook
@@ -9118,12 +9436,23 @@ ComponentHandle.prototype.unmount = function() {
9118
9436
  /**
9119
9437
  * Destroy: unmount + clear state + unregister actions.
9120
9438
  */
9121
- ComponentHandle.prototype.destroy = function() {
9439
+ _chp.destroy = function() {
9122
9440
  // willDestroy hook
9123
9441
  if (this._hooks.willDestroy) {
9124
9442
  this._hooks.willDestroy(this);
9125
9443
  }
9126
9444
 
9445
+ // Cascade destroy to children depth-first (Bug #5)
9446
+ for (var ci = this._children.length - 1; ci >= 0; ci--) {
9447
+ this._children[ci].destroy();
9448
+ }
9449
+ this._children = [];
9450
+ if (this._parent) {
9451
+ var idx = this._parent._children.indexOf(this);
9452
+ if (idx >= 0) this._parent._children.splice(idx, 1);
9453
+ this._parent = null;
9454
+ }
9455
+
9127
9456
  this.unmount();
9128
9457
 
9129
9458
  // Unregister actions from function registry
@@ -9150,12 +9479,36 @@ ComponentHandle.prototype.destroy = function() {
9150
9479
  * Flush dirty state: resolve changed bindings and apply to DOM.
9151
9480
  * @private
9152
9481
  */
9153
- ComponentHandle.prototype._flush = function() {
9482
+ _chp._flush = function() {
9154
9483
  this._scheduled = false;
9155
- var changedKeys = Object.keys(this._dirtyKeys);
9484
+ var changedKeys = _keys(this._dirtyKeys);
9156
9485
  this._dirtyKeys = {};
9157
9486
  if (changedKeys.length === 0 || !this.mounted) return;
9158
9487
 
9488
+ // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
9489
+ // rebuild the TACO from the factory with merged state (Bug #6)
9490
+ if (this._factory) {
9491
+ var rebuildNeeded = false;
9492
+ for (var fi = 0; fi < changedKeys.length; fi++) {
9493
+ if (_hop.call(this._factory.props, changedKeys[fi])) {
9494
+ rebuildNeeded = true; break;
9495
+ }
9496
+ }
9497
+ if (rebuildNeeded) {
9498
+ var merged = {};
9499
+ for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
9500
+ for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
9501
+ this._factory.props = merged;
9502
+ var newTaco = bw.make(this._factory.type, merged);
9503
+ newTaco._bwFactory = this._factory;
9504
+ this.taco = newTaco;
9505
+ this._originalTaco = this._deepCloneTaco(newTaco);
9506
+ this._render();
9507
+ if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
9508
+ return;
9509
+ }
9510
+ }
9511
+
9159
9512
  // willUpdate hook
9160
9513
  if (this._hooks.willUpdate) {
9161
9514
  this._hooks.willUpdate(this, changedKeys);
@@ -9194,7 +9547,7 @@ ComponentHandle.prototype._flush = function() {
9194
9547
  * Returns list of patches to apply.
9195
9548
  * @private
9196
9549
  */
9197
- ComponentHandle.prototype._resolveBindings = function(changedKeys) {
9550
+ _chp._resolveBindings = function(changedKeys) {
9198
9551
  var patches = [];
9199
9552
  for (var i = 0; i < this._bindings.length; i++) {
9200
9553
  var b = this._bindings[i];
@@ -9230,11 +9583,14 @@ ComponentHandle.prototype._resolveBindings = function(changedKeys) {
9230
9583
  * Apply patches to DOM.
9231
9584
  * @private
9232
9585
  */
9233
- ComponentHandle.prototype._applyPatches = function(patches) {
9586
+ _chp._applyPatches = function(patches) {
9234
9587
  for (var i = 0; i < patches.length; i++) {
9235
9588
  var p = patches[i];
9236
9589
  var el = this._bw_refs[p.refId];
9237
- if (!el) continue;
9590
+ if (!el) {
9591
+ if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
9592
+ continue;
9593
+ }
9238
9594
  if (p.type === 'content') {
9239
9595
  el.textContent = p.value;
9240
9596
  } else if (p.type === 'attribute') {
@@ -9251,7 +9607,7 @@ ComponentHandle.prototype._applyPatches = function(patches) {
9251
9607
  * Resolve all bindings and apply (used for initial render).
9252
9608
  * @private
9253
9609
  */
9254
- ComponentHandle.prototype._resolveAndApplyAll = function() {
9610
+ _chp._resolveAndApplyAll = function() {
9255
9611
  var patches = [];
9256
9612
  for (var i = 0; i < this._bindings.length; i++) {
9257
9613
  var b = this._bindings[i];
@@ -9274,7 +9630,7 @@ ComponentHandle.prototype._resolveAndApplyAll = function() {
9274
9630
  * Full re-render for structural changes (when/each branch switches).
9275
9631
  * @private
9276
9632
  */
9277
- ComponentHandle.prototype._render = function() {
9633
+ _chp._render = function() {
9278
9634
  if (!this.element || !this.element.parentNode) return;
9279
9635
  var parent = this.element.parentNode;
9280
9636
  var nextSibling = this.element.nextSibling;
@@ -9314,7 +9670,7 @@ ComponentHandle.prototype._render = function() {
9314
9670
  * @param {string} event - Event name (e.g., 'click')
9315
9671
  * @param {Function} handler - Event handler
9316
9672
  */
9317
- ComponentHandle.prototype.on = function(event, handler) {
9673
+ _chp.on = function(event, handler) {
9318
9674
  if (this.element) {
9319
9675
  this.element.addEventListener(event, handler);
9320
9676
  }
@@ -9326,7 +9682,7 @@ ComponentHandle.prototype.on = function(event, handler) {
9326
9682
  * @param {string} event - Event name
9327
9683
  * @param {Function} handler - Handler to remove
9328
9684
  */
9329
- ComponentHandle.prototype.off = function(event, handler) {
9685
+ _chp.off = function(event, handler) {
9330
9686
  if (this.element) {
9331
9687
  this.element.removeEventListener(event, handler);
9332
9688
  }
@@ -9341,7 +9697,7 @@ ComponentHandle.prototype.off = function(event, handler) {
9341
9697
  * @param {Function} handler - Handler function
9342
9698
  * @returns {Function} Unsubscribe function
9343
9699
  */
9344
- ComponentHandle.prototype.sub = function(topic, handler) {
9700
+ _chp.sub = function(topic, handler) {
9345
9701
  var unsub = bw.sub(topic, handler);
9346
9702
  this._subs.push(unsub);
9347
9703
  return unsub;
@@ -9352,10 +9708,10 @@ ComponentHandle.prototype.sub = function(topic, handler) {
9352
9708
  * @param {string} name - Action name
9353
9709
  * @param {...*} args - Arguments passed after comp
9354
9710
  */
9355
- ComponentHandle.prototype.action = function(name) {
9711
+ _chp.action = function(name) {
9356
9712
  var fn = this._actions[name];
9357
9713
  if (!fn) {
9358
- console.warn('ComponentHandle.action: unknown action "' + name + '"');
9714
+ _cw('ComponentHandle.action: unknown action "' + name + '"');
9359
9715
  return;
9360
9716
  }
9361
9717
  var args = [this].concat(Array.prototype.slice.call(arguments, 1));
@@ -9367,7 +9723,7 @@ ComponentHandle.prototype.action = function(name) {
9367
9723
  * @param {string} sel - CSS selector
9368
9724
  * @returns {Element|null}
9369
9725
  */
9370
- ComponentHandle.prototype.select = function(sel) {
9726
+ _chp.select = function(sel) {
9371
9727
  return this.element ? this.element.querySelector(sel) : null;
9372
9728
  };
9373
9729
 
@@ -9376,7 +9732,7 @@ ComponentHandle.prototype.select = function(sel) {
9376
9732
  * @param {string} sel - CSS selector
9377
9733
  * @returns {Element[]}
9378
9734
  */
9379
- ComponentHandle.prototype.selectAll = function(sel) {
9735
+ _chp.selectAll = function(sel) {
9380
9736
  if (!this.element) return [];
9381
9737
  return Array.prototype.slice.call(this.element.querySelectorAll(sel));
9382
9738
  };
@@ -9387,7 +9743,7 @@ ComponentHandle.prototype.selectAll = function(sel) {
9387
9743
  * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
9388
9744
  * @returns {ComponentHandle} this (for chaining)
9389
9745
  */
9390
- ComponentHandle.prototype.userTag = function(tag) {
9746
+ _chp.userTag = function(tag) {
9391
9747
  this._userTag = tag;
9392
9748
  if (this.element) {
9393
9749
  this.element.classList.add(tag);
@@ -9488,8 +9844,8 @@ bw.message = function(target, action, data) {
9488
9844
  }
9489
9845
  if (!el || !el._bwComponentHandle) return false;
9490
9846
  var comp = el._bwComponentHandle;
9491
- if (typeof comp[action] !== 'function') {
9492
- console.warn('bw.message: unknown action "' + action + '" on component ' + target);
9847
+ if (!_is(comp[action], 'function')) {
9848
+ _cw('bw.message: unknown action "' + action + '" on component ' + target);
9493
9849
  return false;
9494
9850
  }
9495
9851
  comp[action](data);
@@ -9526,7 +9882,7 @@ bw._builtinClientFunctions = {
9526
9882
  },
9527
9883
  focus: function(selector) {
9528
9884
  var el = bw._el(selector);
9529
- if (el && typeof el.focus === 'function') el.focus();
9885
+ if (el && _is(el.focus, 'function')) el.focus();
9530
9886
  },
9531
9887
  download: function(filename, content, mimeType) {
9532
9888
  if (typeof document === 'undefined') return;
@@ -9691,12 +10047,12 @@ bw.clientApply = function(msg) {
9691
10047
  } else if (type === 'remove') {
9692
10048
  var toRemove = bw._el(target);
9693
10049
  if (!toRemove) return false;
9694
- if (typeof bw.cleanup === 'function') bw.cleanup(toRemove);
10050
+ if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
9695
10051
  toRemove.remove();
9696
10052
  return true;
9697
10053
 
9698
10054
  } else if (type === 'batch') {
9699
- if (!Array.isArray(msg.ops)) return false;
10055
+ if (!_isA(msg.ops)) return false;
9700
10056
  var allOk = true;
9701
10057
  msg.ops.forEach(function(op) {
9702
10058
  if (!bw.clientApply(op)) allOk = false;
@@ -9712,26 +10068,26 @@ bw.clientApply = function(msg) {
9712
10068
  bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
9713
10069
  return true;
9714
10070
  } catch (e) {
9715
- console.error('[bw] register error:', msg.name, e);
10071
+ _ce('[bw] register error:', msg.name, e);
9716
10072
  return false;
9717
10073
  }
9718
10074
 
9719
10075
  } else if (type === 'call') {
9720
10076
  if (!msg.name) return false;
9721
10077
  var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
9722
- if (typeof fn !== 'function') return false;
10078
+ if (!_is(fn, 'function')) return false;
9723
10079
  try {
9724
- var args = Array.isArray(msg.args) ? msg.args : [];
10080
+ var args = _isA(msg.args) ? msg.args : [];
9725
10081
  fn.apply(null, args);
9726
10082
  return true;
9727
10083
  } catch (e) {
9728
- console.error('[bw] call error:', msg.name, e);
10084
+ _ce('[bw] call error:', msg.name, e);
9729
10085
  return false;
9730
10086
  }
9731
10087
 
9732
10088
  } else if (type === 'exec') {
9733
10089
  if (!bw._allowExec) {
9734
- console.warn('[bw] exec rejected: allowExec is not enabled');
10090
+ _cw('[bw] exec rejected: allowExec is not enabled');
9735
10091
  return false;
9736
10092
  }
9737
10093
  if (!msg.code) return false;
@@ -9739,7 +10095,7 @@ bw.clientApply = function(msg) {
9739
10095
  new Function(msg.code)();
9740
10096
  return true;
9741
10097
  } catch (e) {
9742
- console.error('[bw] exec error:', e);
10098
+ _ce('[bw] exec error:', e);
9743
10099
  return false;
9744
10100
  }
9745
10101
  }
@@ -9787,7 +10143,7 @@ bw.clientConnect = function(url, opts) {
9787
10143
 
9788
10144
  function handleMessage(data) {
9789
10145
  try {
9790
- var msg = typeof data === 'string' ? bw.clientParse(data) : data;
10146
+ var msg = _is(data, 'string') ? bw.clientParse(data) : data;
9791
10147
  if (onMessage) onMessage(msg);
9792
10148
  if (handlers.message) handlers.message(msg);
9793
10149
  bw.clientApply(msg);
@@ -9825,7 +10181,7 @@ bw.clientConnect = function(url, opts) {
9825
10181
  setStatus('connected');
9826
10182
  conn._pollTimer = setInterval(function() {
9827
10183
  fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
9828
- if (Array.isArray(msgs)) {
10184
+ if (_isA(msgs)) {
9829
10185
  msgs.forEach(handleMessage);
9830
10186
  } else if (msgs && msgs.type) {
9831
10187
  handleMessage(msgs);
@@ -9907,33 +10263,33 @@ bw.inspect = function(target) {
9907
10263
  el = target.element;
9908
10264
  comp = target;
9909
10265
  } else {
9910
- if (typeof target === 'string') {
10266
+ if (_is(target, 'string')) {
9911
10267
  el = bw.$(target)[0];
9912
10268
  }
9913
10269
  if (!el) {
9914
- console.warn('bw.inspect: element not found');
10270
+ _cw('bw.inspect: element not found');
9915
10271
  return null;
9916
10272
  }
9917
10273
  comp = el._bwComponentHandle;
9918
10274
  }
9919
10275
  if (!comp) {
9920
- console.log('bw.inspect: no ComponentHandle on this element');
9921
- console.log(' Tag:', el.tagName);
9922
- console.log(' Classes:', el.className);
9923
- console.log(' _bw_state:', el._bw_state || '(none)');
10276
+ _cl('bw.inspect: no ComponentHandle on this element');
10277
+ _cl(' Tag:', el.tagName);
10278
+ _cl(' Classes:', el.className);
10279
+ _cl(' _bw_state:', el._bw_state || '(none)');
9924
10280
  return null;
9925
10281
  }
9926
10282
  var deps = comp._bindings.reduce(function(s, b) {
9927
10283
  return s.concat(b.deps || []);
9928
10284
  }, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
9929
10285
  console.group('Component: ' + comp._bwId);
9930
- console.log('State:', comp._state);
9931
- console.log('Bindings:', comp._bindings.length, '(deps:', deps, ')');
9932
- console.log('Methods:', Object.keys(comp._methods));
9933
- console.log('Actions:', Object.keys(comp._actions));
9934
- console.log('User tag:', comp._userTag || '(none)');
9935
- console.log('Mounted:', comp.mounted);
9936
- console.log('Element:', comp.element);
10286
+ _cl('State:', comp._state);
10287
+ _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
10288
+ _cl('Methods:', _keys(comp._methods));
10289
+ _cl('Actions:', _keys(comp._actions));
10290
+ _cl('User tag:', comp._userTag || '(none)');
10291
+ _cl('Mounted:', comp.mounted);
10292
+ _cl('Element:', comp.element);
9937
10293
  console.groupEnd();
9938
10294
  return comp;
9939
10295
  };
@@ -9956,8 +10312,8 @@ bw.compile = function(taco) {
9956
10312
  // Pre-extract all binding expressions
9957
10313
  var precompiled = [];
9958
10314
  function walkExpressions(node) {
9959
- if (!node || typeof node !== 'object') return;
9960
- if (typeof node.c === 'string' && node.c.indexOf('${') >= 0) {
10315
+ if (!_is(node, 'object')) return;
10316
+ if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
9961
10317
  var parsed = bw._parseBindings(node.c);
9962
10318
  for (var i = 0; i < parsed.length; i++) {
9963
10319
  try {
@@ -9972,9 +10328,9 @@ bw.compile = function(taco) {
9972
10328
  }
9973
10329
  if (node.a) {
9974
10330
  for (var key in node.a) {
9975
- if (Object.prototype.hasOwnProperty.call(node.a, key)) {
10331
+ if (_hop.call(node.a, key)) {
9976
10332
  var v = node.a[key];
9977
- if (typeof v === 'string' && v.indexOf('${') >= 0) {
10333
+ if (_is(v, 'string') && v.indexOf('${') >= 0) {
9978
10334
  var parsed2 = bw._parseBindings(v);
9979
10335
  for (var j = 0; j < parsed2.length; j++) {
9980
10336
  try {
@@ -9990,9 +10346,9 @@ bw.compile = function(taco) {
9990
10346
  }
9991
10347
  }
9992
10348
  }
9993
- if (Array.isArray(node.c)) {
10349
+ if (_isA(node.c)) {
9994
10350
  for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
9995
- } else if (node.c && typeof node.c === 'object' && node.c.t) {
10351
+ } else if (_is(node.c, 'object') && node.c.t) {
9996
10352
  walkExpressions(node.c);
9997
10353
  }
9998
10354
  }
@@ -10004,7 +10360,7 @@ bw.compile = function(taco) {
10004
10360
  handle._precompiledBindings = precompiled;
10005
10361
  if (initialState) {
10006
10362
  for (var k in initialState) {
10007
- if (Object.prototype.hasOwnProperty.call(initialState, k)) {
10363
+ if (_hop.call(initialState, k)) {
10008
10364
  handle._state[k] = initialState[k];
10009
10365
  }
10010
10366
  }
@@ -10035,18 +10391,18 @@ bw.compile = function(taco) {
10035
10391
  bw.css = function(rules, options = {}) {
10036
10392
  const { minify = false, pretty = !minify } = options;
10037
10393
 
10038
- if (typeof rules === 'string') return rules;
10394
+ if (_is(rules, 'string')) return rules;
10039
10395
 
10040
10396
  let css = '';
10041
10397
  const indent = pretty ? ' ' : '';
10042
10398
  const newline = pretty ? '\n' : '';
10043
10399
  const space = pretty ? ' ' : '';
10044
10400
 
10045
- if (Array.isArray(rules)) {
10401
+ if (_isA(rules)) {
10046
10402
  css = rules.map(rule => bw.css(rule, options)).join(newline);
10047
- } else if (typeof rules === 'object') {
10403
+ } else if (_is(rules, 'object')) {
10048
10404
  Object.entries(rules).forEach(([selector, styles]) => {
10049
- if (typeof styles === 'object' && !Array.isArray(styles)) {
10405
+ if (_is(styles, 'object')) {
10050
10406
  // Handle @media, @keyframes, @supports — recurse into nested block
10051
10407
  if (selector.charAt(0) === '@') {
10052
10408
  const inner = bw.css(styles, options);
@@ -10095,7 +10451,7 @@ bw.css = function(rules, options = {}) {
10095
10451
  */
10096
10452
  bw.injectCSS = function(css, options = {}) {
10097
10453
  if (!bw._isBrowser) {
10098
- console.warn('bw.injectCSS requires a DOM environment');
10454
+ _cw('bw.injectCSS requires a DOM environment');
10099
10455
  return null;
10100
10456
  }
10101
10457
 
@@ -10112,7 +10468,7 @@ bw.injectCSS = function(css, options = {}) {
10112
10468
  }
10113
10469
 
10114
10470
  // Convert CSS if needed
10115
- const cssStr = typeof css === 'string' ? css : bw.css(css, options);
10471
+ const cssStr = _is(css, 'string') ? css : bw.css(css, options);
10116
10472
 
10117
10473
  // Set or append CSS
10118
10474
  if (append && styleEl.textContent) {
@@ -10142,7 +10498,7 @@ bw.s = function() {
10142
10498
  var result = {};
10143
10499
  for (var i = 0; i < arguments.length; i++) {
10144
10500
  var arg = arguments[i];
10145
- if (arg && typeof arg === 'object') Object.assign(result, arg);
10501
+ if (_is(arg, 'object')) Object.assign(result, arg);
10146
10502
  }
10147
10503
  return result;
10148
10504
  };
@@ -10265,7 +10621,7 @@ bw.u = {
10265
10621
  bw.responsive = function(selector, breakpoints) {
10266
10622
  var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
10267
10623
  var parts = [];
10268
- Object.keys(breakpoints).forEach(function(key) {
10624
+ _keys(breakpoints).forEach(function(key) {
10269
10625
  var rules = {};
10270
10626
  if (key === 'base') {
10271
10627
  rules[selector] = breakpoints[key];
@@ -10337,18 +10693,18 @@ if (bw._isBrowser) {
10337
10693
  if (!selector) return [];
10338
10694
 
10339
10695
  // Already an array
10340
- if (Array.isArray(selector)) return selector;
10696
+ if (_isA(selector)) return selector;
10341
10697
 
10342
10698
  // Single element
10343
10699
  if (selector.nodeType) return [selector];
10344
10700
 
10345
10701
  // NodeList or HTMLCollection
10346
- if (selector.length !== undefined && typeof selector !== 'string') {
10702
+ if (selector.length !== undefined && !_is(selector, 'string')) {
10347
10703
  return Array.from(selector);
10348
10704
  }
10349
10705
 
10350
10706
  // CSS selector string
10351
- if (typeof selector === 'string') {
10707
+ if (_is(selector, 'string')) {
10352
10708
  return Array.from(document.querySelectorAll(selector));
10353
10709
  }
10354
10710
 
@@ -10852,7 +11208,7 @@ bw.makeTable = function(config) {
10852
11208
 
10853
11209
  // Auto-detect columns if not provided
10854
11210
  const cols = columns || (data.length > 0
10855
- ? Object.keys(data[0]).map(key => ({ key, label: key }))
11211
+ ? _keys(data[0]).map(key => ({ key, label: key }))
10856
11212
  : []);
10857
11213
 
10858
11214
  // Current sort state
@@ -10867,7 +11223,7 @@ bw.makeTable = function(config) {
10867
11223
  const bVal = b[currentSortColumn];
10868
11224
 
10869
11225
  // Handle different types
10870
- if (typeof aVal === 'number' && typeof bVal === 'number') {
11226
+ if (_is(aVal, 'number') && _is(bVal, 'number')) {
10871
11227
  return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
10872
11228
  }
10873
11229
 
@@ -10977,7 +11333,7 @@ bw.makeTable = function(config) {
10977
11333
  bw.makeTableFromArray = function(config) {
10978
11334
  const { data = [], headerRow = true, columns, ...rest } = config;
10979
11335
 
10980
- if (!Array.isArray(data) || data.length === 0) {
11336
+ if (!_isA(data) || data.length === 0) {
10981
11337
  return bw.makeTable({ data: [], columns: columns || [], ...rest });
10982
11338
  }
10983
11339
 
@@ -11059,7 +11415,7 @@ bw.makeBarChart = function(config) {
11059
11415
  className = ''
11060
11416
  } = config;
11061
11417
 
11062
- if (!Array.isArray(data) || data.length === 0) {
11418
+ if (!_isA(data) || data.length === 0) {
11063
11419
  return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
11064
11420
  }
11065
11421
 
@@ -11208,7 +11564,7 @@ bw._componentRegistry = new Map();
11208
11564
  */
11209
11565
  bw.render = function(element, position, taco) {
11210
11566
  // Get target element
11211
- const targetEl = typeof element === 'string'
11567
+ const targetEl = _is(element, 'string')
11212
11568
  ? document.querySelector(element)
11213
11569
  : element;
11214
11570
 
@@ -11358,7 +11714,7 @@ bw.render = function(element, position, taco) {
11358
11714
  setContent(content) {
11359
11715
  this._taco.c = content;
11360
11716
  if (this.element) {
11361
- if (typeof content === 'string') {
11717
+ if (_is(content, 'string')) {
11362
11718
  this.element.textContent = content;
11363
11719
  } else {
11364
11720
  // Re-render for complex content