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
package/src/bitwrench.js CHANGED
@@ -80,7 +80,7 @@ const bw = {
80
80
  __monkey_patch_is_nodejs__: {
81
81
  _value: 'ignore',
82
82
  set: function(x) {
83
- this._value = (typeof x === 'boolean') ? x : 'ignore';
83
+ this._value = _is(x, 'boolean') ? x : 'ignore';
84
84
  },
85
85
  get: function() {
86
86
  return this._value;
@@ -128,6 +128,67 @@ Object.defineProperty(bw, '_isBrowser', {
128
128
  configurable: true
129
129
  });
130
130
 
131
+ // ── Internal aliases ─────────────────────────────────────────────────────
132
+ // Short names for frequently-used builtins and internal methods.
133
+ // Same pattern as v1 (_to = bw.typeOf, etc.).
134
+ //
135
+ // Why: Terser can't shorten global property chains (console.warn,
136
+ // Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
137
+ // because it can't prove they're side-effect-free. We can, so we alias
138
+ // them here. Each alias saves bytes in the minified output, and the short
139
+ // names also reduce visual noise in the hot paths (binding pipeline,
140
+ // createDOM, etc.).
141
+ //
142
+ // Alias Target Sites
143
+ // ───────── ────────────────────────────────────── ─────
144
+ // _hop Object.prototype.hasOwnProperty 15
145
+ // _isA Array.isArray 25
146
+ // _keys Object.keys 7
147
+ // _to bw.typeOf (type string) 26
148
+ // _is type check boolean: _is(x,'string') ~50
149
+ // _cw console.warn 8
150
+ // _cl console.log 11
151
+ // _ce console.error 4
152
+ // _chp ComponentHandle.prototype 28 (defined after constructor)
153
+ //
154
+ // Note: document.createElement etc. are NOT aliased because they require
155
+ // `this === document` and .bind() would add overhead on every call.
156
+ // Console aliases use thin wrappers (not direct refs) so test monkey-
157
+ // patching of console.warn/log/error continues to work.
158
+ //
159
+ // `typeof x` for UNDECLARED globals (window, document, process, require,
160
+ // EventSource, navigator, Promise, __filename, import.meta) MUST stay as
161
+ // raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
162
+ //
163
+ // ── v1 functional type helpers (kept for reference, not currently used) ──
164
+ // _toa(x, type, trueVal, falseVal) — bw.typeAssign:
165
+ // returns trueVal if _to(x)===type, else falseVal.
166
+ // Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
167
+ // _toc(x, type, trueVal, falseVal) — bw.typeConvert:
168
+ // same as _toa but if trueVal/falseVal are functions, calls them with x.
169
+ // Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
170
+ // Uncomment if pattern frequency justifies them:
171
+ // var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
172
+ // 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); };
173
+ // ─────────────────────────────────────────────────────────────────────────
174
+ var _hop = Object.prototype.hasOwnProperty;
175
+ var _isA = Array.isArray;
176
+ var _keys = Object.keys;
177
+ var _to = _typeOf; // imported from bitwrench-utils.js
178
+ var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
179
+ // Console aliases use thin wrappers (not direct references) so that test
180
+ // code can monkey-patch console.warn/log/error and the patches take effect.
181
+ var _cw = function() { console.warn.apply(console, arguments); };
182
+ var _cl = function() { console.log.apply(console, arguments); };
183
+ var _ce = function() { console.error.apply(console, arguments); };
184
+
185
+ /**
186
+ * Debug flag. When true, emits console.warn for silent binding failures
187
+ * (missing paths, null refs, auto-created intermediate objects).
188
+ * @type {boolean}
189
+ */
190
+ bw.debug = false;
191
+
131
192
  /**
132
193
  * Lazy-resolve Node.js `fs` module.
133
194
  * Tries require('fs') first (available in CJS/UMD Node.js builds),
@@ -275,7 +336,7 @@ bw.uuid = function(prefix) {
275
336
  */
276
337
  bw._el = function(id) {
277
338
  // Pass-through for DOM elements
278
- if (typeof id !== 'string') return id || null;
339
+ if (!_is(id, 'string')) return id || null;
279
340
  if (!id) return null;
280
341
  if (!bw._isBrowser) return null;
281
342
 
@@ -371,7 +432,7 @@ bw._deregisterNode = function(el, bwId) {
371
432
  * // => '<b>Hello</b> & "world"'
372
433
  */
373
434
  bw.escapeHTML = function(str) {
374
- if (typeof str !== 'string') return '';
435
+ if (!_is(str, 'string')) return '';
375
436
 
376
437
  const escapeMap = {
377
438
  '&': '&',
@@ -444,7 +505,7 @@ bw.html = function(taco, options = {}) {
444
505
  }
445
506
 
446
507
  // Handle arrays of TACOs
447
- if (Array.isArray(taco)) {
508
+ if (_isA(taco)) {
448
509
  return taco.map(t => bw.html(t, options)).join('');
449
510
  }
450
511
 
@@ -467,15 +528,15 @@ bw.html = function(taco, options = {}) {
467
528
  if (taco && taco._bwEach && options.state) {
468
529
  var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
469
530
  var arr = bw._evaluatePath(options.state, eachExpr);
470
- if (!Array.isArray(arr)) return '';
531
+ if (!_isA(arr)) return '';
471
532
  return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
472
533
  }
473
534
 
474
535
  // Handle primitives and non-TACO objects
475
- if (typeof taco !== 'object' || !taco.t) {
536
+ if (!_is(taco, 'object') || !taco.t) {
476
537
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
477
538
  // Resolve template bindings if state provided
478
- if (options.state && typeof str === 'string' && str.indexOf('${') >= 0) {
539
+ if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
479
540
  str = bw._resolveTemplate(str, options.state, !!options.compile);
480
541
  }
481
542
  return str;
@@ -495,10 +556,18 @@ bw.html = function(taco, options = {}) {
495
556
  // Skip null, undefined, false
496
557
  if (value == null || value === false) continue;
497
558
 
498
- // Skip event handlers (they're for DOM only)
499
- if (key.startsWith('on')) continue;
559
+ // Serialize event handlers via funcRegister
560
+ if (key.startsWith('on')) {
561
+ if (_is(value, 'function')) {
562
+ var fnId = bw.funcRegister(value);
563
+ attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
564
+ } else if (_is(value, 'string')) {
565
+ attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
566
+ }
567
+ continue;
568
+ }
500
569
 
501
- if (key === 'style' && typeof value === 'object') {
570
+ if (key === 'style' && _is(value, 'object')) {
502
571
  // Convert style object to string
503
572
  const styleStr = Object.entries(value)
504
573
  .filter(([, v]) => v != null)
@@ -509,7 +578,7 @@ bw.html = function(taco, options = {}) {
509
578
  }
510
579
  } else if (key === 'class') {
511
580
  // Handle class as array or string
512
- const classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
581
+ const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
513
582
  if (classStr) {
514
583
  attrStr += ` class="${bw.escapeHTML(classStr)}"`;
515
584
  }
@@ -545,13 +614,184 @@ bw.html = function(taco, options = {}) {
545
614
  // Process content recursively
546
615
  let contentStr = content != null ? bw.html(content, options) : '';
547
616
  // Resolve template bindings in content if state provided
548
- if (options.state && typeof contentStr === 'string' && contentStr.indexOf('${') >= 0) {
617
+ if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
549
618
  contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
550
619
  }
551
620
 
552
621
  return `<${tag}${attrStr}>${contentStr}</${tag}>`;
553
622
  };
554
623
 
624
+ /**
625
+ * Generate a complete, self-contained HTML document from TACO content.
626
+ *
627
+ * Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
628
+ * func registry emission (so serialized event handlers work), optional theme,
629
+ * and extra head elements. Designed for static site generation, offline/airgapped
630
+ * use, and the "static site that isn't static" workflow.
631
+ *
632
+ * @param {Object} [opts={}] - Page options
633
+ * @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
634
+ * @param {string} [opts.title='bitwrench'] - Page title
635
+ * @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
636
+ * @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
637
+ * @param {string} [opts.css=''] - Additional CSS for <style> block
638
+ * @param {string|Object} [opts.theme=null] - Theme preset name or config object
639
+ * @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
640
+ * @param {string} [opts.favicon=''] - Favicon URL
641
+ * @param {string} [opts.lang='en'] - HTML lang attribute
642
+ * @returns {string} Complete HTML document string
643
+ * @category DOM Generation
644
+ * @see bw.html
645
+ * @example
646
+ * bw.htmlPage({
647
+ * title: 'My App',
648
+ * body: { t: 'h1', c: 'Hello World' },
649
+ * runtime: 'shim'
650
+ * })
651
+ */
652
+ bw.htmlPage = function(opts) {
653
+ opts = opts || {};
654
+ var title = opts.title || 'bitwrench';
655
+ var body = opts.body || '';
656
+ var state = opts.state || undefined;
657
+ var runtime = opts.runtime || 'shim';
658
+ var css = opts.css || '';
659
+ var theme = opts.theme || null;
660
+ var headExtra = opts.head || [];
661
+ var favicon = opts.favicon || '';
662
+ var lang = opts.lang || 'en';
663
+
664
+ // Snapshot funcRegistry counter before rendering
665
+ var fnCounterBefore = bw._fnIDCounter;
666
+
667
+ // Render body content
668
+ var bodyHTML = '';
669
+ if (_is(body, 'string')) {
670
+ bodyHTML = body;
671
+ } else {
672
+ var htmlOpts = {};
673
+ if (state) htmlOpts.state = state;
674
+ bodyHTML = bw.html(body, htmlOpts);
675
+ }
676
+
677
+ // Collect functions registered during this render
678
+ var fnCounterAfter = bw._fnIDCounter;
679
+ var registryEntries = '';
680
+ for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
681
+ var fnKey = 'bw_fn_' + i;
682
+ if (bw._fnRegistry[fnKey]) {
683
+ registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
684
+ bw._fnRegistry[fnKey].toString() + ';\n';
685
+ }
686
+ }
687
+
688
+ // Build runtime script for <head>
689
+ var runtimeHead = '';
690
+ if (runtime === 'inline') {
691
+ // Read UMD bundle synchronously if in Node.js
692
+ var umdSource = null;
693
+ if (bw._isNode) {
694
+ try {
695
+ var fs = (typeof require === 'function') ? require('fs') : null;
696
+ var pathMod = (typeof require === 'function') ? require('path') : null;
697
+ if (fs && pathMod) {
698
+ // Resolve dist/ relative to this source file
699
+ var srcDir = '';
700
+ try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
701
+ catch(e2) { /* ESM: __filename not available */ }
702
+ if (!srcDir && typeof import.meta !== 'undefined' && import.meta.url) {
703
+ var url = (typeof require === 'function') ? require('url') : null;
704
+ if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath(import.meta.url));
705
+ }
706
+ if (srcDir) {
707
+ var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
708
+ umdSource = fs.readFileSync(distPath, 'utf8');
709
+ }
710
+ }
711
+ } catch(e) { /* fall through */ }
712
+ }
713
+ if (umdSource) {
714
+ runtimeHead = '<script>' + umdSource + '</script>';
715
+ } else {
716
+ // Fallback to shim in browser or if dist not available
717
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
718
+ }
719
+ } else if (runtime === 'cdn') {
720
+ runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
721
+ } else if (runtime === 'shim') {
722
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
723
+ }
724
+ // runtime === 'none' → empty
725
+
726
+ // Theme CSS
727
+ var themeCSS = '';
728
+ if (theme) {
729
+ var themeConfig = _is(theme, 'string')
730
+ ? (THEME_PRESETS[theme.toLowerCase()] || null)
731
+ : theme;
732
+ if (themeConfig) {
733
+ var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, { inject: false }));
734
+ themeCSS = themeResult.css;
735
+ }
736
+ }
737
+
738
+ // Extra <head> elements
739
+ var headHTML = '';
740
+ if (_isA(headExtra) && headExtra.length > 0) {
741
+ headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
742
+ }
743
+
744
+ // Favicon
745
+ var faviconTag = '';
746
+ if (favicon) {
747
+ var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
748
+ return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c];
749
+ });
750
+ faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
751
+ }
752
+
753
+ // Escaped title
754
+ var safeTitle = bw.escapeHTML(title);
755
+
756
+ // Combine all CSS
757
+ var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
758
+
759
+ // Body-end script: registry entries + optional loadDefaultStyles
760
+ var bodyEndScript = '';
761
+ var bodyEndParts = [];
762
+ if (registryEntries) {
763
+ bodyEndParts.push(registryEntries);
764
+ }
765
+ if (runtime === 'inline' || runtime === 'cdn') {
766
+ bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
767
+ }
768
+ if (bodyEndParts.length > 0) {
769
+ bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
770
+ }
771
+
772
+ // Assemble document
773
+ var parts = [
774
+ '<!DOCTYPE html>',
775
+ '<html lang="' + lang + '">',
776
+ '<head>',
777
+ '<meta charset="UTF-8">',
778
+ '<meta name="viewport" content="width=device-width, initial-scale=1">'
779
+ ];
780
+ parts.push('<title>' + safeTitle + '</title>');
781
+ if (faviconTag) parts.push(faviconTag);
782
+ if (runtimeHead) parts.push(runtimeHead);
783
+ if (headHTML) parts.push(headHTML);
784
+ if (allCSS) parts.push('<style>' + allCSS + '</style>');
785
+ parts.push('</head>');
786
+ parts.push('<body>');
787
+ parts.push(bodyHTML);
788
+ if (bodyEndScript) parts.push(bodyEndScript);
789
+ parts.push('</body>');
790
+ parts.push('</html>');
791
+
792
+ return parts.join('\n');
793
+ };
794
+
555
795
  /**
556
796
  * Create a live DOM element from a TACO object (browser only).
557
797
  *
@@ -596,7 +836,7 @@ bw.createDOM = function(taco, options = {}) {
596
836
  }
597
837
 
598
838
  // Handle text nodes
599
- if (typeof taco !== 'object' || !taco.t) {
839
+ if (!_is(taco, 'object') || !taco.t) {
600
840
  return document.createTextNode(String(taco));
601
841
  }
602
842
 
@@ -609,16 +849,16 @@ bw.createDOM = function(taco, options = {}) {
609
849
  for (const [key, value] of Object.entries(attrs)) {
610
850
  if (value == null || value === false) continue;
611
851
 
612
- if (key === 'style' && typeof value === 'object') {
852
+ if (key === 'style' && _is(value, 'object')) {
613
853
  // Apply styles directly
614
854
  Object.assign(el.style, value);
615
855
  } else if (key === 'class') {
616
856
  // Handle class as array or string
617
- const classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
857
+ const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
618
858
  if (classStr) {
619
859
  el.className = classStr;
620
860
  }
621
- } else if (key.startsWith('on') && typeof value === 'function') {
861
+ } else if (key.startsWith('on') && _is(value, 'function')) {
622
862
  // Event handlers
623
863
  const eventName = key.slice(2).toLowerCase();
624
864
  el.addEventListener(eventName, value);
@@ -638,7 +878,7 @@ bw.createDOM = function(taco, options = {}) {
638
878
  // Children with data-bw_id or id attributes get local refs on the parent,
639
879
  // so o.render functions can access them without any DOM lookup.
640
880
  if (content != null) {
641
- if (Array.isArray(content)) {
881
+ if (_isA(content)) {
642
882
  content.forEach(child => {
643
883
  if (child != null) {
644
884
  // Handle ComponentHandle in content arrays (Level 2 children)
@@ -658,20 +898,20 @@ bw.createDOM = function(taco, options = {}) {
658
898
  if (childEl._bw_refs) {
659
899
  if (!el._bw_refs) el._bw_refs = {};
660
900
  for (var rk in childEl._bw_refs) {
661
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
901
+ if (_hop.call(childEl._bw_refs, rk)) {
662
902
  el._bw_refs[rk] = childEl._bw_refs[rk];
663
903
  }
664
904
  }
665
905
  }
666
906
  }
667
907
  });
668
- } else if (typeof content === 'object' && content.__bw_raw) {
908
+ } else if (_is(content, 'object') && content.__bw_raw) {
669
909
  // Raw HTML content — inject via innerHTML
670
910
  el.innerHTML = content.v;
671
911
  } else if (content._bwComponent === true) {
672
912
  // Single ComponentHandle as content
673
913
  content.mount(el);
674
- } else if (typeof content === 'object' && content.t) {
914
+ } else if (_is(content, 'object') && content.t) {
675
915
  var childEl = bw.createDOM(content, options);
676
916
  el.appendChild(childEl);
677
917
  var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
@@ -682,7 +922,7 @@ bw.createDOM = function(taco, options = {}) {
682
922
  if (childEl._bw_refs) {
683
923
  if (!el._bw_refs) el._bw_refs = {};
684
924
  for (var rk in childEl._bw_refs) {
685
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
925
+ if (_hop.call(childEl._bw_refs, rk)) {
686
926
  el._bw_refs[rk] = childEl._bw_refs[rk];
687
927
  }
688
928
  }
@@ -715,7 +955,7 @@ bw.createDOM = function(taco, options = {}) {
715
955
  el._bw_render = opts.render;
716
956
 
717
957
  if (opts.mounted) {
718
- console.warn('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
958
+ _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
719
959
  }
720
960
 
721
961
  // Queue initial render (same timing as mounted)
@@ -788,7 +1028,7 @@ bw.DOM = function(target, taco, options = {}) {
788
1028
  const targetEl = bw._el(target);
789
1029
 
790
1030
  if (!targetEl) {
791
- console.error('bw.DOM: Target element not found:', target);
1031
+ _ce('bw.DOM: Target element not found:', target);
792
1032
  return null;
793
1033
  }
794
1034
 
@@ -828,7 +1068,7 @@ bw.DOM = function(target, taco, options = {}) {
828
1068
  targetEl.appendChild(taco.element);
829
1069
  }
830
1070
  // Handle arrays
831
- else if (Array.isArray(taco)) {
1071
+ else if (_isA(taco)) {
832
1072
  taco.forEach(t => {
833
1073
  if (t != null) {
834
1074
  if (t._bwComponent === true) {
@@ -864,7 +1104,7 @@ bw.DOM = function(target, taco, options = {}) {
864
1104
  bw.compileProps = function(handle, props = {}) {
865
1105
  const compiledProps = {};
866
1106
 
867
- Object.keys(props).forEach(key => {
1107
+ _keys(props).forEach(key => {
868
1108
  // Create getter/setter for each prop
869
1109
  Object.defineProperty(compiledProps, key, {
870
1110
  get() {
@@ -1182,17 +1422,17 @@ bw.patch = function(id, content, attr) {
1182
1422
  if (attr) {
1183
1423
  // Patch an attribute
1184
1424
  el.setAttribute(attr, String(content));
1185
- } else if (Array.isArray(content)) {
1425
+ } else if (_isA(content)) {
1186
1426
  // Patch with array of children (strings and/or TACOs)
1187
1427
  el.innerHTML = '';
1188
1428
  content.forEach(function(item) {
1189
- if (typeof item === 'string' || typeof item === 'number') {
1429
+ if (_is(item, 'string') || _is(item, 'number')) {
1190
1430
  el.appendChild(document.createTextNode(String(item)));
1191
1431
  } else if (item && item.t) {
1192
1432
  el.appendChild(bw.createDOM(item));
1193
1433
  }
1194
1434
  });
1195
- } else if (typeof content === 'object' && content !== null && content.t) {
1435
+ } else if (_is(content, 'object') && content.t) {
1196
1436
  // Patch with a TACO — replace children
1197
1437
  el.innerHTML = '';
1198
1438
  el.appendChild(bw.createDOM(content));
@@ -1223,7 +1463,7 @@ bw.patch = function(id, content, attr) {
1223
1463
  bw.patchAll = function(patches) {
1224
1464
  var results = {};
1225
1465
  for (var id in patches) {
1226
- if (Object.prototype.hasOwnProperty.call(patches, id)) {
1466
+ if (_hop.call(patches, id)) {
1227
1467
  results[id] = bw.patch(id, patches[id]);
1228
1468
  }
1229
1469
  }
@@ -1320,7 +1560,7 @@ bw.pub = function(topic, detail) {
1320
1560
  snapshot[i].handler(detail);
1321
1561
  called++;
1322
1562
  } catch (err) {
1323
- console.warn('bw.pub: subscriber error on topic "' + topic + '":', err);
1563
+ _cw('bw.pub: subscriber error on topic "' + topic + '":', err);
1324
1564
  }
1325
1565
  }
1326
1566
  return called;
@@ -1416,8 +1656,8 @@ bw._fnIDCounter = 0;
1416
1656
  * @see bw.funcGetDispatchStr
1417
1657
  */
1418
1658
  bw.funcRegister = function(fn, name) {
1419
- if (typeof fn !== 'function') return '';
1420
- var fnID = (typeof name === 'string' && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
1659
+ if (!_is(fn, 'function')) return '';
1660
+ var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
1421
1661
  bw._fnRegistry[fnID] = fn;
1422
1662
  return fnID;
1423
1663
  };
@@ -1436,7 +1676,7 @@ bw.funcRegister = function(fn, name) {
1436
1676
  bw.funcGetById = function(name, errFn) {
1437
1677
  name = String(name);
1438
1678
  if (name in bw._fnRegistry) return bw._fnRegistry[name];
1439
- return (typeof errFn === 'function') ? errFn : function() { console.warn('bw.funcGetById: unregistered fn "' + name + '"'); };
1679
+ return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
1440
1680
  };
1441
1681
 
1442
1682
  /**
@@ -1477,13 +1717,30 @@ bw.funcUnregister = function(name) {
1477
1717
  bw.funcGetRegistry = function() {
1478
1718
  var copy = {};
1479
1719
  for (var k in bw._fnRegistry) {
1480
- if (Object.prototype.hasOwnProperty.call(bw._fnRegistry, k)) {
1720
+ if (_hop.call(bw._fnRegistry, k)) {
1481
1721
  copy[k] = bw._fnRegistry[k];
1482
1722
  }
1483
1723
  }
1484
1724
  return copy;
1485
1725
  };
1486
1726
 
1727
+ /**
1728
+ * Minimal runtime shim for funcRegister dispatch in static HTML.
1729
+ * When embedded in a `<script>` tag, provides just enough infrastructure
1730
+ * for `bw.funcGetById()` calls to resolve. The actual function bodies
1731
+ * are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
1732
+ * @type {string}
1733
+ * @category Function Registry
1734
+ */
1735
+ bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
1736
+ 'if(!bw._fnRegistry)bw._fnRegistry={};' +
1737
+ 'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
1738
+ 'console.warn("bw: unregistered fn "+n)};};' +
1739
+ 'bw.funcRegister=function(fn,name){' +
1740
+ 'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
1741
+ 'bw._fnRegistry[id]=fn;return id;};' +
1742
+ 'window.bw=bw;})();';
1743
+
1487
1744
  // ===================================================================================
1488
1745
  // Template Binding Utilities
1489
1746
  // ===================================================================================
@@ -1511,7 +1768,10 @@ bw._evaluatePath = function(state, path) {
1511
1768
  var parts = path.split('.');
1512
1769
  var val = state;
1513
1770
  for (var i = 0; i < parts.length; i++) {
1514
- if (val == null) return '';
1771
+ if (val == null) {
1772
+ if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
1773
+ return '';
1774
+ }
1515
1775
  val = val[parts[i]];
1516
1776
  }
1517
1777
  return (val == null) ? '' : val;
@@ -1531,7 +1791,7 @@ bw._evaluatePath = function(state, path) {
1531
1791
  */
1532
1792
  bw._compiledExprs = {};
1533
1793
  bw._resolveTemplate = function(str, state, compile) {
1534
- if (typeof str !== 'string' || str.indexOf('${') < 0) return str;
1794
+ if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
1535
1795
  var bindings = bw._parseBindings(str);
1536
1796
  if (bindings.length === 0) return str;
1537
1797
 
@@ -1553,6 +1813,7 @@ bw._resolveTemplate = function(str, state, compile) {
1553
1813
  try {
1554
1814
  val = bw._compiledExprs[b.expr](state);
1555
1815
  } catch (e) {
1816
+ if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
1556
1817
  val = '';
1557
1818
  }
1558
1819
  } else {
@@ -1661,7 +1922,7 @@ function ComponentHandle(taco) {
1661
1922
  this._state = {};
1662
1923
  if (o.state) {
1663
1924
  for (var k in o.state) {
1664
- if (Object.prototype.hasOwnProperty.call(o.state, k)) {
1925
+ if (_hop.call(o.state, k)) {
1665
1926
  this._state[k] = o.state[k];
1666
1927
  }
1667
1928
  }
@@ -1670,7 +1931,7 @@ function ComponentHandle(taco) {
1670
1931
  this._actions = {};
1671
1932
  if (o.actions) {
1672
1933
  for (var k2 in o.actions) {
1673
- if (Object.prototype.hasOwnProperty.call(o.actions, k2)) {
1934
+ if (_hop.call(o.actions, k2)) {
1674
1935
  this._actions[k2] = o.actions[k2];
1675
1936
  }
1676
1937
  }
@@ -1680,7 +1941,7 @@ function ComponentHandle(taco) {
1680
1941
  if (o.methods) {
1681
1942
  var self = this;
1682
1943
  for (var k3 in o.methods) {
1683
- if (Object.prototype.hasOwnProperty.call(o.methods, k3)) {
1944
+ if (_hop.call(o.methods, k3)) {
1684
1945
  this._methods[k3] = o.methods[k3];
1685
1946
  (function(methodName, methodFn) {
1686
1947
  self[methodName] = function() {
@@ -1713,14 +1974,23 @@ function ComponentHandle(taco) {
1713
1974
  this._compile = !!o.compile;
1714
1975
  this._bw_refs = {};
1715
1976
  this._refCounter = 0;
1977
+ // Child component ownership (Bug #5)
1978
+ this._children = [];
1979
+ this._parent = null;
1980
+ // Factory metadata for BCCL rebuild (Bug #6)
1981
+ this._factory = taco._bwFactory || null;
1716
1982
  }
1717
1983
 
1984
+ // Short alias for ComponentHandle.prototype (see alias block at top of file).
1985
+ // 28 method definitions × 25 chars = ~700B raw savings in minified output.
1986
+ var _chp = ComponentHandle.prototype;
1987
+
1718
1988
  // ── State Methods ──
1719
1989
 
1720
1990
  /**
1721
1991
  * Get a state value. Dot-path supported: `get('user.name')`
1722
1992
  */
1723
- ComponentHandle.prototype.get = function(key) {
1993
+ _chp.get = function(key) {
1724
1994
  return bw._evaluatePath(this._state, key);
1725
1995
  };
1726
1996
 
@@ -1730,12 +2000,13 @@ ComponentHandle.prototype.get = function(key) {
1730
2000
  * @param {*} value - New value
1731
2001
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
1732
2002
  */
1733
- ComponentHandle.prototype.set = function(key, value, opts) {
2003
+ _chp.set = function(key, value, opts) {
1734
2004
  // Dot-path set
1735
2005
  var parts = key.split('.');
1736
2006
  var obj = this._state;
1737
2007
  for (var i = 0; i < parts.length - 1; i++) {
1738
- if (obj[parts[i]] == null || typeof obj[parts[i]] !== 'object') {
2008
+ if (!_is(obj[parts[i]], 'object')) {
2009
+ if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
1739
2010
  obj[parts[i]] = {};
1740
2011
  }
1741
2012
  obj = obj[parts[i]];
@@ -1755,10 +2026,10 @@ ComponentHandle.prototype.set = function(key, value, opts) {
1755
2026
  /**
1756
2027
  * Get a shallow clone of the full state.
1757
2028
  */
1758
- ComponentHandle.prototype.getState = function() {
2029
+ _chp.getState = function() {
1759
2030
  var clone = {};
1760
2031
  for (var k in this._state) {
1761
- if (Object.prototype.hasOwnProperty.call(this._state, k)) {
2032
+ if (_hop.call(this._state, k)) {
1762
2033
  clone[k] = this._state[k];
1763
2034
  }
1764
2035
  }
@@ -1770,9 +2041,9 @@ ComponentHandle.prototype.getState = function() {
1770
2041
  * @param {Object} updates - Key-value pairs to merge
1771
2042
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
1772
2043
  */
1773
- ComponentHandle.prototype.setState = function(updates, opts) {
2044
+ _chp.setState = function(updates, opts) {
1774
2045
  for (var k in updates) {
1775
- if (Object.prototype.hasOwnProperty.call(updates, k)) {
2046
+ if (_hop.call(updates, k)) {
1776
2047
  this._state[k] = updates[k];
1777
2048
  this._dirtyKeys[k] = true;
1778
2049
  }
@@ -1789,9 +2060,9 @@ ComponentHandle.prototype.setState = function(updates, opts) {
1789
2060
  /**
1790
2061
  * Push a value onto an array in state. Clones the array.
1791
2062
  */
1792
- ComponentHandle.prototype.push = function(key, val) {
2063
+ _chp.push = function(key, val) {
1793
2064
  var arr = this.get(key);
1794
- var newArr = Array.isArray(arr) ? arr.slice() : [];
2065
+ var newArr = _isA(arr) ? arr.slice() : [];
1795
2066
  newArr.push(val);
1796
2067
  this.set(key, newArr);
1797
2068
  };
@@ -1799,9 +2070,9 @@ ComponentHandle.prototype.push = function(key, val) {
1799
2070
  /**
1800
2071
  * Splice an array in state. Clones the array.
1801
2072
  */
1802
- ComponentHandle.prototype.splice = function(key, start, deleteCount) {
2073
+ _chp.splice = function(key, start, deleteCount) {
1803
2074
  var arr = this.get(key);
1804
- var newArr = Array.isArray(arr) ? arr.slice() : [];
2075
+ var newArr = _isA(arr) ? arr.slice() : [];
1805
2076
  var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
1806
2077
  Array.prototype.splice.apply(newArr, args);
1807
2078
  this.set(key, newArr);
@@ -1809,7 +2080,7 @@ ComponentHandle.prototype.splice = function(key, start, deleteCount) {
1809
2080
 
1810
2081
  // ── Scheduling ──
1811
2082
 
1812
- ComponentHandle.prototype._scheduleDirty = function() {
2083
+ _chp._scheduleDirty = function() {
1813
2084
  if (!this._scheduled) {
1814
2085
  this._scheduled = true;
1815
2086
  bw._dirtyComponents.push(this);
@@ -1824,17 +2095,17 @@ ComponentHandle.prototype._scheduleDirty = function() {
1824
2095
  * Creates binding descriptors with refIds for targeted DOM updates.
1825
2096
  * @private
1826
2097
  */
1827
- ComponentHandle.prototype._compileBindings = function() {
2098
+ _chp._compileBindings = function() {
1828
2099
  this._bindings = [];
1829
2100
  this._refCounter = 0;
1830
- var stateKeys = Object.keys(this._state);
2101
+ var stateKeys = _keys(this._state);
1831
2102
  var self = this;
1832
2103
 
1833
2104
  function walkTaco(taco, path) {
1834
- if (taco == null || typeof taco !== 'object' || !taco.t) return taco;
2105
+ if (!_is(taco, 'object') || !taco.t) return taco;
1835
2106
 
1836
2107
  // Check content for bindings
1837
- if (typeof taco.c === 'string' && taco.c.indexOf('${') >= 0) {
2108
+ if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
1838
2109
  var refId = 'bw_ref_' + self._refCounter++;
1839
2110
  var parsed = bw._parseBindings(taco.c);
1840
2111
  var deps = [];
@@ -1856,10 +2127,10 @@ ComponentHandle.prototype._compileBindings = function() {
1856
2127
  // Check attributes for bindings
1857
2128
  if (taco.a) {
1858
2129
  for (var attrName in taco.a) {
1859
- if (!Object.prototype.hasOwnProperty.call(taco.a, attrName)) continue;
2130
+ if (!_hop.call(taco.a, attrName)) continue;
1860
2131
  if (attrName === 'data-bw_ref') continue;
1861
2132
  var attrVal = taco.a[attrName];
1862
- if (typeof attrVal === 'string' && attrVal.indexOf('${') >= 0) {
2133
+ if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
1863
2134
  var refId2 = 'bw_ref_' + self._refCounter++;
1864
2135
  var parsed2 = bw._parseBindings(attrVal);
1865
2136
  var deps2 = [];
@@ -1885,9 +2156,27 @@ ComponentHandle.prototype._compileBindings = function() {
1885
2156
  }
1886
2157
 
1887
2158
  // Recurse into children
1888
- if (Array.isArray(taco.c)) {
2159
+ if (_isA(taco.c)) {
1889
2160
  for (var i = 0; i < taco.c.length; i++) {
1890
- if (taco.c[i] && typeof taco.c[i] === 'object' && taco.c[i].t) {
2161
+ // Wrap string children with ${expr} in a span so patches target the span, not the parent
2162
+ if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
2163
+ var mixedRefId = 'bw_ref_' + self._refCounter++;
2164
+ var mixedParsed = bw._parseBindings(taco.c[i]);
2165
+ var mixedDeps = [];
2166
+ for (var mi = 0; mi < mixedParsed.length; mi++) {
2167
+ mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
2168
+ }
2169
+ self._bindings.push({
2170
+ expr: taco.c[i],
2171
+ type: 'content',
2172
+ refId: mixedRefId,
2173
+ deps: mixedDeps,
2174
+ template: taco.c[i]
2175
+ });
2176
+ // Replace string with a span wrapper so textContent targets the span only
2177
+ taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
2178
+ }
2179
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
1891
2180
  walkTaco(taco.c[i], path.concat(i));
1892
2181
  }
1893
2182
  // Handle bw.when/bw.each markers
@@ -1922,7 +2211,7 @@ ComponentHandle.prototype._compileBindings = function() {
1922
2211
  taco.c[i]._refId = eachRefId;
1923
2212
  }
1924
2213
  }
1925
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
2214
+ } else if (_is(taco.c, 'object') && taco.c.t) {
1926
2215
  walkTaco(taco.c, path.concat(0));
1927
2216
  }
1928
2217
 
@@ -1938,7 +2227,7 @@ ComponentHandle.prototype._compileBindings = function() {
1938
2227
  * Build ref map from the live DOM after createDOM.
1939
2228
  * @private
1940
2229
  */
1941
- ComponentHandle.prototype._collectRefs = function() {
2230
+ _chp._collectRefs = function() {
1942
2231
  this._bw_refs = {};
1943
2232
  if (!this.element) return;
1944
2233
  var els = this.element.querySelectorAll('[data-bw_ref]');
@@ -1959,7 +2248,7 @@ ComponentHandle.prototype._collectRefs = function() {
1959
2248
  * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
1960
2249
  * @param {Element} parentEl - DOM element to mount into
1961
2250
  */
1962
- ComponentHandle.prototype.mount = function(parentEl) {
2251
+ _chp.mount = function(parentEl) {
1963
2252
  // willMount hook
1964
2253
  if (this._hooks.willMount) this._hooks.willMount(this);
1965
2254
 
@@ -1981,7 +2270,7 @@ ComponentHandle.prototype.mount = function(parentEl) {
1981
2270
  // Register named actions in function registry
1982
2271
  var self = this;
1983
2272
  for (var actionName in this._actions) {
1984
- if (Object.prototype.hasOwnProperty.call(this._actions, actionName)) {
2273
+ if (_hop.call(this._actions, actionName)) {
1985
2274
  var registeredName = this._bwId + '_' + actionName;
1986
2275
  (function(aName) {
1987
2276
  bw.funcRegister(function(evt) {
@@ -2000,6 +2289,11 @@ ComponentHandle.prototype.mount = function(parentEl) {
2000
2289
  this.element = bw.createDOM(tacoForDOM);
2001
2290
  this.element._bwComponentHandle = this;
2002
2291
  this.element.setAttribute('data-bw_comp_id', this._bwId);
2292
+
2293
+ // Restore o.render from original TACO (stripped by _tacoForDOM)
2294
+ if (this.taco.o && this.taco.o.render) {
2295
+ this.element._bw_render = this.taco.o.render;
2296
+ }
2003
2297
  if (this._userTag) {
2004
2298
  this.element.classList.add(this._userTag);
2005
2299
  }
@@ -2015,6 +2309,16 @@ ComponentHandle.prototype.mount = function(parentEl) {
2015
2309
 
2016
2310
  this.mounted = true;
2017
2311
 
2312
+ // Scan for child ComponentHandles and link parent/child (Bug #5)
2313
+ var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
2314
+ for (var ci = 0; ci < childEls.length; ci++) {
2315
+ var ch = childEls[ci]._bwComponentHandle;
2316
+ if (ch && ch !== this && !ch._parent) {
2317
+ ch._parent = this;
2318
+ this._children.push(ch);
2319
+ }
2320
+ }
2321
+
2018
2322
  // mounted hook (backward compat: fn.length === 2 wraps (el, state))
2019
2323
  if (this._hooks.mounted) {
2020
2324
  if (this._hooks.mounted.length === 2) {
@@ -2023,16 +2327,21 @@ ComponentHandle.prototype.mount = function(parentEl) {
2023
2327
  this._hooks.mounted(this);
2024
2328
  }
2025
2329
  }
2330
+
2331
+ // Invoke o.render on initial mount (if present)
2332
+ if (this.element._bw_render) {
2333
+ this.element._bw_render(this.element, this._state);
2334
+ }
2026
2335
  };
2027
2336
 
2028
2337
  /**
2029
2338
  * Prepare TACO for initial render: resolve when/each markers.
2030
2339
  * @private
2031
2340
  */
2032
- ComponentHandle.prototype._prepareTaco = function(taco) {
2033
- if (!taco || typeof taco !== 'object') return;
2341
+ _chp._prepareTaco = function(taco) {
2342
+ if (!_is(taco, 'object')) return;
2034
2343
 
2035
- if (Array.isArray(taco.c)) {
2344
+ if (_isA(taco.c)) {
2036
2345
  for (var i = taco.c.length - 1; i >= 0; i--) {
2037
2346
  var child = taco.c[i];
2038
2347
  if (child && child._bwWhen) {
@@ -2057,18 +2366,18 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
2057
2366
  var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
2058
2367
  var arr = bw._evaluatePath(this._state, eachExprStr);
2059
2368
  var items = [];
2060
- if (Array.isArray(arr)) {
2369
+ if (_isA(arr)) {
2061
2370
  for (var j = 0; j < arr.length; j++) {
2062
2371
  items.push(child.factory(arr[j], j));
2063
2372
  }
2064
2373
  }
2065
2374
  taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
2066
2375
  }
2067
- if (taco.c[i] && typeof taco.c[i] === 'object' && taco.c[i].t) {
2376
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
2068
2377
  this._prepareTaco(taco.c[i]);
2069
2378
  }
2070
2379
  }
2071
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
2380
+ } else if (_is(taco.c, 'object') && taco.c.t) {
2072
2381
  this._prepareTaco(taco.c);
2073
2382
  }
2074
2383
  };
@@ -2077,12 +2386,12 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
2077
2386
  * Wire action name strings (in onclick etc.) to dispatch function calls.
2078
2387
  * @private
2079
2388
  */
2080
- ComponentHandle.prototype._wireActions = function(taco) {
2081
- if (!taco || typeof taco !== 'object' || !taco.t) return;
2389
+ _chp._wireActions = function(taco) {
2390
+ if (!_is(taco, 'object') || !taco.t) return;
2082
2391
  if (taco.a) {
2083
2392
  for (var key in taco.a) {
2084
- if (!Object.prototype.hasOwnProperty.call(taco.a, key)) continue;
2085
- if (key.startsWith('on') && typeof taco.a[key] === 'string') {
2393
+ if (!_hop.call(taco.a, key)) continue;
2394
+ if (key.startsWith('on') && _is(taco.a[key], 'string')) {
2086
2395
  var actionName = taco.a[key];
2087
2396
  if (actionName in this._actions) {
2088
2397
  var registeredName = this._bwId + '_' + actionName;
@@ -2096,11 +2405,11 @@ ComponentHandle.prototype._wireActions = function(taco) {
2096
2405
  }
2097
2406
  }
2098
2407
  }
2099
- if (Array.isArray(taco.c)) {
2408
+ if (_isA(taco.c)) {
2100
2409
  for (var i = 0; i < taco.c.length; i++) {
2101
2410
  this._wireActions(taco.c[i]);
2102
2411
  }
2103
- } else if (taco.c && typeof taco.c === 'object' && taco.c.t) {
2412
+ } else if (_is(taco.c, 'object') && taco.c.t) {
2104
2413
  this._wireActions(taco.c);
2105
2414
  }
2106
2415
  };
@@ -2109,7 +2418,7 @@ ComponentHandle.prototype._wireActions = function(taco) {
2109
2418
  * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
2110
2419
  * @private
2111
2420
  */
2112
- ComponentHandle.prototype._deepCloneTaco = function(taco) {
2421
+ _chp._deepCloneTaco = function(taco) {
2113
2422
  if (taco == null) return taco;
2114
2423
  // Preserve _bwWhen / _bwEach markers (contain functions)
2115
2424
  if (taco._bwWhen) {
@@ -2121,18 +2430,18 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
2121
2430
  if (taco._bwEach) {
2122
2431
  return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
2123
2432
  }
2124
- if (typeof taco !== 'object' || !taco.t) return taco;
2433
+ if (!_is(taco, 'object') || !taco.t) return taco;
2125
2434
  var result = { t: taco.t };
2126
2435
  if (taco.a) {
2127
2436
  result.a = {};
2128
2437
  for (var k in taco.a) {
2129
- if (Object.prototype.hasOwnProperty.call(taco.a, k)) result.a[k] = taco.a[k];
2438
+ if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
2130
2439
  }
2131
2440
  }
2132
2441
  if (taco.c != null) {
2133
- if (Array.isArray(taco.c)) {
2442
+ if (_isA(taco.c)) {
2134
2443
  result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
2135
- } else if (typeof taco.c === 'object') {
2444
+ } else if (_is(taco.c, 'object')) {
2136
2445
  result.c = this._deepCloneTaco(taco.c);
2137
2446
  } else {
2138
2447
  result.c = taco.c;
@@ -2146,27 +2455,31 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
2146
2455
  * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
2147
2456
  * @private
2148
2457
  */
2149
- ComponentHandle.prototype._tacoForDOM = function(taco) {
2150
- if (!taco || typeof taco !== 'object' || !taco.t) return taco;
2458
+ _chp._tacoForDOM = function(taco) {
2459
+ if (!_is(taco, 'object') || !taco.t) return taco;
2151
2460
  var result = { t: taco.t };
2152
2461
  if (taco.a) result.a = taco.a;
2153
2462
  if (taco.c != null) {
2154
- if (Array.isArray(taco.c)) {
2463
+ if (_isA(taco.c)) {
2155
2464
  result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
2156
- } else if (typeof taco.c === 'object' && taco.c.t) {
2465
+ } else if (_is(taco.c, 'object') && taco.c.t) {
2157
2466
  result.c = this._tacoForDOM(taco.c);
2158
2467
  } else {
2159
2468
  result.c = taco.c;
2160
2469
  }
2161
2470
  }
2162
2471
  // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
2472
+ if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
2473
+ _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
2474
+ '>. Use onclick attribute or bw.component() for child interactivity.');
2475
+ }
2163
2476
  return result;
2164
2477
  };
2165
2478
 
2166
2479
  /**
2167
2480
  * Unmount: remove from DOM, deactivate, preserve state for re-mount.
2168
2481
  */
2169
- ComponentHandle.prototype.unmount = function() {
2482
+ _chp.unmount = function() {
2170
2483
  if (!this.mounted) return;
2171
2484
 
2172
2485
  // unmount hook
@@ -2201,12 +2514,23 @@ ComponentHandle.prototype.unmount = function() {
2201
2514
  /**
2202
2515
  * Destroy: unmount + clear state + unregister actions.
2203
2516
  */
2204
- ComponentHandle.prototype.destroy = function() {
2517
+ _chp.destroy = function() {
2205
2518
  // willDestroy hook
2206
2519
  if (this._hooks.willDestroy) {
2207
2520
  this._hooks.willDestroy(this);
2208
2521
  }
2209
2522
 
2523
+ // Cascade destroy to children depth-first (Bug #5)
2524
+ for (var ci = this._children.length - 1; ci >= 0; ci--) {
2525
+ this._children[ci].destroy();
2526
+ }
2527
+ this._children = [];
2528
+ if (this._parent) {
2529
+ var idx = this._parent._children.indexOf(this);
2530
+ if (idx >= 0) this._parent._children.splice(idx, 1);
2531
+ this._parent = null;
2532
+ }
2533
+
2210
2534
  this.unmount();
2211
2535
 
2212
2536
  // Unregister actions from function registry
@@ -2233,12 +2557,36 @@ ComponentHandle.prototype.destroy = function() {
2233
2557
  * Flush dirty state: resolve changed bindings and apply to DOM.
2234
2558
  * @private
2235
2559
  */
2236
- ComponentHandle.prototype._flush = function() {
2560
+ _chp._flush = function() {
2237
2561
  this._scheduled = false;
2238
- var changedKeys = Object.keys(this._dirtyKeys);
2562
+ var changedKeys = _keys(this._dirtyKeys);
2239
2563
  this._dirtyKeys = {};
2240
2564
  if (changedKeys.length === 0 || !this.mounted) return;
2241
2565
 
2566
+ // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
2567
+ // rebuild the TACO from the factory with merged state (Bug #6)
2568
+ if (this._factory) {
2569
+ var rebuildNeeded = false;
2570
+ for (var fi = 0; fi < changedKeys.length; fi++) {
2571
+ if (_hop.call(this._factory.props, changedKeys[fi])) {
2572
+ rebuildNeeded = true; break;
2573
+ }
2574
+ }
2575
+ if (rebuildNeeded) {
2576
+ var merged = {};
2577
+ for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
2578
+ for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
2579
+ this._factory.props = merged;
2580
+ var newTaco = bw.make(this._factory.type, merged);
2581
+ newTaco._bwFactory = this._factory;
2582
+ this.taco = newTaco;
2583
+ this._originalTaco = this._deepCloneTaco(newTaco);
2584
+ this._render();
2585
+ if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
2586
+ return;
2587
+ }
2588
+ }
2589
+
2242
2590
  // willUpdate hook
2243
2591
  if (this._hooks.willUpdate) {
2244
2592
  this._hooks.willUpdate(this, changedKeys);
@@ -2277,7 +2625,7 @@ ComponentHandle.prototype._flush = function() {
2277
2625
  * Returns list of patches to apply.
2278
2626
  * @private
2279
2627
  */
2280
- ComponentHandle.prototype._resolveBindings = function(changedKeys) {
2628
+ _chp._resolveBindings = function(changedKeys) {
2281
2629
  var patches = [];
2282
2630
  for (var i = 0; i < this._bindings.length; i++) {
2283
2631
  var b = this._bindings[i];
@@ -2313,11 +2661,14 @@ ComponentHandle.prototype._resolveBindings = function(changedKeys) {
2313
2661
  * Apply patches to DOM.
2314
2662
  * @private
2315
2663
  */
2316
- ComponentHandle.prototype._applyPatches = function(patches) {
2664
+ _chp._applyPatches = function(patches) {
2317
2665
  for (var i = 0; i < patches.length; i++) {
2318
2666
  var p = patches[i];
2319
2667
  var el = this._bw_refs[p.refId];
2320
- if (!el) continue;
2668
+ if (!el) {
2669
+ if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
2670
+ continue;
2671
+ }
2321
2672
  if (p.type === 'content') {
2322
2673
  el.textContent = p.value;
2323
2674
  } else if (p.type === 'attribute') {
@@ -2334,7 +2685,7 @@ ComponentHandle.prototype._applyPatches = function(patches) {
2334
2685
  * Resolve all bindings and apply (used for initial render).
2335
2686
  * @private
2336
2687
  */
2337
- ComponentHandle.prototype._resolveAndApplyAll = function() {
2688
+ _chp._resolveAndApplyAll = function() {
2338
2689
  var patches = [];
2339
2690
  for (var i = 0; i < this._bindings.length; i++) {
2340
2691
  var b = this._bindings[i];
@@ -2357,7 +2708,7 @@ ComponentHandle.prototype._resolveAndApplyAll = function() {
2357
2708
  * Full re-render for structural changes (when/each branch switches).
2358
2709
  * @private
2359
2710
  */
2360
- ComponentHandle.prototype._render = function() {
2711
+ _chp._render = function() {
2361
2712
  if (!this.element || !this.element.parentNode) return;
2362
2713
  var parent = this.element.parentNode;
2363
2714
  var nextSibling = this.element.nextSibling;
@@ -2397,7 +2748,7 @@ ComponentHandle.prototype._render = function() {
2397
2748
  * @param {string} event - Event name (e.g., 'click')
2398
2749
  * @param {Function} handler - Event handler
2399
2750
  */
2400
- ComponentHandle.prototype.on = function(event, handler) {
2751
+ _chp.on = function(event, handler) {
2401
2752
  if (this.element) {
2402
2753
  this.element.addEventListener(event, handler);
2403
2754
  }
@@ -2409,7 +2760,7 @@ ComponentHandle.prototype.on = function(event, handler) {
2409
2760
  * @param {string} event - Event name
2410
2761
  * @param {Function} handler - Handler to remove
2411
2762
  */
2412
- ComponentHandle.prototype.off = function(event, handler) {
2763
+ _chp.off = function(event, handler) {
2413
2764
  if (this.element) {
2414
2765
  this.element.removeEventListener(event, handler);
2415
2766
  }
@@ -2424,7 +2775,7 @@ ComponentHandle.prototype.off = function(event, handler) {
2424
2775
  * @param {Function} handler - Handler function
2425
2776
  * @returns {Function} Unsubscribe function
2426
2777
  */
2427
- ComponentHandle.prototype.sub = function(topic, handler) {
2778
+ _chp.sub = function(topic, handler) {
2428
2779
  var unsub = bw.sub(topic, handler);
2429
2780
  this._subs.push(unsub);
2430
2781
  return unsub;
@@ -2435,10 +2786,10 @@ ComponentHandle.prototype.sub = function(topic, handler) {
2435
2786
  * @param {string} name - Action name
2436
2787
  * @param {...*} args - Arguments passed after comp
2437
2788
  */
2438
- ComponentHandle.prototype.action = function(name) {
2789
+ _chp.action = function(name) {
2439
2790
  var fn = this._actions[name];
2440
2791
  if (!fn) {
2441
- console.warn('ComponentHandle.action: unknown action "' + name + '"');
2792
+ _cw('ComponentHandle.action: unknown action "' + name + '"');
2442
2793
  return;
2443
2794
  }
2444
2795
  var args = [this].concat(Array.prototype.slice.call(arguments, 1));
@@ -2450,7 +2801,7 @@ ComponentHandle.prototype.action = function(name) {
2450
2801
  * @param {string} sel - CSS selector
2451
2802
  * @returns {Element|null}
2452
2803
  */
2453
- ComponentHandle.prototype.select = function(sel) {
2804
+ _chp.select = function(sel) {
2454
2805
  return this.element ? this.element.querySelector(sel) : null;
2455
2806
  };
2456
2807
 
@@ -2459,7 +2810,7 @@ ComponentHandle.prototype.select = function(sel) {
2459
2810
  * @param {string} sel - CSS selector
2460
2811
  * @returns {Element[]}
2461
2812
  */
2462
- ComponentHandle.prototype.selectAll = function(sel) {
2813
+ _chp.selectAll = function(sel) {
2463
2814
  if (!this.element) return [];
2464
2815
  return Array.prototype.slice.call(this.element.querySelectorAll(sel));
2465
2816
  };
@@ -2470,7 +2821,7 @@ ComponentHandle.prototype.selectAll = function(sel) {
2470
2821
  * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
2471
2822
  * @returns {ComponentHandle} this (for chaining)
2472
2823
  */
2473
- ComponentHandle.prototype.userTag = function(tag) {
2824
+ _chp.userTag = function(tag) {
2474
2825
  this._userTag = tag;
2475
2826
  if (this.element) {
2476
2827
  this.element.classList.add(tag);
@@ -2571,8 +2922,8 @@ bw.message = function(target, action, data) {
2571
2922
  }
2572
2923
  if (!el || !el._bwComponentHandle) return false;
2573
2924
  var comp = el._bwComponentHandle;
2574
- if (typeof comp[action] !== 'function') {
2575
- console.warn('bw.message: unknown action "' + action + '" on component ' + target);
2925
+ if (!_is(comp[action], 'function')) {
2926
+ _cw('bw.message: unknown action "' + action + '" on component ' + target);
2576
2927
  return false;
2577
2928
  }
2578
2929
  comp[action](data);
@@ -2609,7 +2960,7 @@ bw._builtinClientFunctions = {
2609
2960
  },
2610
2961
  focus: function(selector) {
2611
2962
  var el = bw._el(selector);
2612
- if (el && typeof el.focus === 'function') el.focus();
2963
+ if (el && _is(el.focus, 'function')) el.focus();
2613
2964
  },
2614
2965
  download: function(filename, content, mimeType) {
2615
2966
  if (typeof document === 'undefined') return;
@@ -2774,12 +3125,12 @@ bw.clientApply = function(msg) {
2774
3125
  } else if (type === 'remove') {
2775
3126
  var toRemove = bw._el(target);
2776
3127
  if (!toRemove) return false;
2777
- if (typeof bw.cleanup === 'function') bw.cleanup(toRemove);
3128
+ if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
2778
3129
  toRemove.remove();
2779
3130
  return true;
2780
3131
 
2781
3132
  } else if (type === 'batch') {
2782
- if (!Array.isArray(msg.ops)) return false;
3133
+ if (!_isA(msg.ops)) return false;
2783
3134
  var allOk = true;
2784
3135
  msg.ops.forEach(function(op) {
2785
3136
  if (!bw.clientApply(op)) allOk = false;
@@ -2795,26 +3146,26 @@ bw.clientApply = function(msg) {
2795
3146
  bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
2796
3147
  return true;
2797
3148
  } catch (e) {
2798
- console.error('[bw] register error:', msg.name, e);
3149
+ _ce('[bw] register error:', msg.name, e);
2799
3150
  return false;
2800
3151
  }
2801
3152
 
2802
3153
  } else if (type === 'call') {
2803
3154
  if (!msg.name) return false;
2804
3155
  var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
2805
- if (typeof fn !== 'function') return false;
3156
+ if (!_is(fn, 'function')) return false;
2806
3157
  try {
2807
- var args = Array.isArray(msg.args) ? msg.args : [];
3158
+ var args = _isA(msg.args) ? msg.args : [];
2808
3159
  fn.apply(null, args);
2809
3160
  return true;
2810
3161
  } catch (e) {
2811
- console.error('[bw] call error:', msg.name, e);
3162
+ _ce('[bw] call error:', msg.name, e);
2812
3163
  return false;
2813
3164
  }
2814
3165
 
2815
3166
  } else if (type === 'exec') {
2816
3167
  if (!bw._allowExec) {
2817
- console.warn('[bw] exec rejected: allowExec is not enabled');
3168
+ _cw('[bw] exec rejected: allowExec is not enabled');
2818
3169
  return false;
2819
3170
  }
2820
3171
  if (!msg.code) return false;
@@ -2822,7 +3173,7 @@ bw.clientApply = function(msg) {
2822
3173
  new Function(msg.code)();
2823
3174
  return true;
2824
3175
  } catch (e) {
2825
- console.error('[bw] exec error:', e);
3176
+ _ce('[bw] exec error:', e);
2826
3177
  return false;
2827
3178
  }
2828
3179
  }
@@ -2870,7 +3221,7 @@ bw.clientConnect = function(url, opts) {
2870
3221
 
2871
3222
  function handleMessage(data) {
2872
3223
  try {
2873
- var msg = typeof data === 'string' ? bw.clientParse(data) : data;
3224
+ var msg = _is(data, 'string') ? bw.clientParse(data) : data;
2874
3225
  if (onMessage) onMessage(msg);
2875
3226
  if (handlers.message) handlers.message(msg);
2876
3227
  bw.clientApply(msg);
@@ -2908,7 +3259,7 @@ bw.clientConnect = function(url, opts) {
2908
3259
  setStatus('connected');
2909
3260
  conn._pollTimer = setInterval(function() {
2910
3261
  fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
2911
- if (Array.isArray(msgs)) {
3262
+ if (_isA(msgs)) {
2912
3263
  msgs.forEach(handleMessage);
2913
3264
  } else if (msgs && msgs.type) {
2914
3265
  handleMessage(msgs);
@@ -2990,33 +3341,33 @@ bw.inspect = function(target) {
2990
3341
  el = target.element;
2991
3342
  comp = target;
2992
3343
  } else {
2993
- if (typeof target === 'string') {
3344
+ if (_is(target, 'string')) {
2994
3345
  el = bw.$(target)[0];
2995
3346
  }
2996
3347
  if (!el) {
2997
- console.warn('bw.inspect: element not found');
3348
+ _cw('bw.inspect: element not found');
2998
3349
  return null;
2999
3350
  }
3000
3351
  comp = el._bwComponentHandle;
3001
3352
  }
3002
3353
  if (!comp) {
3003
- console.log('bw.inspect: no ComponentHandle on this element');
3004
- console.log(' Tag:', el.tagName);
3005
- console.log(' Classes:', el.className);
3006
- console.log(' _bw_state:', el._bw_state || '(none)');
3354
+ _cl('bw.inspect: no ComponentHandle on this element');
3355
+ _cl(' Tag:', el.tagName);
3356
+ _cl(' Classes:', el.className);
3357
+ _cl(' _bw_state:', el._bw_state || '(none)');
3007
3358
  return null;
3008
3359
  }
3009
3360
  var deps = comp._bindings.reduce(function(s, b) {
3010
3361
  return s.concat(b.deps || []);
3011
3362
  }, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
3012
3363
  console.group('Component: ' + comp._bwId);
3013
- console.log('State:', comp._state);
3014
- console.log('Bindings:', comp._bindings.length, '(deps:', deps, ')');
3015
- console.log('Methods:', Object.keys(comp._methods));
3016
- console.log('Actions:', Object.keys(comp._actions));
3017
- console.log('User tag:', comp._userTag || '(none)');
3018
- console.log('Mounted:', comp.mounted);
3019
- console.log('Element:', comp.element);
3364
+ _cl('State:', comp._state);
3365
+ _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
3366
+ _cl('Methods:', _keys(comp._methods));
3367
+ _cl('Actions:', _keys(comp._actions));
3368
+ _cl('User tag:', comp._userTag || '(none)');
3369
+ _cl('Mounted:', comp.mounted);
3370
+ _cl('Element:', comp.element);
3020
3371
  console.groupEnd();
3021
3372
  return comp;
3022
3373
  };
@@ -3039,8 +3390,8 @@ bw.compile = function(taco) {
3039
3390
  // Pre-extract all binding expressions
3040
3391
  var precompiled = [];
3041
3392
  function walkExpressions(node) {
3042
- if (!node || typeof node !== 'object') return;
3043
- if (typeof node.c === 'string' && node.c.indexOf('${') >= 0) {
3393
+ if (!_is(node, 'object')) return;
3394
+ if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
3044
3395
  var parsed = bw._parseBindings(node.c);
3045
3396
  for (var i = 0; i < parsed.length; i++) {
3046
3397
  try {
@@ -3055,9 +3406,9 @@ bw.compile = function(taco) {
3055
3406
  }
3056
3407
  if (node.a) {
3057
3408
  for (var key in node.a) {
3058
- if (Object.prototype.hasOwnProperty.call(node.a, key)) {
3409
+ if (_hop.call(node.a, key)) {
3059
3410
  var v = node.a[key];
3060
- if (typeof v === 'string' && v.indexOf('${') >= 0) {
3411
+ if (_is(v, 'string') && v.indexOf('${') >= 0) {
3061
3412
  var parsed2 = bw._parseBindings(v);
3062
3413
  for (var j = 0; j < parsed2.length; j++) {
3063
3414
  try {
@@ -3073,9 +3424,9 @@ bw.compile = function(taco) {
3073
3424
  }
3074
3425
  }
3075
3426
  }
3076
- if (Array.isArray(node.c)) {
3427
+ if (_isA(node.c)) {
3077
3428
  for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
3078
- } else if (node.c && typeof node.c === 'object' && node.c.t) {
3429
+ } else if (_is(node.c, 'object') && node.c.t) {
3079
3430
  walkExpressions(node.c);
3080
3431
  }
3081
3432
  }
@@ -3087,7 +3438,7 @@ bw.compile = function(taco) {
3087
3438
  handle._precompiledBindings = precompiled;
3088
3439
  if (initialState) {
3089
3440
  for (var k in initialState) {
3090
- if (Object.prototype.hasOwnProperty.call(initialState, k)) {
3441
+ if (_hop.call(initialState, k)) {
3091
3442
  handle._state[k] = initialState[k];
3092
3443
  }
3093
3444
  }
@@ -3118,18 +3469,18 @@ bw.compile = function(taco) {
3118
3469
  bw.css = function(rules, options = {}) {
3119
3470
  const { minify = false, pretty = !minify } = options;
3120
3471
 
3121
- if (typeof rules === 'string') return rules;
3472
+ if (_is(rules, 'string')) return rules;
3122
3473
 
3123
3474
  let css = '';
3124
3475
  const indent = pretty ? ' ' : '';
3125
3476
  const newline = pretty ? '\n' : '';
3126
3477
  const space = pretty ? ' ' : '';
3127
3478
 
3128
- if (Array.isArray(rules)) {
3479
+ if (_isA(rules)) {
3129
3480
  css = rules.map(rule => bw.css(rule, options)).join(newline);
3130
- } else if (typeof rules === 'object') {
3481
+ } else if (_is(rules, 'object')) {
3131
3482
  Object.entries(rules).forEach(([selector, styles]) => {
3132
- if (typeof styles === 'object' && !Array.isArray(styles)) {
3483
+ if (_is(styles, 'object')) {
3133
3484
  // Handle @media, @keyframes, @supports — recurse into nested block
3134
3485
  if (selector.charAt(0) === '@') {
3135
3486
  const inner = bw.css(styles, options);
@@ -3178,7 +3529,7 @@ bw.css = function(rules, options = {}) {
3178
3529
  */
3179
3530
  bw.injectCSS = function(css, options = {}) {
3180
3531
  if (!bw._isBrowser) {
3181
- console.warn('bw.injectCSS requires a DOM environment');
3532
+ _cw('bw.injectCSS requires a DOM environment');
3182
3533
  return null;
3183
3534
  }
3184
3535
 
@@ -3195,7 +3546,7 @@ bw.injectCSS = function(css, options = {}) {
3195
3546
  }
3196
3547
 
3197
3548
  // Convert CSS if needed
3198
- const cssStr = typeof css === 'string' ? css : bw.css(css, options);
3549
+ const cssStr = _is(css, 'string') ? css : bw.css(css, options);
3199
3550
 
3200
3551
  // Set or append CSS
3201
3552
  if (append && styleEl.textContent) {
@@ -3225,7 +3576,7 @@ bw.s = function() {
3225
3576
  var result = {};
3226
3577
  for (var i = 0; i < arguments.length; i++) {
3227
3578
  var arg = arguments[i];
3228
- if (arg && typeof arg === 'object') Object.assign(result, arg);
3579
+ if (_is(arg, 'object')) Object.assign(result, arg);
3229
3580
  }
3230
3581
  return result;
3231
3582
  };
@@ -3348,7 +3699,7 @@ bw.u = {
3348
3699
  bw.responsive = function(selector, breakpoints) {
3349
3700
  var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
3350
3701
  var parts = [];
3351
- Object.keys(breakpoints).forEach(function(key) {
3702
+ _keys(breakpoints).forEach(function(key) {
3352
3703
  var rules = {};
3353
3704
  if (key === 'base') {
3354
3705
  rules[selector] = breakpoints[key];
@@ -3420,18 +3771,18 @@ if (bw._isBrowser) {
3420
3771
  if (!selector) return [];
3421
3772
 
3422
3773
  // Already an array
3423
- if (Array.isArray(selector)) return selector;
3774
+ if (_isA(selector)) return selector;
3424
3775
 
3425
3776
  // Single element
3426
3777
  if (selector.nodeType) return [selector];
3427
3778
 
3428
3779
  // NodeList or HTMLCollection
3429
- if (selector.length !== undefined && typeof selector !== 'string') {
3780
+ if (selector.length !== undefined && !_is(selector, 'string')) {
3430
3781
  return Array.from(selector);
3431
3782
  }
3432
3783
 
3433
3784
  // CSS selector string
3434
- if (typeof selector === 'string') {
3785
+ if (_is(selector, 'string')) {
3435
3786
  return Array.from(document.querySelectorAll(selector));
3436
3787
  }
3437
3788
 
@@ -3935,7 +4286,7 @@ bw.makeTable = function(config) {
3935
4286
 
3936
4287
  // Auto-detect columns if not provided
3937
4288
  const cols = columns || (data.length > 0
3938
- ? Object.keys(data[0]).map(key => ({ key, label: key }))
4289
+ ? _keys(data[0]).map(key => ({ key, label: key }))
3939
4290
  : []);
3940
4291
 
3941
4292
  // Current sort state
@@ -3950,7 +4301,7 @@ bw.makeTable = function(config) {
3950
4301
  const bVal = b[currentSortColumn];
3951
4302
 
3952
4303
  // Handle different types
3953
- if (typeof aVal === 'number' && typeof bVal === 'number') {
4304
+ if (_is(aVal, 'number') && _is(bVal, 'number')) {
3954
4305
  return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
3955
4306
  }
3956
4307
 
@@ -4060,7 +4411,7 @@ bw.makeTable = function(config) {
4060
4411
  bw.makeTableFromArray = function(config) {
4061
4412
  const { data = [], headerRow = true, columns, ...rest } = config;
4062
4413
 
4063
- if (!Array.isArray(data) || data.length === 0) {
4414
+ if (!_isA(data) || data.length === 0) {
4064
4415
  return bw.makeTable({ data: [], columns: columns || [], ...rest });
4065
4416
  }
4066
4417
 
@@ -4142,7 +4493,7 @@ bw.makeBarChart = function(config) {
4142
4493
  className = ''
4143
4494
  } = config;
4144
4495
 
4145
- if (!Array.isArray(data) || data.length === 0) {
4496
+ if (!_isA(data) || data.length === 0) {
4146
4497
  return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
4147
4498
  }
4148
4499
 
@@ -4291,7 +4642,7 @@ bw._componentRegistry = new Map();
4291
4642
  */
4292
4643
  bw.render = function(element, position, taco) {
4293
4644
  // Get target element
4294
- const targetEl = typeof element === 'string'
4645
+ const targetEl = _is(element, 'string')
4295
4646
  ? document.querySelector(element)
4296
4647
  : element;
4297
4648
 
@@ -4441,7 +4792,7 @@ bw.render = function(element, position, taco) {
4441
4792
  setContent(content) {
4442
4793
  this._taco.c = content;
4443
4794
  if (this.element) {
4444
- if (typeof content === 'string') {
4795
+ if (_is(content, 'string')) {
4445
4796
  this.element.textContent = content;
4446
4797
  } else {
4447
4798
  // Re-render for complex content