bitwrench 2.0.18 → 2.0.19

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 (58) hide show
  1. package/README.md +86 -81
  2. package/dist/bitwrench-bccl.cjs.js +221 -48
  3. package/dist/bitwrench-bccl.cjs.min.js +3 -3
  4. package/dist/bitwrench-bccl.esm.js +221 -48
  5. package/dist/bitwrench-bccl.esm.min.js +3 -3
  6. package/dist/bitwrench-bccl.umd.js +221 -48
  7. package/dist/bitwrench-bccl.umd.min.js +3 -3
  8. package/dist/bitwrench-code-edit.cjs.js +7 -9
  9. package/dist/bitwrench-code-edit.cjs.min.js +5 -7
  10. package/dist/bitwrench-code-edit.es5.js +6 -8
  11. package/dist/bitwrench-code-edit.es5.min.js +5 -7
  12. package/dist/bitwrench-code-edit.esm.js +7 -9
  13. package/dist/bitwrench-code-edit.esm.min.js +5 -7
  14. package/dist/bitwrench-code-edit.umd.js +7 -9
  15. package/dist/bitwrench-code-edit.umd.min.js +5 -7
  16. package/dist/bitwrench-debug.js +268 -0
  17. package/dist/bitwrench-debug.min.js +3 -0
  18. package/dist/bitwrench-lean.cjs.js +250 -1574
  19. package/dist/bitwrench-lean.cjs.min.js +6 -6
  20. package/dist/bitwrench-lean.es5.js +344 -1661
  21. package/dist/bitwrench-lean.es5.min.js +4 -4
  22. package/dist/bitwrench-lean.esm.js +250 -1574
  23. package/dist/bitwrench-lean.esm.min.js +6 -6
  24. package/dist/bitwrench-lean.umd.js +250 -1574
  25. package/dist/bitwrench-lean.umd.min.js +6 -6
  26. package/dist/bitwrench-util-css.cjs.js +1 -1
  27. package/dist/bitwrench-util-css.cjs.min.js +1 -1
  28. package/dist/bitwrench-util-css.es5.js +1 -1
  29. package/dist/bitwrench-util-css.es5.min.js +1 -1
  30. package/dist/bitwrench-util-css.esm.js +1 -1
  31. package/dist/bitwrench-util-css.esm.min.js +1 -1
  32. package/dist/bitwrench-util-css.umd.js +1 -1
  33. package/dist/bitwrench-util-css.umd.min.js +1 -1
  34. package/dist/bitwrench.cjs.js +510 -1660
  35. package/dist/bitwrench.cjs.min.js +7 -7
  36. package/dist/bitwrench.css +80 -33
  37. package/dist/bitwrench.es5.js +569 -1694
  38. package/dist/bitwrench.es5.min.js +5 -5
  39. package/dist/bitwrench.esm.js +510 -1660
  40. package/dist/bitwrench.esm.min.js +7 -7
  41. package/dist/bitwrench.min.css +1 -1
  42. package/dist/bitwrench.umd.js +510 -1660
  43. package/dist/bitwrench.umd.min.js +7 -7
  44. package/dist/builds.json +133 -111
  45. package/dist/bwserve.cjs.js +2 -2
  46. package/dist/bwserve.esm.js +2 -2
  47. package/dist/sri.json +46 -44
  48. package/package.json +5 -3
  49. package/readme.html +86 -75
  50. package/src/bitwrench-bccl-entry.js +3 -4
  51. package/src/bitwrench-bccl.js +217 -43
  52. package/src/bitwrench-code-edit.js +6 -8
  53. package/src/bitwrench-debug.js +245 -0
  54. package/src/bitwrench-styles.js +35 -8
  55. package/src/bitwrench.js +212 -1563
  56. package/src/cli/attach.js +53 -21
  57. package/src/cli/serve.js +179 -3
  58. package/src/version.js +3 -3
@@ -1,4 +1,4 @@
1
- /*! bitwrench-lean v2.0.18 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-lean v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
4
4
  typeof define === 'function' && define.amd ? define(factory) :
@@ -12,14 +12,14 @@
12
12
  */
13
13
 
14
14
  const VERSION_INFO = {
15
- version: '2.0.18',
15
+ version: '2.0.19',
16
16
  name: 'bitwrench',
17
17
  description: 'A library for javascript UI functions.',
18
18
  license: 'BSD-2-Clause',
19
19
  homepage: 'https://deftio.github.com/bitwrench/pages',
20
20
  repository: 'git+https://github.com/deftio/bitwrench.git',
21
21
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
22
- buildDate: '2026-03-17T00:50:09.505Z'
22
+ buildDate: '2026-03-22T19:09:32.608Z'
23
23
  };
24
24
 
25
25
  /**
@@ -2176,10 +2176,10 @@
2176
2176
  'position': 'relative', 'display': 'flex', 'flex-direction': 'column', 'pointer-events': 'auto',
2177
2177
  'background-clip': 'padding-box', 'border': '1px solid transparent', 'outline': '0'
2178
2178
  },
2179
- '.bw_modal_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between' },
2179
+ '.bw_modal_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '1rem 1.25rem', 'border-bottom': '1px solid transparent' },
2180
2180
  '.bw_modal_title': { 'margin': '0', 'font-size': '1.25rem', 'font-weight': '600', 'line-height': '1.3' },
2181
- '.bw_modal_body': { 'position': 'relative', 'flex': '1 1 auto' },
2182
- '.bw_modal_footer': { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'flex-end', 'gap': '0.5rem' }
2181
+ '.bw_modal_body': { 'position': 'relative', 'flex': '1 1 auto', 'padding': '1rem 1.25rem' },
2182
+ '.bw_modal_footer': { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'flex-end', 'gap': '0.5rem', 'padding': '0.75rem 1.25rem', 'border-top': '1px solid transparent' }
2183
2183
  },
2184
2184
 
2185
2185
  // ---- Toast ----
@@ -2200,8 +2200,8 @@
2200
2200
  },
2201
2201
  '.bw_toast.bw_toast_show': { 'opacity': '1', 'transform': 'translateY(0)' },
2202
2202
  '.bw_toast.bw_toast_hiding': { 'opacity': '0' },
2203
- '.bw_toast_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'font-size': '0.875rem' },
2204
- '.bw_toast_body': { 'font-size': '0.9375rem' }
2203
+ '.bw_toast_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '0.5rem 0.75rem', 'font-size': '0.875rem', 'border-bottom': '1px solid transparent' },
2204
+ '.bw_toast_body': { 'padding': '0.5rem 0.75rem', 'font-size': '0.9375rem' }
2205
2205
  },
2206
2206
 
2207
2207
  // ---- Dropdown ----
@@ -2215,15 +2215,15 @@
2215
2215
  '.bw_dropdown_menu': {
2216
2216
  'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': 'block',
2217
2217
  'min-width': '10rem', 'padding': '0.5rem 0', 'margin': '0.125rem 0 0',
2218
- 'background-clip': 'padding-box',
2218
+ 'background-clip': 'padding-box', 'border': '1px solid transparent',
2219
2219
  'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none'
2220
2220
  },
2221
2221
  '.bw_dropdown_menu.bw_dropdown_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
2222
2222
  '.bw_dropdown_menu_end': { 'left': 'auto', 'right': '0' },
2223
2223
  '.bw_dropdown_item': {
2224
- 'display': 'block', 'width': '100%', 'clear': 'both',
2224
+ 'display': 'block', 'width': '100%', 'padding': '0.4rem 1rem', 'clear': 'both',
2225
2225
  'font-weight': '400', 'text-align': 'inherit', 'text-decoration': 'none', 'white-space': 'nowrap',
2226
- 'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem'
2226
+ 'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem', 'cursor': 'pointer'
2227
2227
  },
2228
2228
  '.bw_dropdown_item:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '-2px' },
2229
2229
  '.bw_dropdown_divider': { 'height': '0', 'margin': '0.5rem 0', 'overflow': 'hidden', 'opacity': '1' }
@@ -2545,6 +2545,33 @@
2545
2545
  rules['.bw_text_left'] = { 'text-align': 'left' };
2546
2546
  rules['.bw_text_right'] = { 'text-align': 'right' };
2547
2547
  rules['.bw_text_center'] = { 'text-align': 'center' };
2548
+ rules['.bw_text_justify'] = { 'text-align': 'justify' };
2549
+
2550
+ // Font weight
2551
+ rules['.bw_fw_bold'] = { 'font-weight': '700' };
2552
+ rules['.bw_fw_semibold'] = { 'font-weight': '600' };
2553
+ rules['.bw_fw_normal'] = { 'font-weight': '400' };
2554
+ rules['.bw_fw_light'] = { 'font-weight': '300' };
2555
+
2556
+ // Font style
2557
+ rules['.bw_fst_italic'] = { 'font-style': 'italic' };
2558
+ rules['.bw_fst_normal'] = { 'font-style': 'normal' };
2559
+
2560
+ // Text decoration
2561
+ rules['.bw_text_underline'] = { 'text-decoration': 'underline' };
2562
+ rules['.bw_text_line_through'] = { 'text-decoration': 'line-through' };
2563
+ rules['.bw_text_decoration_none'] = { 'text-decoration': 'none' };
2564
+
2565
+ // Text transform
2566
+ rules['.bw_text_uppercase'] = { 'text-transform': 'uppercase' };
2567
+ rules['.bw_text_lowercase'] = { 'text-transform': 'lowercase' };
2568
+ rules['.bw_text_capitalize'] = { 'text-transform': 'capitalize' };
2569
+
2570
+ // Font size
2571
+ rules['.bw_fs_sm'] = { 'font-size': '0.875rem' };
2572
+ rules['.bw_fs_base'] = { 'font-size': '1rem' };
2573
+ rules['.bw_fs_lg'] = { 'font-size': '1.25rem' };
2574
+ rules['.bw_fs_xl'] = { 'font-size': '1.5rem' };
2548
2575
 
2549
2576
  // Flexbox
2550
2577
  var jc = { start: 'flex-start', end: 'flex-end', center: 'center', between: 'space-between', around: 'space-around' };
@@ -3494,12 +3521,11 @@
3494
3521
  _subIdCounter: 0, // monotonic ID for subscriptions
3495
3522
 
3496
3523
  // ── Node reference cache ──────────────────────────────────────────────
3497
- // Fast O(1) lookup for elements by bw_id, id attribute, or bw_uuid.
3524
+ // Fast O(1) lookup for elements by id attribute or bw_uuid_* class.
3498
3525
  //
3499
3526
  // Populated by bw.createDOM() when elements have:
3500
- // - data-bw_id attribute (user-declared addressable elements)
3501
3527
  // - id attribute (standard HTML id)
3502
- // - bw_uuid (internal, for lifecycle-managed elements)
3528
+ // - bw_uuid_* class (lifecycle-managed or explicitly addressed elements)
3503
3529
  //
3504
3530
  // Cleaned up by bw.cleanup() when elements are destroyed via bitwrench APIs.
3505
3531
  // On cache miss, falls back to querySelector/getElementById — never fails,
@@ -3507,7 +3533,7 @@
3507
3533
  // via parentNode === null check (IE11-safe, unlike el.isConnected).
3508
3534
  //
3509
3535
  // Elements created via bw.createDOM() also get el._bw_refs — a local map of
3510
- // child bw_id DOM node ref for fast parentchild access in o.render.
3536
+ // child id/UUID -> DOM node ref for fast parent->child access in o.render.
3511
3537
  // This is the bitwrench equivalent of React's compiled template "holes".
3512
3538
  //
3513
3539
  // Contract: if you remove elements outside of bitwrench APIs (raw el.remove()),
@@ -3587,7 +3613,6 @@
3587
3613
  // _cw console.warn 8
3588
3614
  // _cl console.log 11
3589
3615
  // _ce console.error 4
3590
- // _chp ComponentHandle.prototype 28 (defined after constructor)
3591
3616
  //
3592
3617
  // Note: document.createElement etc. are NOT aliased because they require
3593
3618
  // `this === document` and .bind() would add overhead on every call.
@@ -3760,15 +3785,15 @@
3760
3785
  * 1. Check `bw._nodeMap[id]` — if found and still attached (parentNode !== null), return it
3761
3786
  * 2. If cached ref is detached (parentNode === null), remove stale entry
3762
3787
  * 3. Fall back to `document.getElementById(id)` then `document.querySelector(...)`
3763
- * 4. If fallback finds the element, cache it for next time
3764
- * 5. If not found anywhere, return null
3788
+ * 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
3789
+ * 5. Cache the result for next time
3765
3790
  *
3766
3791
  * Accepts a DOM element directly (pass-through) or a string identifier.
3767
3792
  * String identifiers are tried as: direct map key, getElementById,
3768
3793
  * querySelector (for CSS selectors starting with . or #), and
3769
- * data-bw_id attribute selector.
3794
+ * bw_uuid_* class selector.
3770
3795
  *
3771
- * @param {string|Element} id - Element ID, CSS selector, data-bw_id value, or DOM element
3796
+ * @param {string|Element} id - Element ID, CSS selector, bw_uuid_* class, or DOM element
3772
3797
  * @returns {Element|null} The DOM element, or null if not found
3773
3798
  * @category Internal
3774
3799
  */
@@ -3797,17 +3822,12 @@
3797
3822
  el = document.querySelector(id);
3798
3823
  }
3799
3824
 
3800
- // 4. Try data-bw_id attribute (for bw.uuid-generated IDs)
3801
- if (!el) {
3802
- el = document.querySelector('[data-bw_id="' + id + '"]');
3803
- }
3804
-
3805
- // 5. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
3825
+ // 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
3806
3826
  if (!el && id.indexOf('bw_uuid_') === 0) {
3807
3827
  el = document.querySelector('.' + id);
3808
3828
  }
3809
3829
 
3810
- // 6. Cache the result for next time
3830
+ // 5. Cache the result for next time
3811
3831
  if (el) {
3812
3832
  bw._nodeMap[id] = el;
3813
3833
  }
@@ -3819,17 +3839,17 @@
3819
3839
  * Register a DOM element in the node cache under one or more keys.
3820
3840
  *
3821
3841
  * Called internally by `bw.createDOM()`. Registers elements that have
3822
- * id attributes, data-bw_id attributes, or both.
3842
+ * id attributes, UUID classes, or both.
3823
3843
  *
3824
3844
  * @param {Element} el - DOM element to register
3825
- * @param {string} [bwId] - data-bw_id value to register under
3845
+ * @param {string} [uuid] - bw_uuid_* class token to register under
3826
3846
  * @category Internal
3827
3847
  */
3828
- bw._registerNode = function(el, bwId) {
3848
+ bw._registerNode = function(el, uuid) {
3829
3849
  if (!el) return;
3830
- // Register under data-bw_id
3831
- if (bwId) {
3832
- bw._nodeMap[bwId] = el;
3850
+ // Register under UUID class token
3851
+ if (uuid) {
3852
+ bw._nodeMap[uuid] = el;
3833
3853
  }
3834
3854
  // Register under id attribute
3835
3855
  var htmlId = el.getAttribute ? el.getAttribute('id') : null;
@@ -3845,13 +3865,13 @@
3845
3865
  * through bitwrench APIs.
3846
3866
  *
3847
3867
  * @param {Element} el - DOM element to deregister
3848
- * @param {string} [bwId] - data-bw_id value to remove
3868
+ * @param {string} [uuid] - bw_uuid_* class token to remove
3849
3869
  * @category Internal
3850
3870
  */
3851
- bw._deregisterNode = function(el, bwId) {
3852
- // Remove data-bw_id entry
3853
- if (bwId) {
3854
- delete bw._nodeMap[bwId];
3871
+ bw._deregisterNode = function(el, uuid) {
3872
+ // Remove UUID class entry
3873
+ if (uuid) {
3874
+ delete bw._nodeMap[uuid];
3855
3875
  }
3856
3876
  // Remove id attribute entry
3857
3877
  var htmlId = el && el.getAttribute ? el.getAttribute('id') : null;
@@ -3864,6 +3884,13 @@
3864
3884
  // bw.assignUUID() / bw.getUUID() — Explicit UUID addressing for TACO objects
3865
3885
  // ===================================================================================
3866
3886
 
3887
+ /**
3888
+ * Marker class for elements with lifecycle hooks (mounted/unmount/render/state).
3889
+ * Used by cleanup() to find lifecycle-managed elements via querySelectorAll('.bw_lc').
3890
+ * @private
3891
+ */
3892
+ var _BW_LC = 'bw_lc';
3893
+
3867
3894
  /**
3868
3895
  * Regex to match a bw_uuid_* token in a class string.
3869
3896
  * @private
@@ -4052,15 +4079,6 @@
4052
4079
  // Handle null/undefined
4053
4080
  if (taco == null) return '';
4054
4081
 
4055
- // Handle ComponentHandle — use its .taco
4056
- if (taco && taco._bwComponent === true) {
4057
- var compOptions = Object.assign({}, options);
4058
- if (!compOptions.state && taco._state) {
4059
- compOptions.state = taco._state;
4060
- }
4061
- return bw.html(taco.taco, compOptions);
4062
- }
4063
-
4064
4082
  // Handle arrays of TACOs
4065
4083
  if (_isA(taco)) {
4066
4084
  return taco.map(t => bw.html(t, options)).join('');
@@ -4071,24 +4089,6 @@
4071
4089
  return taco.v;
4072
4090
  }
4073
4091
 
4074
- // Handle bw.when() markers
4075
- if (taco && taco._bwWhen && options.state) {
4076
- var whenExpr = taco.expr.replace(/^\$\{|\}$/g, '');
4077
- var whenVal = options.compile
4078
- ? bw._resolveTemplate('${' + whenExpr + '}', options.state, true)
4079
- : bw._evaluatePath(options.state, whenExpr);
4080
- var branch = whenVal ? taco.branches[0] : (taco.branches[1] || null);
4081
- return branch ? bw.html(branch, options) : '';
4082
- }
4083
-
4084
- // Handle bw.each() markers
4085
- if (taco && taco._bwEach && options.state) {
4086
- var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
4087
- var arr = bw._evaluatePath(options.state, eachExpr);
4088
- if (!_isA(arr)) return '';
4089
- return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
4090
- }
4091
-
4092
4092
  // Handle primitives and non-TACO objects
4093
4093
  if (!_is(taco, 'object') || !taco.t) {
4094
4094
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
@@ -4152,14 +4152,14 @@
4152
4152
  }
4153
4153
  }
4154
4154
 
4155
- // Add bw_id as a class if lifecycle hooks present
4156
- if ((opts.mounted || opts.unmount) && !attrs.class?.includes('bw_id_')) {
4157
- const id = opts.bw_id || bw.uuid();
4155
+ // Add bw_uuid + bw_lc classes if lifecycle hooks present
4156
+ if ((opts.mounted || opts.unmount) && !_UUID_RE.test(attrs.class || '')) {
4157
+ const uuid = bw.uuid('uuid');
4158
4158
  attrStr = attrStr.replace(/class="([^"]*)"/, (_match, classes) => {
4159
- return `class="${classes} bw_id_${id}"`.trim();
4159
+ return `class="${classes} ${uuid} ${_BW_LC}"`.trim();
4160
4160
  });
4161
4161
  if (!attrStr.includes('class=')) {
4162
- attrStr += ` class="bw_id_${id}"`;
4162
+ attrStr += ` class="${uuid} ${_BW_LC}"`;
4163
4163
  }
4164
4164
  }
4165
4165
 
@@ -4387,11 +4387,6 @@
4387
4387
  return frag;
4388
4388
  }
4389
4389
 
4390
- // Handle ComponentHandle — extract .taco for DOM creation
4391
- if (taco && taco._bwComponent === true) {
4392
- return bw.createDOM(taco.taco, options);
4393
- }
4394
-
4395
4390
  // Handle text nodes
4396
4391
  if (!_is(taco, 'object') || !taco.t) {
4397
4392
  return document.createTextNode(String(taco));
@@ -4432,24 +4427,19 @@
4432
4427
  }
4433
4428
 
4434
4429
  // Add children, building _bw_refs for fast parent→child access.
4435
- // Children with data-bw_id or id attributes get local refs on the parent,
4430
+ // Children with id attributes or bw_uuid_* classes get local refs on the parent,
4436
4431
  // so o.render functions can access them without any DOM lookup.
4437
4432
  if (content != null) {
4438
4433
  if (_isA(content)) {
4439
4434
  content.forEach(child => {
4440
4435
  if (child != null) {
4441
- // Handle ComponentHandle in content arrays (Level 2 children)
4442
- if (child._bwComponent === true) {
4443
- child.mount(el);
4444
- return;
4445
- }
4446
4436
  var childEl = bw.createDOM(child, options);
4447
4437
  el.appendChild(childEl);
4448
4438
  // Build local refs for addressable children
4449
- var childBwId = (child && child.a) ? (child.a['data-bw_id'] || child.a.id) : null;
4450
- if (childBwId) {
4439
+ var childRefId = (child && child.a) ? (child.a.id || bw.getUUID(child)) : null;
4440
+ if (childRefId) {
4451
4441
  if (!el._bw_refs) el._bw_refs = {};
4452
- el._bw_refs[childBwId] = childEl;
4442
+ el._bw_refs[childRefId] = childEl;
4453
4443
  }
4454
4444
  // Bubble up grandchild refs (flatten one level)
4455
4445
  if (childEl._bw_refs) {
@@ -4465,16 +4455,13 @@
4465
4455
  } else if (_is(content, 'object') && content.__bw_raw) {
4466
4456
  // Raw HTML content — inject via innerHTML
4467
4457
  el.innerHTML = content.v;
4468
- } else if (content._bwComponent === true) {
4469
- // Single ComponentHandle as content
4470
- content.mount(el);
4471
4458
  } else if (_is(content, 'object') && content.t) {
4472
4459
  var childEl = bw.createDOM(content, options);
4473
4460
  el.appendChild(childEl);
4474
- var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
4475
- if (childBwId) {
4461
+ var childRefId = content.a ? (content.a.id || bw.getUUID(content)) : null;
4462
+ if (childRefId) {
4476
4463
  if (!el._bw_refs) el._bw_refs = {};
4477
- el._bw_refs[childBwId] = childEl;
4464
+ el._bw_refs[childRefId] = childEl;
4478
4465
  }
4479
4466
  if (childEl._bw_refs) {
4480
4467
  if (!el._bw_refs) el._bw_refs = {};
@@ -4504,57 +4491,88 @@
4504
4491
 
4505
4492
  // Handle lifecycle hooks and state
4506
4493
  if (opts.mounted || opts.unmount || opts.render || opts.state) {
4507
- const id = attrs['data-bw_id'] || bw.uuid();
4508
- el.setAttribute('data-bw_id', id);
4494
+ // Ensure element has a UUID class for identity
4495
+ var uuid = bw.getUUID(el) || bw.uuid('uuid');
4496
+ el.classList.add(uuid);
4497
+ el.classList.add(_BW_LC);
4509
4498
 
4510
- // Register in node cache under data-bw_id
4511
- bw._registerNode(el, id);
4499
+ // Register in node cache under UUID class
4500
+ bw._registerNode(el, uuid);
4512
4501
 
4513
4502
  // Store state
4514
4503
  if (opts.state) {
4515
4504
  el._bw_state = opts.state;
4516
4505
  }
4517
4506
 
4518
- // o.render — first-class render function (replaces mounted boilerplate)
4507
+ // o.render — store the render function for bw.update()
4519
4508
  if (opts.render) {
4520
4509
  el._bw_render = opts.render;
4510
+ }
4521
4511
 
4522
- if (opts.mounted) {
4523
- _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
4524
- }
4512
+ // Determine what to call on mount:
4513
+ // - If o.mounted exists, call it (it can call el._bw_render() for initial render)
4514
+ // - Otherwise if o.render exists, auto-call it as a convenience shorthand
4515
+ var mountFn = opts.mounted || (opts.render ? function(mountEl) {
4516
+ opts.render(mountEl, mountEl._bw_state || {});
4517
+ } : null);
4525
4518
 
4526
- // Queue initial render (same timing as mounted)
4527
- if (document.body.contains(el)) {
4528
- opts.render(el, el._bw_state || {});
4529
- } else {
4530
- requestAnimationFrame(() => {
4531
- if (document.body.contains(el)) {
4532
- opts.render(el, el._bw_state || {});
4533
- }
4534
- });
4535
- }
4536
- } else if (opts.mounted) {
4537
- // Queue mounted callback (legacy pattern)
4519
+ if (mountFn) {
4538
4520
  if (document.body.contains(el)) {
4539
- opts.mounted(el, el._bw_state || {});
4521
+ mountFn(el, el._bw_state || {});
4540
4522
  } else {
4541
4523
  requestAnimationFrame(() => {
4542
4524
  if (document.body.contains(el)) {
4543
- opts.mounted(el, el._bw_state || {});
4525
+ mountFn(el, el._bw_state || {});
4544
4526
  }
4545
4527
  });
4546
4528
  }
4547
4529
  }
4548
4530
 
4549
- // Store unmount callback
4531
+ // Store unmount callback keyed by UUID class
4550
4532
  if (opts.unmount) {
4551
- bw._unmountCallbacks.set(id, () => {
4533
+ bw._unmountCallbacks.set(uuid, () => {
4552
4534
  opts.unmount(el, el._bw_state || {});
4553
4535
  });
4554
4536
  }
4555
- } else if (attrs['data-bw_id']) {
4556
- // Element has explicit data-bw_id but no lifecycle hooks — still register it
4557
- bw._registerNode(el, attrs['data-bw_id']);
4537
+ }
4538
+
4539
+ // Component handle: attach methods to el.bw namespace
4540
+ if (opts.handle || opts.slots) {
4541
+ if (!el.bw) el.bw = {};
4542
+
4543
+ // Explicit handle methods: fn(el, ...args) -> el.bw.method(...args)
4544
+ if (opts.handle) {
4545
+ for (var hk in opts.handle) {
4546
+ if (_hop.call(opts.handle, hk)) {
4547
+ el.bw[hk] = opts.handle[hk].bind(null, el);
4548
+ }
4549
+ }
4550
+ }
4551
+
4552
+ // Slot declarations: auto-generate setX/getX pairs
4553
+ if (opts.slots) {
4554
+ for (var sk in opts.slots) {
4555
+ if (_hop.call(opts.slots, sk)) {
4556
+ (function(name, selector) {
4557
+ var cap = name.charAt(0).toUpperCase() + name.slice(1);
4558
+ el.bw['set' + cap] = function(value) {
4559
+ var t = el.querySelector(selector);
4560
+ if (!t) return;
4561
+ if (value != null && typeof value === 'object' && value.t) {
4562
+ t.innerHTML = '';
4563
+ t.appendChild(bw.createDOM(value));
4564
+ } else {
4565
+ t.textContent = (value != null) ? String(value) : '';
4566
+ }
4567
+ };
4568
+ el.bw['get' + cap] = function() {
4569
+ var t = el.querySelector(selector);
4570
+ return t ? t.textContent : '';
4571
+ };
4572
+ })(sk, opts.slots[sk]);
4573
+ }
4574
+ }
4575
+ }
4558
4576
  }
4559
4577
 
4560
4578
  return el;
@@ -4601,7 +4619,7 @@
4601
4619
  // the target is the mount point, not the content being replaced)
4602
4620
  const savedState = targetEl._bw_state;
4603
4621
  const savedRender = targetEl._bw_render;
4604
- const savedBwId = targetEl.getAttribute('data-bw_id');
4622
+ const savedUuid = bw.getUUID(targetEl);
4605
4623
  const savedSubs = targetEl._bw_subs;
4606
4624
 
4607
4625
  // Temporarily remove _bw_subs so cleanup doesn't call them
@@ -4613,10 +4631,9 @@
4613
4631
  // Restore the target's own state/render/subs after cleanup
4614
4632
  if (savedState !== undefined) targetEl._bw_state = savedState;
4615
4633
  if (savedRender) targetEl._bw_render = savedRender;
4616
- if (savedBwId) {
4617
- targetEl.setAttribute('data-bw_id', savedBwId);
4618
- // Re-register mount point in node cache (cleanup deregistered it)
4619
- bw._registerNode(targetEl, savedBwId);
4634
+ if (savedUuid) {
4635
+ // UUID class stays on element through cleanup; re-register in cache
4636
+ bw._registerNode(targetEl, savedUuid);
4620
4637
  }
4621
4638
  if (savedSubs) targetEl._bw_subs = savedSubs;
4622
4639
 
@@ -4624,25 +4641,11 @@
4624
4641
  targetEl.innerHTML = '';
4625
4642
 
4626
4643
  if (taco != null) {
4627
- // Handle ComponentHandle (reactive components from bw.component())
4628
- if (taco._bwComponent === true) {
4629
- taco.mount(targetEl);
4630
- }
4631
- // Handle component handles (objects with element property)
4632
- else if (taco.element instanceof Element) {
4633
- targetEl.appendChild(taco.element);
4634
- }
4635
4644
  // Handle arrays
4636
- else if (_isA(taco)) {
4645
+ if (_isA(taco)) {
4637
4646
  taco.forEach(t => {
4638
4647
  if (t != null) {
4639
- if (t._bwComponent === true) {
4640
- t.mount(targetEl);
4641
- } else if (t.element instanceof Element) {
4642
- targetEl.appendChild(t.element);
4643
- } else {
4644
- targetEl.appendChild(bw.createDOM(t, options));
4645
- }
4648
+ targetEl.appendChild(bw.createDOM(t, options));
4646
4649
  }
4647
4650
  });
4648
4651
  }
@@ -4655,205 +4658,36 @@
4655
4658
  return targetEl;
4656
4659
  };
4657
4660
 
4658
- /**
4659
- * Compile props into getter/setter functions for reactive updates.
4660
- *
4661
- * Used internally by `bw.renderComponent()`. Creates a proxy-like object
4662
- * where setting a property triggers `handle.onPropChange()`.
4663
- *
4664
- * @param {Object} handle - Component handle
4665
- * @param {Object} props - Initial props
4666
- * @returns {Object} Compiled props object with getters/setters
4667
- * @category DOM Generation
4668
- */
4669
- bw.compileProps = function(handle, props = {}) {
4670
- const compiledProps = {};
4671
-
4672
- _keys(props).forEach(key => {
4673
- // Create getter/setter for each prop
4674
- Object.defineProperty(compiledProps, key, {
4675
- get() {
4676
- return handle._props[key];
4677
- },
4678
- set(value) {
4679
- const oldValue = handle._props[key];
4680
- if (oldValue !== value) {
4681
- handle._props[key] = value;
4682
- // Trigger update if prop changed
4683
- if (handle.onPropChange) {
4684
- handle.onPropChange(key, value, oldValue);
4685
- }
4686
- }
4687
- },
4688
- enumerable: true,
4689
- configurable: true
4690
- });
4691
- });
4692
-
4693
- return compiledProps;
4694
- };
4661
+ // Deprecation stubs for removed ComponentHandle APIs
4662
+ bw.compileProps = function() { throw new Error('bw.compileProps() removed in v2.0.19. Use o.handle/o.slots instead.'); };
4663
+ bw.renderComponent = function() { throw new Error('bw.renderComponent() removed in v2.0.19. Use bw.mount() with o.handle/o.slots instead.'); };
4695
4664
 
4696
4665
  /**
4697
- * Render a TACO component and return an enhanced handle object.
4666
+ * Mount a TACO into a target element and return the created root element.
4667
+ * Like bw.DOM() but returns the root element of the TACO (not the container),
4668
+ * giving direct access to el.bw handle methods.
4698
4669
  *
4699
- * The handle provides compiled props, state management, child registration,
4700
- * and a destroy method. Used internally by `bw.createCard()`, `bw.createTable()`, etc.
4701
- *
4702
- * @param {Object} taco - TACO object to render
4703
- * @param {Object} [options] - Render options
4704
- * @returns {Object} Component handle with element, props, state, update(), destroy()
4670
+ * @param {string|Element} target - CSS selector or DOM element
4671
+ * @param {Object} taco - TACO to render
4672
+ * @param {Object} [options] - Mount options
4673
+ * @returns {Element} The created root element
4705
4674
  * @category DOM Generation
4706
- */
4707
- bw.renderComponent = function(taco, options = {}) {
4708
- const element = bw.createDOM(taco, options);
4709
-
4710
- // Enhanced handle with prop compilation
4711
- const handle = {
4712
- element,
4713
- taco,
4714
- _props: { ...taco.a }, // Store props internally
4715
- _state: taco.o?.state || {},
4716
- _children: {}, // Store child component references
4717
-
4718
- // Get compiled props with getters/setters
4719
- get props() {
4720
- if (!this._compiledProps) {
4721
- this._compiledProps = bw.compileProps(this, this._props);
4722
- }
4723
- return this._compiledProps;
4724
- },
4725
-
4726
- /**
4727
- * Query all matching elements within this component
4728
- * @param {string} selector - CSS selector
4729
- * @returns {NodeList} Matching elements
4730
- */
4731
- $(selector) {
4732
- return this.element.querySelectorAll(selector);
4733
- },
4734
-
4735
- /**
4736
- * Query the first matching element within this component
4737
- * @param {string} selector - CSS selector
4738
- * @returns {Element|null} First matching element or null
4739
- */
4740
- $first(selector) {
4741
- return this.element.querySelector(selector);
4742
- },
4743
-
4744
- /**
4745
- * Update component with new props and re-render in place
4746
- * @param {Object} newProps - Properties to merge into current props
4747
- * @returns {Object} this handle (for chaining)
4748
- */
4749
- update(newProps) {
4750
- // Update internal props
4751
- Object.assign(this._props, newProps);
4752
-
4753
- // Rebuild TACO with new props
4754
- const newTaco = { ...this.taco, a: { ...this.taco.a, ...newProps } };
4755
- const newElement = bw.createDOM(newTaco, options);
4756
-
4757
- // Replace in DOM
4758
- this.element.replaceWith(newElement);
4759
- this.element = newElement;
4760
- this.taco = newTaco;
4761
-
4762
- return this;
4763
- },
4764
-
4765
- /**
4766
- * Re-render the component from its current TACO, replacing the DOM element
4767
- * @returns {Object} this handle (for chaining)
4768
- */
4769
- render() {
4770
- const newElement = bw.createDOM(this.taco, options);
4771
- this.element.replaceWith(newElement);
4772
- this.element = newElement;
4773
- return this;
4774
- },
4775
-
4776
- /**
4777
- * Called when a compiled prop value changes. Override to customize behavior.
4778
- * Default implementation triggers a full re-render.
4779
- * @param {string} key - Property name that changed
4780
- * @param {*} newValue - New property value
4781
- * @param {*} oldValue - Previous property value
4782
- */
4783
- onPropChange(_key, _newValue, _oldValue) {
4784
- // Auto re-render on prop change by default
4785
- this.render();
4786
- },
4787
-
4788
- // State management
4789
- get state() {
4790
- return this._state;
4791
- },
4792
-
4793
- set state(newState) {
4794
- this._state = newState;
4795
- this.render();
4796
- },
4797
-
4798
- /**
4799
- * Merge state updates and re-render the component
4800
- * @param {Object} updates - State properties to merge
4801
- * @returns {Object} this handle (for chaining)
4802
- */
4803
- setState(updates) {
4804
- Object.assign(this._state, updates);
4805
- this.render();
4806
- return this;
4807
- },
4808
-
4809
- /**
4810
- * Register a child component under a name for later retrieval
4811
- * @param {string} name - Child name key
4812
- * @param {Object} component - Child component handle
4813
- * @returns {Object} this handle (for chaining)
4814
- */
4815
- addChild(name, component) {
4816
- this._children[name] = component;
4817
- return this;
4818
- },
4819
-
4820
- /**
4821
- * Retrieve a registered child component by name
4822
- * @param {string} name - Child name key
4823
- * @returns {Object|undefined} Child component handle
4824
- */
4825
- getChild(name) {
4826
- return this._children[name];
4827
- },
4828
-
4829
- /**
4830
- * Destroy this component and all registered children
4831
- *
4832
- * Calls destroy() recursively on children, runs bw.cleanup(),
4833
- * removes the element from DOM, and clears all internal references.
4834
- */
4835
- destroy() {
4836
- // Destroy children first
4837
- Object.values(this._children).forEach(child => {
4838
- if (child && child.destroy) child.destroy();
4839
- });
4840
-
4841
- // Clean up this component
4842
- bw.cleanup(this.element);
4843
- this.element.remove();
4844
-
4845
- // Clear references
4846
- this._children = {};
4847
- this._props = {};
4848
- this._state = {};
4849
- this._compiledProps = null;
4850
- }
4851
- };
4852
-
4853
- // Store handle reference on element
4854
- element._bwHandle = handle;
4855
-
4856
- return handle;
4675
+ * @example
4676
+ * var el = bw.mount('#app', bw.makeCarousel({ items: slides }));
4677
+ * el.bw.goToSlide(2);
4678
+ * el.bw.next();
4679
+ */
4680
+ bw.mount = function(target, taco, options) {
4681
+ var container = _is(target, 'string') ? bw.$(target)[0] : target;
4682
+ if (!container) {
4683
+ _cw('bw.mount: target not found');
4684
+ return null;
4685
+ }
4686
+ bw.cleanup(container);
4687
+ container.innerHTML = '';
4688
+ var el = bw.createDOM(taco, options || {});
4689
+ container.appendChild(el);
4690
+ return el;
4857
4691
  };
4858
4692
 
4859
4693
  /**
@@ -4874,34 +4708,29 @@
4874
4708
  bw.cleanup = function(element) {
4875
4709
  if (!bw._isBrowser || !element) return;
4876
4710
 
4877
- // Deregister UUID classes from node cache (element + descendants)
4878
- // Covers elements that have UUID but no data-bw_id
4879
- var selfUuidMatch = element.className && element.className.match(_UUID_RE);
4880
- if (selfUuidMatch) delete bw._nodeMap[selfUuidMatch[0]];
4711
+ // Deregister UUID classes from node cache for non-lifecycle UUID elements
4881
4712
  var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
4882
4713
  uuidEls.forEach(function(uel) {
4883
4714
  var m = uel.className && uel.className.match(_UUID_RE);
4884
4715
  if (m) delete bw._nodeMap[m[0]];
4885
4716
  });
4886
4717
 
4887
- // Find all elements with data-bw_id
4888
- const elements = element.querySelectorAll('[data-bw_id]');
4718
+ // Find all lifecycle-managed elements (have bw_lc marker class)
4719
+ const elements = element.querySelectorAll('.' + _BW_LC);
4889
4720
 
4890
4721
  elements.forEach(el => {
4891
- const id = el.getAttribute('data-bw_id');
4892
- const callback = bw._unmountCallbacks.get(id);
4893
-
4894
- if (callback) {
4895
- callback();
4896
- bw._unmountCallbacks.delete(id);
4897
- }
4722
+ var uuid = bw.getUUID(el);
4898
4723
 
4899
- // Deregister from node cache
4900
- bw._deregisterNode(el, id);
4724
+ if (uuid) {
4725
+ const callback = bw._unmountCallbacks.get(uuid);
4726
+ if (callback) {
4727
+ callback();
4728
+ bw._unmountCallbacks.delete(uuid);
4729
+ }
4901
4730
 
4902
- // Deregister UUID class from node cache
4903
- var uuidMatch = el.className && el.className.match(_UUID_RE);
4904
- if (uuidMatch) delete bw._nodeMap[uuidMatch[0]];
4731
+ // Deregister from node cache
4732
+ bw._deregisterNode(el, uuid);
4733
+ }
4905
4734
 
4906
4735
  // Clean up pub/sub subscriptions tied to this element
4907
4736
  if (el._bw_subs) {
@@ -4916,20 +4745,18 @@
4916
4745
  });
4917
4746
 
4918
4747
  // Check element itself
4919
- const id = element.getAttribute('data-bw_id');
4920
- if (id) {
4921
- const callback = bw._unmountCallbacks.get(id);
4748
+ var selfUuid = bw.getUUID(element);
4749
+ if (selfUuid) {
4750
+ delete bw._nodeMap[selfUuid];
4751
+
4752
+ const callback = bw._unmountCallbacks.get(selfUuid);
4922
4753
  if (callback) {
4923
4754
  callback();
4924
- bw._unmountCallbacks.delete(id);
4755
+ bw._unmountCallbacks.delete(selfUuid);
4925
4756
  }
4926
4757
 
4927
4758
  // Deregister from node cache
4928
- bw._deregisterNode(element, id);
4929
-
4930
- // Deregister UUID class from node cache
4931
- var elemUuidMatch = element.className && element.className.match(_UUID_RE);
4932
- if (elemUuidMatch) delete bw._nodeMap[elemUuidMatch[0]];
4759
+ bw._deregisterNode(element, selfUuid);
4933
4760
 
4934
4761
  // Clean up pub/sub subscriptions tied to element itself
4935
4762
  if (element._bw_subs) {
@@ -4940,11 +4767,11 @@
4940
4767
  delete element._bw_render;
4941
4768
  delete element._bw_refs;
4942
4769
 
4943
- // Clean up ComponentHandle back-reference
4944
- if (element._bwComponentHandle) {
4945
- element._bwComponentHandle.mounted = false;
4946
- element._bwComponentHandle.element = null;
4947
- delete element._bwComponentHandle;
4770
+ } else {
4771
+ // No UUID on element itself, but still check for _bw_subs (from bw.sub())
4772
+ if (element._bw_subs) {
4773
+ element._bw_subs.forEach(function(unsub) { unsub(); });
4774
+ delete element._bw_subs;
4948
4775
  }
4949
4776
  }
4950
4777
  };
@@ -4960,7 +4787,7 @@
4960
4787
  * Calls `el._bw_render(el, state)` and emits `bw:statechange` so other
4961
4788
  * components can react without tight coupling.
4962
4789
  *
4963
- * @param {string|Element} target - Element ID, data-bw_id, CSS selector, or DOM element
4790
+ * @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element
4964
4791
  * @returns {Element|null} The element, or null if not found / no render function
4965
4792
  * @category State Management
4966
4793
  * @see bw.patch
@@ -4985,7 +4812,7 @@
4985
4812
  * Use `bw.patch()` for lightweight value updates (scores, labels, counters)
4986
4813
  * and `bw.update()` for full structural re-renders.
4987
4814
  *
4988
- * @param {string|Element} id - Element ID, data-bw_id, CSS selector, or DOM element.
4815
+ * @param {string|Element} id - Element ID, bw_uuid_* class, CSS selector, or DOM element.
4989
4816
  * Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
4990
4817
  * @param {string|Object} content - New text content, or TACO object to replace children
4991
4818
  * @param {string} [attr] - If provided, sets this attribute instead of content
@@ -5060,7 +4887,7 @@
5060
4887
  * bubble by default so ancestor elements can listen. Use with `bw.on()` for
5061
4888
  * DOM-scoped communication between components.
5062
4889
  *
5063
- * @param {string|Element} target - Element ID, data-bw_id, CSS selector, or DOM element.
4890
+ * @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
5064
4891
  * Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
5065
4892
  * @param {string} eventName - Event name (will be prefixed with 'bw:')
5066
4893
  * @param {*} [detail] - Data to pass with the event
@@ -5087,7 +4914,7 @@
5087
4914
  * is the first argument so you don't need to destructure `e.detail`.
5088
4915
  * Events bubble, so you can listen on an ancestor element.
5089
4916
  *
5090
- * @param {string|Element} target - Element ID, data-bw_id, CSS selector, or DOM element.
4917
+ * @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
5091
4918
  * Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
5092
4919
  * @param {string} eventName - Event name (will be prefixed with 'bw:')
5093
4920
  * @param {Function} handler - Called with (detail, event)
@@ -5185,10 +5012,12 @@
5185
5012
  if (el) {
5186
5013
  if (!el._bw_subs) el._bw_subs = [];
5187
5014
  el._bw_subs.push(unsub);
5188
- // Ensure element has data-bw_id so bw.cleanup() finds it
5189
- if (!el.getAttribute('data-bw_id')) {
5190
- var bwId = 'bw_sub_' + id;
5191
- el.setAttribute('data-bw_id', bwId);
5015
+ // Ensure element has UUID + bw_lc so bw.cleanup() finds it
5016
+ if (!bw.getUUID(el)) {
5017
+ el.classList.add(bw.uuid('uuid'));
5018
+ }
5019
+ if (!el.classList.contains(_BW_LC)) {
5020
+ el.classList.add(_BW_LC);
5192
5021
  }
5193
5022
  }
5194
5023
 
@@ -5410,1087 +5239,46 @@
5410
5239
  return result;
5411
5240
  };
5412
5241
 
5413
- /**
5414
- * Extract top-level state keys that an expression depends on.
5415
- * @param {string} expr - Expression string
5416
- * @param {string[]} stateKeys - Declared state keys
5417
- * @returns {string[]} Matching dependency keys
5418
- * @private
5419
- */
5420
- bw._extractDeps = function(expr, stateKeys) {
5421
- var deps = [];
5422
- for (var i = 0; i < stateKeys.length; i++) {
5423
- var key = stateKeys[i];
5424
- // Match word boundary: key must be preceded by start/non-word and followed by non-word/end
5425
- var re = new RegExp('(?:^|[^\\w$.])' + key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:[^\\w$]|$)');
5426
- if (re.test(expr) || expr === key || expr.indexOf(key + '.') === 0) {
5427
- deps.push(key);
5428
- }
5429
- }
5430
- return deps;
5431
- };
5432
-
5433
- // ===================================================================================
5434
- // Microtask Batching
5435
- // ===================================================================================
5436
-
5437
- bw._dirtyComponents = [];
5438
- bw._flushScheduled = false;
5439
-
5440
- /**
5441
- * Schedule a microtask flush for dirty components.
5442
- * @private
5443
- */
5444
- bw._scheduleFlush = function() {
5445
- if (bw._flushScheduled) return;
5446
- bw._flushScheduled = true;
5447
- if (typeof Promise !== 'undefined') {
5448
- Promise.resolve().then(bw._doFlush);
5449
- } else {
5450
- setTimeout(bw._doFlush, 0);
5451
- }
5452
- };
5453
-
5454
- /**
5455
- * Flush all dirty components. Deduplicates by _bwId.
5456
- * @private
5457
- */
5458
- bw._doFlush = function() {
5459
- bw._flushScheduled = false;
5460
- var queue = bw._dirtyComponents.slice();
5461
- bw._dirtyComponents = [];
5462
- // Deduplicate by _bwId
5463
- var seen = {};
5464
- for (var i = 0; i < queue.length; i++) {
5465
- var comp = queue[i];
5466
- if (!seen[comp._bwId]) {
5467
- seen[comp._bwId] = true;
5468
- comp._flush();
5469
- }
5470
- }
5471
- };
5472
-
5473
- /**
5474
- * Synchronous flush for testing and imperative code.
5475
- * Forces immediate re-render of all dirty components.
5476
- *
5477
- * @category Component
5478
- */
5479
- bw.flush = function() {
5480
- bw._doFlush();
5481
- };
5482
-
5483
5242
  // ===================================================================================
5484
- // ComponentHandle unified reactive component (Phase 1)
5243
+ // Deprecation stubs for removed ComponentHandle APIs (v2.0.19)
5485
5244
  // ===================================================================================
5486
5245
 
5487
- /**
5488
- * ComponentHandle constructor.
5489
- * Wraps a TACO definition with reactive state, lifecycle hooks,
5490
- * template bindings, and named actions.
5491
- *
5492
- * @param {Object} taco - TACO definition {t, a, c, o}
5493
- * @constructor
5494
- * @private
5495
- */
5496
- function ComponentHandle(taco) {
5497
- this._bwComponent = true; // duck-type marker
5498
- this._bwId = bw.uuid('comp');
5499
- this.taco = taco;
5500
- this.element = null;
5501
- this.mounted = false;
5502
-
5503
- var o = taco.o || {};
5504
- // Copy initial state
5505
- this._state = {};
5506
- if (o.state) {
5507
- for (var k in o.state) {
5508
- if (_hop.call(o.state, k)) {
5509
- this._state[k] = o.state[k];
5510
- }
5511
- }
5512
- }
5513
- // Copy actions
5514
- this._actions = {};
5515
- if (o.actions) {
5516
- for (var k2 in o.actions) {
5517
- if (_hop.call(o.actions, k2)) {
5518
- this._actions[k2] = o.actions[k2];
5519
- }
5520
- }
5521
- }
5522
- // Promote o.methods to handle API (MFC/Qt pattern: component owns its methods)
5523
- this._methods = {};
5524
- if (o.methods) {
5525
- var self = this;
5526
- for (var k3 in o.methods) {
5527
- if (_hop.call(o.methods, k3)) {
5528
- this._methods[k3] = o.methods[k3];
5529
- (function(methodName, methodFn) {
5530
- self[methodName] = function() {
5531
- var args = [self].concat(Array.prototype.slice.call(arguments));
5532
- return methodFn.apply(null, args);
5533
- };
5534
- })(k3, o.methods[k3]);
5535
- }
5536
- }
5537
- }
5538
- // User tag for addressing via bw.message()
5539
- this._userTag = null;
5540
- // Lifecycle hooks
5541
- this._hooks = {
5542
- willMount: o.willMount || null,
5543
- mounted: o.mounted || null,
5544
- willUpdate: o.willUpdate || null,
5545
- onUpdate: o.onUpdate || o.updated || null,
5546
- unmount: o.unmount || null,
5547
- willDestroy: o.willDestroy || null
5548
- };
5549
- // Binding tracking
5550
- this._bindings = [];
5551
- this._dirtyKeys = {};
5552
- this._scheduled = false;
5553
- this._subs = [];
5554
- this._eventListeners = [];
5555
- this._registeredActions = [];
5556
- this._prevValues = {};
5557
- this._compile = !!o.compile;
5558
- this._bw_refs = {};
5559
- this._refCounter = 0;
5560
- // Child component ownership (Bug #5)
5561
- this._children = [];
5562
- this._parent = null;
5563
- // Factory metadata for BCCL rebuild (Bug #6)
5564
- this._factory = taco._bwFactory || null;
5565
- }
5566
-
5567
- // Short alias for ComponentHandle.prototype (see alias block at top of file).
5568
- // 28 method definitions × 25 chars = ~700B raw savings in minified output.
5569
- var _chp = ComponentHandle.prototype;
5570
-
5571
- // ── State Methods ──
5572
-
5573
- /**
5574
- * Get a state value. Dot-path supported: `get('user.name')`
5575
- */
5576
- _chp.get = function(key) {
5577
- return bw._evaluatePath(this._state, key);
5578
- };
5579
-
5580
- /**
5581
- * Set a state value. Dot-path supported. Schedules re-render.
5582
- * @param {string} key - State key (dot-path)
5583
- * @param {*} value - New value
5584
- * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
5585
- */
5586
- _chp.set = function(key, value, opts) {
5587
- // Dot-path set
5588
- var parts = key.split('.');
5589
- var obj = this._state;
5590
- for (var i = 0; i < parts.length - 1; i++) {
5591
- if (!_is(obj[parts[i]], 'object')) {
5592
- if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
5593
- obj[parts[i]] = {};
5594
- }
5595
- obj = obj[parts[i]];
5596
- }
5597
- obj[parts[parts.length - 1]] = value;
5598
- // Mark top-level key dirty
5599
- this._dirtyKeys[parts[0]] = true;
5600
- if (this.mounted) {
5601
- if (opts && opts.sync) {
5602
- this._flush();
5603
- } else {
5604
- this._scheduleDirty();
5605
- }
5606
- }
5607
- };
5608
-
5609
- /**
5610
- * Get a shallow clone of the full state.
5611
- */
5612
- _chp.getState = function() {
5613
- var clone = {};
5614
- for (var k in this._state) {
5615
- if (_hop.call(this._state, k)) {
5616
- clone[k] = this._state[k];
5617
- }
5618
- }
5619
- return clone;
5620
- };
5621
-
5622
- /**
5623
- * Merge multiple state keys. Schedules re-render.
5624
- * @param {Object} updates - Key-value pairs to merge
5625
- * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
5626
- */
5627
- _chp.setState = function(updates, opts) {
5628
- for (var k in updates) {
5629
- if (_hop.call(updates, k)) {
5630
- this._state[k] = updates[k];
5631
- this._dirtyKeys[k] = true;
5632
- }
5633
- }
5634
- if (this.mounted) {
5635
- if (opts && opts.sync) {
5636
- this._flush();
5637
- } else {
5638
- this._scheduleDirty();
5639
- }
5640
- }
5641
- };
5642
-
5643
- /**
5644
- * Push a value onto an array in state. Clones the array.
5645
- */
5646
- _chp.push = function(key, val) {
5647
- var arr = this.get(key);
5648
- var newArr = _isA(arr) ? arr.slice() : [];
5649
- newArr.push(val);
5650
- this.set(key, newArr);
5651
- };
5652
-
5653
- /**
5654
- * Splice an array in state. Clones the array.
5655
- */
5656
- _chp.splice = function(key, start, deleteCount) {
5657
- var arr = this.get(key);
5658
- var newArr = _isA(arr) ? arr.slice() : [];
5659
- var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
5660
- Array.prototype.splice.apply(newArr, args);
5661
- this.set(key, newArr);
5662
- };
5663
-
5664
- // ── Scheduling ──
5665
-
5666
- _chp._scheduleDirty = function() {
5667
- if (!this._scheduled) {
5668
- this._scheduled = true;
5669
- bw._dirtyComponents.push(this);
5670
- bw._scheduleFlush();
5671
- }
5672
- };
5673
-
5674
- // ── Binding Compilation ──
5675
-
5676
- /**
5677
- * Walk the TACO tree and extract ${expr} bindings.
5678
- * Creates binding descriptors with refIds for targeted DOM updates.
5679
- * @private
5680
- */
5681
- _chp._compileBindings = function() {
5682
- this._bindings = [];
5683
- this._refCounter = 0;
5684
- var stateKeys = _keys(this._state);
5685
- var self = this;
5686
-
5687
- function walkTaco(taco, path) {
5688
- if (!_is(taco, 'object') || !taco.t) return taco;
5689
-
5690
- // Check content for bindings
5691
- if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
5692
- var refId = 'bw_ref_' + self._refCounter++;
5693
- var parsed = bw._parseBindings(taco.c);
5694
- var deps = [];
5695
- for (var j = 0; j < parsed.length; j++) {
5696
- deps = deps.concat(bw._extractDeps(parsed[j].expr, stateKeys));
5697
- }
5698
- self._bindings.push({
5699
- expr: taco.c,
5700
- type: 'content',
5701
- refId: refId,
5702
- deps: deps,
5703
- template: taco.c
5704
- });
5705
- // Inject data-bw_ref on the TACO for createDOM to pick up
5706
- if (!taco.a) taco.a = {};
5707
- taco.a['data-bw_ref'] = refId;
5708
- }
5709
-
5710
- // Check attributes for bindings
5711
- if (taco.a) {
5712
- for (var attrName in taco.a) {
5713
- if (!_hop.call(taco.a, attrName)) continue;
5714
- if (attrName === 'data-bw_ref') continue;
5715
- var attrVal = taco.a[attrName];
5716
- if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
5717
- var refId2 = 'bw_ref_' + self._refCounter++;
5718
- var parsed2 = bw._parseBindings(attrVal);
5719
- var deps2 = [];
5720
- for (var j2 = 0; j2 < parsed2.length; j2++) {
5721
- deps2 = deps2.concat(bw._extractDeps(parsed2[j2].expr, stateKeys));
5722
- }
5723
- self._bindings.push({
5724
- expr: attrVal,
5725
- type: 'attribute',
5726
- attrName: attrName,
5727
- refId: refId2,
5728
- deps: deps2,
5729
- template: attrVal
5730
- });
5731
- if (!taco.a) taco.a = {};
5732
- taco.a['data-bw_ref'] = taco.a['data-bw_ref'] || refId2;
5733
- // If multiple attribute bindings on same element, store additional marker
5734
- if (taco.a['data-bw_ref'] !== refId2) {
5735
- taco.a['data-bw_ref_' + attrName] = refId2;
5736
- }
5737
- }
5738
- }
5739
- }
5740
-
5741
- // Recurse into children
5742
- if (_isA(taco.c)) {
5743
- for (var i = 0; i < taco.c.length; i++) {
5744
- // Wrap string children with ${expr} in a span so patches target the span, not the parent
5745
- if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
5746
- var mixedRefId = 'bw_ref_' + self._refCounter++;
5747
- var mixedParsed = bw._parseBindings(taco.c[i]);
5748
- var mixedDeps = [];
5749
- for (var mi = 0; mi < mixedParsed.length; mi++) {
5750
- mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
5751
- }
5752
- self._bindings.push({
5753
- expr: taco.c[i],
5754
- type: 'content',
5755
- refId: mixedRefId,
5756
- deps: mixedDeps,
5757
- template: taco.c[i]
5758
- });
5759
- // Replace string with a span wrapper so textContent targets the span only
5760
- taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
5761
- }
5762
- if (_is(taco.c[i], 'object') && taco.c[i].t) {
5763
- walkTaco(taco.c[i], path.concat(i));
5764
- }
5765
- // Handle bw.when/bw.each markers
5766
- if (taco.c[i] && taco.c[i]._bwWhen) {
5767
- var whenRefId = 'bw_ref_' + self._refCounter++;
5768
- var whenDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
5769
- self._bindings.push({
5770
- expr: taco.c[i].expr,
5771
- type: 'structural',
5772
- subtype: 'when',
5773
- refId: whenRefId,
5774
- deps: whenDeps,
5775
- branches: taco.c[i].branches,
5776
- index: i,
5777
- parentPath: path
5778
- });
5779
- taco.c[i]._refId = whenRefId;
5780
- }
5781
- if (taco.c[i] && taco.c[i]._bwEach) {
5782
- var eachRefId = 'bw_ref_' + self._refCounter++;
5783
- var eachDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
5784
- self._bindings.push({
5785
- expr: taco.c[i].expr,
5786
- type: 'structural',
5787
- subtype: 'each',
5788
- refId: eachRefId,
5789
- deps: eachDeps,
5790
- factory: taco.c[i].factory,
5791
- index: i,
5792
- parentPath: path
5793
- });
5794
- taco.c[i]._refId = eachRefId;
5795
- }
5796
- }
5797
- } else if (_is(taco.c, 'object') && taco.c.t) {
5798
- walkTaco(taco.c, path.concat(0));
5799
- }
5800
-
5801
- return taco;
5802
- }
5803
-
5804
- walkTaco(this.taco, []);
5805
- };
5806
-
5807
- // ── DOM Reference Collection ──
5808
-
5809
- /**
5810
- * Build ref map from the live DOM after createDOM.
5811
- * @private
5812
- */
5813
- _chp._collectRefs = function() {
5814
- this._bw_refs = {};
5815
- if (!this.element) return;
5816
- var els = this.element.querySelectorAll('[data-bw_ref]');
5817
- for (var i = 0; i < els.length; i++) {
5818
- this._bw_refs[els[i].getAttribute('data-bw_ref')] = els[i];
5819
- }
5820
- // Also check root element
5821
- var rootRef = this.element.getAttribute && this.element.getAttribute('data-bw_ref');
5822
- if (rootRef) {
5823
- this._bw_refs[rootRef] = this.element;
5824
- }
5825
- };
5826
-
5827
- // ── Lifecycle ──
5828
-
5829
- /**
5830
- * Mount the component into a parent DOM element.
5831
- * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
5832
- * @param {Element} parentEl - DOM element to mount into
5833
- */
5834
- _chp.mount = function(parentEl) {
5835
- // willMount hook
5836
- if (this._hooks.willMount) this._hooks.willMount(this);
5837
-
5838
- // Save original TACO for re-renders (structural changes clone from this)
5839
- if (!this._originalTaco) {
5840
- this._originalTaco = this.taco;
5841
- }
5842
-
5843
- // Deep-clone TACO so binding annotations don't mutate original.
5844
- // Custom clone to preserve _bwWhen/_bwEach markers and their factory functions.
5845
- this.taco = this._deepCloneTaco(this._originalTaco);
5846
-
5847
- // Compile bindings (annotates TACO with data-bw_ref attributes)
5848
- this._compileBindings();
5849
-
5850
- // Prepare TACO: resolve initial binding values, evaluate when/each
5851
- this._prepareTaco(this.taco);
5852
-
5853
- // Register named actions in function registry
5854
- var self = this;
5855
- for (var actionName in this._actions) {
5856
- if (_hop.call(this._actions, actionName)) {
5857
- var registeredName = this._bwId + '_' + actionName;
5858
- (function(aName) {
5859
- bw.funcRegister(function(evt) {
5860
- self._actions[aName](self, evt);
5861
- }, registeredName);
5862
- })(actionName);
5863
- this._registeredActions.push(registeredName);
5864
- }
5865
- }
5866
-
5867
- // Wire action names in onclick etc. to dispatch strings
5868
- this._wireActions(this.taco);
5869
-
5870
- // Create DOM (strip o before createDOM to prevent double lifecycle)
5871
- var tacoForDOM = this._tacoForDOM(this.taco);
5872
- this.element = bw.createDOM(tacoForDOM);
5873
- this.element._bwComponentHandle = this;
5874
- this.element.setAttribute('data-bw_comp_id', this._bwId);
5875
-
5876
- // Restore o.render from original TACO (stripped by _tacoForDOM)
5877
- if (this.taco.o && this.taco.o.render) {
5878
- this.element._bw_render = this.taco.o.render;
5879
- }
5880
- if (this._userTag) {
5881
- this.element.classList.add(this._userTag);
5882
- }
5883
-
5884
- // Append to parent
5885
- parentEl.appendChild(this.element);
5886
-
5887
- // Collect refs from live DOM
5888
- this._collectRefs();
5889
-
5890
- // Resolve initial bindings and apply to DOM
5891
- this._resolveAndApplyAll();
5892
-
5893
- this.mounted = true;
5894
-
5895
- // Scan for child ComponentHandles and link parent/child (Bug #5)
5896
- var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
5897
- for (var ci = 0; ci < childEls.length; ci++) {
5898
- var ch = childEls[ci]._bwComponentHandle;
5899
- if (ch && ch !== this && !ch._parent) {
5900
- ch._parent = this;
5901
- this._children.push(ch);
5902
- }
5903
- }
5904
-
5905
- // mounted hook (backward compat: fn.length === 2 wraps (el, state))
5906
- if (this._hooks.mounted) {
5907
- if (this._hooks.mounted.length === 2) {
5908
- this._hooks.mounted(this.element, this.getState());
5909
- } else {
5910
- this._hooks.mounted(this);
5911
- }
5912
- }
5913
-
5914
- // Invoke o.render on initial mount (if present)
5915
- if (this.element._bw_render) {
5916
- this.element._bw_render(this.element, this._state);
5917
- }
5918
- };
5919
-
5920
- /**
5921
- * Prepare TACO for initial render: resolve when/each markers.
5922
- * @private
5923
- */
5924
- _chp._prepareTaco = function(taco) {
5925
- if (!_is(taco, 'object')) return;
5926
-
5927
- if (_isA(taco.c)) {
5928
- for (var i = taco.c.length - 1; i >= 0; i--) {
5929
- var child = taco.c[i];
5930
- if (child && child._bwWhen) {
5931
- var exprStr = child.expr.replace(/^\$\{|\}$/g, '');
5932
- var val;
5933
- if (this._compile) {
5934
- try {
5935
- val = (new Function('state', 'with(state){return (' + exprStr + ');}'))(this._state);
5936
- } catch(e) { val = false; }
5937
- } else {
5938
- val = bw._evaluatePath(this._state, exprStr);
5939
- }
5940
- var branch = val ? child.branches[0] : (child.branches[1] || null);
5941
- if (branch) {
5942
- // Wrap in a container so we can track it
5943
- taco.c[i] = { t: 'span', a: { 'data-bw_when': child._refId, style: 'display:contents' }, c: branch };
5944
- } else {
5945
- taco.c[i] = { t: 'span', a: { 'data-bw_when': child._refId, style: 'display:contents' }, c: '' };
5946
- }
5947
- }
5948
- if (child && child._bwEach) {
5949
- var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
5950
- var arr = bw._evaluatePath(this._state, eachExprStr);
5951
- var items = [];
5952
- if (_isA(arr)) {
5953
- for (var j = 0; j < arr.length; j++) {
5954
- items.push(child.factory(arr[j], j));
5955
- }
5956
- }
5957
- taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
5958
- }
5959
- if (_is(taco.c[i], 'object') && taco.c[i].t) {
5960
- this._prepareTaco(taco.c[i]);
5961
- }
5962
- }
5963
- } else if (_is(taco.c, 'object') && taco.c.t) {
5964
- this._prepareTaco(taco.c);
5965
- }
5966
- };
5967
-
5968
- /**
5969
- * Wire action name strings (in onclick etc.) to dispatch function calls.
5970
- * @private
5971
- */
5972
- _chp._wireActions = function(taco) {
5973
- if (!_is(taco, 'object') || !taco.t) return;
5974
- if (taco.a) {
5975
- for (var key in taco.a) {
5976
- if (!_hop.call(taco.a, key)) continue;
5977
- if (key.startsWith('on') && _is(taco.a[key], 'string')) {
5978
- var actionName = taco.a[key];
5979
- if (actionName in this._actions) {
5980
- var registeredName = this._bwId + '_' + actionName;
5981
- // Replace string with actual function for createDOM event binding
5982
- (function(rName) {
5983
- taco.a[key] = function(evt) {
5984
- bw.funcGetById(rName)(evt);
5985
- };
5986
- })(registeredName);
5987
- }
5988
- }
5989
- }
5990
- }
5991
- if (_isA(taco.c)) {
5992
- for (var i = 0; i < taco.c.length; i++) {
5993
- this._wireActions(taco.c[i]);
5994
- }
5995
- } else if (_is(taco.c, 'object') && taco.c.t) {
5996
- this._wireActions(taco.c);
5997
- }
5998
- };
5999
-
6000
- /**
6001
- * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
6002
- * @private
6003
- */
6004
- _chp._deepCloneTaco = function(taco) {
6005
- if (taco == null) return taco;
6006
- // Preserve _bwWhen / _bwEach markers (contain functions)
6007
- if (taco._bwWhen) {
6008
- return { _bwWhen: true, expr: taco.expr, branches: [
6009
- this._deepCloneTaco(taco.branches[0]),
6010
- taco.branches[1] ? this._deepCloneTaco(taco.branches[1]) : null
6011
- ], _refId: taco._refId };
6012
- }
6013
- if (taco._bwEach) {
6014
- return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
6015
- }
6016
- if (!_is(taco, 'object') || !taco.t) return taco;
6017
- var result = { t: taco.t };
6018
- if (taco.a) {
6019
- result.a = {};
6020
- for (var k in taco.a) {
6021
- if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
6022
- }
6023
- }
6024
- if (taco.c != null) {
6025
- if (_isA(taco.c)) {
6026
- result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
6027
- } else if (_is(taco.c, 'object')) {
6028
- result.c = this._deepCloneTaco(taco.c);
6029
- } else {
6030
- result.c = taco.c;
6031
- }
6032
- }
6033
- if (taco.o) result.o = taco.o; // Keep o reference (not deep-cloned; hooks are functions)
6034
- return result;
6035
- };
6036
-
6037
- /**
6038
- * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
6039
- * @private
6040
- */
6041
- _chp._tacoForDOM = function(taco) {
6042
- if (!_is(taco, 'object') || !taco.t) return taco;
6043
- var result = { t: taco.t };
6044
- if (taco.a) result.a = taco.a;
6045
- if (taco.c != null) {
6046
- if (_isA(taco.c)) {
6047
- result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
6048
- } else if (_is(taco.c, 'object') && taco.c.t) {
6049
- result.c = this._tacoForDOM(taco.c);
6050
- } else {
6051
- result.c = taco.c;
6052
- }
6053
- }
6054
- // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
6055
- if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
6056
- _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
6057
- '>. Use onclick attribute or bw.component() for child interactivity.');
6058
- }
6059
- return result;
6060
- };
6061
-
6062
- /**
6063
- * Unmount: remove from DOM, deactivate, preserve state for re-mount.
6064
- */
6065
- _chp.unmount = function() {
6066
- if (!this.mounted) return;
6067
-
6068
- // unmount hook
6069
- if (this._hooks.unmount) {
6070
- this._hooks.unmount(this);
6071
- }
6072
-
6073
- // Remove DOM event listeners
6074
- for (var i = 0; i < this._eventListeners.length; i++) {
6075
- var l = this._eventListeners[i];
6076
- if (this.element) {
6077
- this.element.removeEventListener(l.event, l.handler);
6078
- }
6079
- }
6080
- this._eventListeners = [];
6081
-
6082
- // Unsubscribe pub/sub
6083
- for (var j = 0; j < this._subs.length; j++) {
6084
- this._subs[j]();
6085
- }
6086
- this._subs = [];
6087
-
6088
- // Remove from DOM
6089
- if (this.element && this.element.parentNode) {
6090
- this.element.parentNode.removeChild(this.element);
6091
- }
6092
-
6093
- this.mounted = false;
6094
- // State preserved — can re-mount
6095
- };
6096
-
6097
- /**
6098
- * Destroy: unmount + clear state + unregister actions.
6099
- */
6100
- _chp.destroy = function() {
6101
- // willDestroy hook
6102
- if (this._hooks.willDestroy) {
6103
- this._hooks.willDestroy(this);
6104
- }
6105
-
6106
- // Cascade destroy to children depth-first (Bug #5)
6107
- for (var ci = this._children.length - 1; ci >= 0; ci--) {
6108
- this._children[ci].destroy();
6109
- }
6110
- this._children = [];
6111
- if (this._parent) {
6112
- var idx = this._parent._children.indexOf(this);
6113
- if (idx >= 0) this._parent._children.splice(idx, 1);
6114
- this._parent = null;
6115
- }
6116
-
6117
- this.unmount();
6118
-
6119
- // Unregister actions from function registry
6120
- for (var i = 0; i < this._registeredActions.length; i++) {
6121
- bw.funcUnregister(this._registeredActions[i]);
6122
- }
6123
- this._registeredActions = [];
6124
-
6125
- // Clear state
6126
- this._state = {};
6127
- this._bindings = [];
6128
- this._bw_refs = {};
6129
- this._prevValues = {};
6130
- this._dirtyKeys = {};
6131
- if (this.element) {
6132
- delete this.element._bwComponentHandle;
6133
- this.element = null;
6134
- }
6135
- };
6136
-
6137
- // ── Flush & Binding Resolution ──
6138
-
6139
- /**
6140
- * Flush dirty state: resolve changed bindings and apply to DOM.
6141
- * @private
6142
- */
6143
- _chp._flush = function() {
6144
- this._scheduled = false;
6145
- var changedKeys = _keys(this._dirtyKeys);
6146
- this._dirtyKeys = {};
6147
- if (changedKeys.length === 0 || !this.mounted) return;
6148
-
6149
- // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
6150
- // rebuild the TACO from the factory with merged state (Bug #6)
6151
- if (this._factory) {
6152
- var rebuildNeeded = false;
6153
- for (var fi = 0; fi < changedKeys.length; fi++) {
6154
- if (_hop.call(this._factory.props, changedKeys[fi])) {
6155
- rebuildNeeded = true; break;
6156
- }
6157
- }
6158
- if (rebuildNeeded) {
6159
- var merged = {};
6160
- for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
6161
- for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
6162
- this._factory.props = merged;
6163
- var newTaco = bw.make(this._factory.type, merged);
6164
- newTaco._bwFactory = this._factory;
6165
- this.taco = newTaco;
6166
- this._originalTaco = this._deepCloneTaco(newTaco);
6167
- this._render();
6168
- if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
6169
- return;
6170
- }
6171
- }
6172
-
6173
- // willUpdate hook
6174
- if (this._hooks.willUpdate) {
6175
- this._hooks.willUpdate(this, changedKeys);
6176
- }
6177
-
6178
- // Check if any structural bindings are affected
6179
- var needsFullRender = false;
6180
- for (var i = 0; i < this._bindings.length; i++) {
6181
- var b = this._bindings[i];
6182
- if (b.type === 'structural') {
6183
- for (var j = 0; j < b.deps.length; j++) {
6184
- if (changedKeys.indexOf(b.deps[j]) >= 0) {
6185
- needsFullRender = true;
6186
- break;
6187
- }
6188
- }
6189
- if (needsFullRender) break;
6190
- }
6191
- }
6192
-
6193
- if (needsFullRender) {
6194
- this._render();
6195
- } else {
6196
- var patches = this._resolveBindings(changedKeys);
6197
- this._applyPatches(patches);
6198
- }
6199
-
6200
- // onUpdate hook
6201
- if (this._hooks.onUpdate) {
6202
- this._hooks.onUpdate(this, changedKeys);
6203
- }
6204
- };
6205
-
6206
- /**
6207
- * Resolve bindings whose deps intersect with changedKeys.
6208
- * Returns list of patches to apply.
6209
- * @private
6210
- */
6211
- _chp._resolveBindings = function(changedKeys) {
6212
- var patches = [];
6213
- for (var i = 0; i < this._bindings.length; i++) {
6214
- var b = this._bindings[i];
6215
- if (b.type === 'structural') continue;
6216
-
6217
- // Check if any dep matches
6218
- var affected = false;
6219
- for (var j = 0; j < b.deps.length; j++) {
6220
- if (changedKeys.indexOf(b.deps[j]) >= 0) {
6221
- affected = true;
6222
- break;
6223
- }
6224
- }
6225
- if (!affected) continue;
6226
-
6227
- // Evaluate
6228
- var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
6229
- var prevKey = b.refId + '_' + (b.attrName || 'content');
6230
- if (this._prevValues[prevKey] !== newVal) {
6231
- this._prevValues[prevKey] = newVal;
6232
- patches.push({
6233
- refId: b.refId,
6234
- type: b.type,
6235
- attrName: b.attrName,
6236
- value: newVal
6237
- });
6238
- }
6239
- }
6240
- return patches;
6241
- };
6242
-
6243
- /**
6244
- * Apply patches to DOM.
6245
- * @private
6246
- */
6247
- _chp._applyPatches = function(patches) {
6248
- for (var i = 0; i < patches.length; i++) {
6249
- var p = patches[i];
6250
- var el = this._bw_refs[p.refId];
6251
- if (!el) {
6252
- if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
6253
- continue;
6254
- }
6255
- if (p.type === 'content') {
6256
- el.textContent = p.value;
6257
- } else if (p.type === 'attribute') {
6258
- if (p.attrName === 'class') {
6259
- el.className = p.value;
6260
- } else {
6261
- el.setAttribute(p.attrName, p.value);
6262
- }
6263
- }
6264
- }
6265
- };
6266
-
6267
- /**
6268
- * Resolve all bindings and apply (used for initial render).
6269
- * @private
6270
- */
6271
- _chp._resolveAndApplyAll = function() {
6272
- var patches = [];
6273
- for (var i = 0; i < this._bindings.length; i++) {
6274
- var b = this._bindings[i];
6275
- if (b.type === 'structural') continue;
6276
-
6277
- var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
6278
- var prevKey = b.refId + '_' + (b.attrName || 'content');
6279
- this._prevValues[prevKey] = newVal;
6280
- patches.push({
6281
- refId: b.refId,
6282
- type: b.type,
6283
- attrName: b.attrName,
6284
- value: newVal
6285
- });
6286
- }
6287
- this._applyPatches(patches);
6288
- };
6289
-
6290
- /**
6291
- * Full re-render for structural changes (when/each branch switches).
6292
- * @private
6293
- */
6294
- _chp._render = function() {
6295
- if (!this.element || !this.element.parentNode) return;
6296
- var parent = this.element.parentNode;
6297
- var nextSibling = this.element.nextSibling;
6298
-
6299
- // Remove old DOM
6300
- parent.removeChild(this.element);
6301
-
6302
- // Re-prepare TACO with current state (deep clone preserving functions)
6303
- this.taco = this._deepCloneTaco(this._originalTaco || this.taco);
6304
-
6305
- // Re-compile bindings and prepare
6306
- this._compileBindings();
6307
- this._prepareTaco(this.taco);
6308
- this._wireActions(this.taco);
6309
-
6310
- var tacoForDOM = this._tacoForDOM(this.taco);
6311
- this.element = bw.createDOM(tacoForDOM);
6312
- this.element._bwComponentHandle = this;
6313
- this.element.setAttribute('data-bw_comp_id', this._bwId);
6314
-
6315
- // Re-insert at same position
6316
- if (nextSibling) {
6317
- parent.insertBefore(this.element, nextSibling);
6318
- } else {
6319
- parent.appendChild(this.element);
6320
- }
6321
-
6322
- // Re-collect refs and apply all bindings
6323
- this._collectRefs();
6324
- this._resolveAndApplyAll();
6325
- };
6326
-
6327
- // ── Event & Pub/Sub Methods ──
6328
-
6329
- /**
6330
- * Add a DOM event listener on the component's root element.
6331
- * @param {string} event - Event name (e.g., 'click')
6332
- * @param {Function} handler - Event handler
6333
- */
6334
- _chp.on = function(event, handler) {
6335
- if (this.element) {
6336
- this.element.addEventListener(event, handler);
6337
- }
6338
- this._eventListeners.push({ event: event, handler: handler });
6339
- };
6340
-
6341
- /**
6342
- * Remove a DOM event listener.
6343
- * @param {string} event - Event name
6344
- * @param {Function} handler - Handler to remove
6345
- */
6346
- _chp.off = function(event, handler) {
6347
- if (this.element) {
6348
- this.element.removeEventListener(event, handler);
6349
- }
6350
- this._eventListeners = this._eventListeners.filter(function(l) {
6351
- return !(l.event === event && l.handler === handler);
6352
- });
6353
- };
6354
-
6355
- /**
6356
- * Subscribe to a pub/sub topic. Lifecycle-tied: auto-unsubs on destroy.
6357
- * @param {string} topic - Topic name
6358
- * @param {Function} handler - Handler function
6359
- * @returns {Function} Unsubscribe function
6360
- */
6361
- _chp.sub = function(topic, handler) {
6362
- var unsub = bw.sub(topic, handler);
6363
- this._subs.push(unsub);
6364
- return unsub;
6365
- };
6366
-
6367
- /**
6368
- * Call a named action.
6369
- * @param {string} name - Action name
6370
- * @param {...*} args - Arguments passed after comp
6371
- */
6372
- _chp.action = function(name) {
6373
- var fn = this._actions[name];
6374
- if (!fn) {
6375
- _cw('ComponentHandle.action: unknown action "' + name + '"');
6376
- return;
6377
- }
6378
- var args = [this].concat(Array.prototype.slice.call(arguments, 1));
6379
- return fn.apply(null, args);
6380
- };
6381
-
6382
- /**
6383
- * querySelector within the component's DOM.
6384
- * @param {string} sel - CSS selector
6385
- * @returns {Element|null}
6386
- */
6387
- _chp.select = function(sel) {
6388
- return this.element ? this.element.querySelector(sel) : null;
6389
- };
6390
-
6391
- /**
6392
- * querySelectorAll within the component's DOM.
6393
- * @param {string} sel - CSS selector
6394
- * @returns {Element[]}
6395
- */
6396
- _chp.selectAll = function(sel) {
6397
- if (!this.element) return [];
6398
- return Array.prototype.slice.call(this.element.querySelectorAll(sel));
6399
- };
5246
+ bw._extractDeps = undefined;
5247
+ bw._dirtyComponents = undefined;
5248
+ bw._flushScheduled = undefined;
5249
+ bw._scheduleFlush = undefined;
5250
+ bw._doFlush = undefined;
5251
+ bw._ComponentHandle = undefined;
6400
5252
 
6401
5253
  /**
6402
- * Tag this component with a user-defined ID for addressing via bw.message().
6403
- * The tag is added as a CSS class on the root element (DOM IS the registry).
6404
- * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
6405
- * @returns {ComponentHandle} this (for chaining)
6406
- */
6407
- _chp.userTag = function(tag) {
6408
- this._userTag = tag;
6409
- if (this.element) {
6410
- this.element.classList.add(tag);
6411
- }
6412
- return this;
6413
- };
6414
-
6415
- // Expose ComponentHandle on bw (for testing and advanced use)
6416
- bw._ComponentHandle = ComponentHandle;
6417
-
6418
- // ===================================================================================
6419
- // Control Flow Helpers
6420
- // ===================================================================================
6421
-
6422
- /**
6423
- * Conditional rendering helper.
6424
- * Returns a marker object that ComponentHandle detects during binding compilation.
6425
- * In static contexts (bw.html with state), evaluates immediately.
6426
- *
6427
- * @param {string} expr - Expression string like '${loggedIn}'
6428
- * @param {Object} tacoTrue - TACO to render when truthy
6429
- * @param {Object} [tacoFalse] - TACO to render when falsy
6430
- * @returns {Object} Marker object with _bwWhen flag
5254
+ * No-op flush (ComponentHandle removed in v2.0.19).
5255
+ * Kept as no-op for backward compatibility.
6431
5256
  * @category Component
6432
5257
  */
6433
- bw.when = function(expr, tacoTrue, tacoFalse) {
6434
- return { _bwWhen: true, expr: expr, branches: [tacoTrue, tacoFalse || null] };
6435
- };
5258
+ bw.flush = function() {};
6436
5259
 
6437
- /**
6438
- * List rendering helper.
6439
- * Returns a marker object that ComponentHandle detects during binding compilation.
6440
- *
6441
- * @param {string} expr - Expression string like '${items}'
6442
- * @param {Function} fn - Factory function(item, index) returning TACO
6443
- * @returns {Object} Marker object with _bwEach flag
6444
- * @category Component
6445
- */
6446
- bw.each = function(expr, fn) {
6447
- return { _bwEach: true, expr: expr, factory: fn };
6448
- };
6449
5260
 
6450
- // ===================================================================================
6451
- // bw.component() Factory for ComponentHandle
6452
- // ===================================================================================
5261
+ bw.when = function() { throw new Error('bw.when() removed in v2.0.19. Use conditional logic in o.render instead.'); };
5262
+ bw.each = function() { throw new Error('bw.each() removed in v2.0.19. Use array mapping in o.render instead.'); };
5263
+ bw.component = function() { throw new Error('bw.component() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
6453
5264
 
6454
- /**
6455
- * Create a ComponentHandle from a TACO definition.
6456
- * The returned handle has .get(), .set(), .mount(), .destroy(), etc.
6457
- *
6458
- * @param {Object} taco - TACO definition with {t, a, c, o}
6459
- * @returns {ComponentHandle} Reactive component handle
6460
- * @category Component
6461
- * @see bw.DOM
6462
- * @example
6463
- * var counter = bw.component({
6464
- * t: 'div', c: [{ t: 'h3', c: 'Count: ${count}' }],
6465
- * o: { state: { count: 0 } }
6466
- * });
6467
- * bw.DOM('#app', counter);
6468
- * counter.set('count', 42); // DOM auto-updates
6469
- */
6470
- bw.component = function(taco) {
6471
- return new ComponentHandle(taco);
6472
- };
6473
5265
 
6474
5266
  // ===================================================================================
6475
5267
  // bw.message() — SendMessage() for the web
6476
5268
  // ===================================================================================
6477
5269
 
6478
5270
  /**
6479
- * Dispatch a message to a component by UUID or user tag.
6480
- * Finds the component's DOM element, looks up its ComponentHandle,
6481
- * and calls the named method. This is the bitwrench equivalent of
6482
- * Win32 SendMessage(hwnd, msg, wParam, lParam).
5271
+ * Dispatch a message to a component by UUID, CSS class, or selector.
5272
+ * Finds the element, looks up el.bw, and calls the named method.
5273
+ * This is the bitwrench equivalent of Win32 SendMessage(hwnd, msg, wParam, lParam).
6483
5274
  *
6484
- * @param {string} target - Component UUID (bw_uuid_*), comp ID (data-bw_comp_id), or user tag (CSS class)
6485
- * @param {string} action - Method name to call on the component
5275
+ * @param {string} target - Component UUID (bw_uuid_*), CSS class, or selector
5276
+ * @param {string} action - Method name to call on el.bw
6486
5277
  * @param {*} data - Data to pass to the method
6487
5278
  * @returns {boolean} True if message was dispatched successfully
6488
5279
  * @category Component
6489
5280
  * @example
6490
- * // Tag a component
6491
- * myDash.userTag('dashboard_prod');
6492
- * // Dispatch locally
6493
- * bw.message('dashboard_prod', 'addAlert', { severity: 'warning', text: 'CPU spike' });
5281
+ * bw.message('my_carousel', 'goToSlide', 2);
6494
5282
  * // Or from SSE handler:
6495
5283
  * es.onmessage = function(e) {
6496
5284
  * var msg = JSON.parse(e.data);
@@ -6498,23 +5286,13 @@
6498
5286
  * };
6499
5287
  */
6500
5288
  bw.message = function(target, action, data) {
6501
- // Try bw._el() first (handles UUID class, nodeMap cache, getElementById)
6502
5289
  var el = bw._el(target);
6503
- // Then try data-bw_comp_id attribute
6504
- if (!el || !el._bwComponentHandle) {
6505
- el = bw.$('[data-bw_comp_id="' + target + '"]')[0];
6506
- }
6507
- // Then try CSS class (user tag)
6508
- if (!el || !el._bwComponentHandle) {
6509
- el = bw.$('.' + target)[0];
6510
- }
6511
- if (!el || !el._bwComponentHandle) return false;
6512
- var comp = el._bwComponentHandle;
6513
- if (!_is(comp[action], 'function')) {
6514
- _cw('bw.message: unknown action "' + action + '" on component ' + target);
5290
+ if (!el) el = bw.$('.' + target)[0];
5291
+ if (!el || !el.bw || typeof el.bw[action] !== 'function') {
5292
+ _cw('bw.message: no handle method "' + action + '" on ' + target);
6515
5293
  return false;
6516
5294
  }
6517
- comp[action](data);
5295
+ el.bw[action](data);
6518
5296
  return true;
6519
5297
  };
6520
5298
 
@@ -6740,132 +5518,29 @@
6740
5518
  // ===================================================================================
6741
5519
 
6742
5520
  /**
6743
- * Inspect a component's state, bindings, methods, and metadata.
6744
- * Works with DOM elements, CSS selectors, or ComponentHandle objects.
6745
- * Returns the ComponentHandle for console chaining.
5521
+ * Inspect a DOM element's bitwrench state, handle methods, and metadata.
5522
+ * Works with DOM elements or CSS selectors.
6746
5523
  *
6747
- * @param {string|Element|ComponentHandle} target - Selector, element, or handle
6748
- * @returns {ComponentHandle|null} The component handle, or null if not found
5524
+ * @param {string|Element} target - Selector or DOM element
5525
+ * @returns {Element|null} The element, or null if not found
6749
5526
  * @category Component
6750
5527
  * @example
6751
- * // In browser console, click element in Elements panel then:
5528
+ * bw.inspect('#my-carousel');
6752
5529
  * bw.inspect($0);
6753
- * // Or by selector:
6754
- * var h = bw.inspect('#my-dashboard');
6755
- * h.set('count', 99); // chain from returned handle
6756
5530
  */
6757
5531
  bw.inspect = function(target) {
6758
- var el = target;
6759
- var comp;
6760
- if (target && target._bwComponent === true) {
6761
- el = target.element;
6762
- comp = target;
6763
- } else {
6764
- if (_is(target, 'string')) {
6765
- el = bw.$(target)[0];
6766
- }
6767
- if (!el) {
6768
- _cw('bw.inspect: element not found');
6769
- return null;
6770
- }
6771
- comp = el._bwComponentHandle;
6772
- }
6773
- if (!comp) {
6774
- _cl('bw.inspect: no ComponentHandle on this element');
6775
- _cl(' Tag:', el.tagName);
6776
- _cl(' Classes:', el.className);
6777
- _cl(' _bw_state:', el._bw_state || '(none)');
6778
- return null;
6779
- }
6780
- var deps = comp._bindings.reduce(function(s, b) {
6781
- return s.concat(b.deps || []);
6782
- }, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
6783
- console.group('Component: ' + comp._bwId);
6784
- _cl('State:', comp._state);
6785
- _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
6786
- _cl('Methods:', _keys(comp._methods));
6787
- _cl('Actions:', _keys(comp._actions));
6788
- _cl('User tag:', comp._userTag || '(none)');
6789
- _cl('Mounted:', comp.mounted);
6790
- _cl('Element:', comp.element);
5532
+ var el = _is(target, 'string') ? bw.$(target)[0] : target;
5533
+ if (!el) { _cw('bw.inspect: element not found'); return null; }
5534
+ console.group('Element: ' + (bw.getUUID(el) || el.id || el.tagName));
5535
+ _cl('State:', el._bw_state || '(none)');
5536
+ _cl('Handle:', el.bw ? _keys(el.bw) : '(none)');
5537
+ _cl('Classes:', el.className);
5538
+ _cl('Refs:', el._bw_refs || '(none)');
6791
5539
  console.groupEnd();
6792
- return comp;
5540
+ return el;
6793
5541
  };
6794
5542
 
6795
- // ===================================================================================
6796
- // bw.compile() — Pre-compile TACO into optimized factory
6797
- // ===================================================================================
6798
-
6799
- /**
6800
- * Pre-compile a TACO definition into a factory function.
6801
- * The factory produces ComponentHandles with pre-compiled binding evaluators.
6802
- *
6803
- * Phase 1: validates API surface. Template cloning optimization deferred.
6804
- *
6805
- * @param {Object} taco - TACO definition
6806
- * @returns {Function} Factory function(initialState?) → ComponentHandle
6807
- * @category Component
6808
- */
6809
- bw.compile = function(taco) {
6810
- // Pre-extract all binding expressions
6811
- var precompiled = [];
6812
- function walkExpressions(node) {
6813
- if (!_is(node, 'object')) return;
6814
- if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
6815
- var parsed = bw._parseBindings(node.c);
6816
- for (var i = 0; i < parsed.length; i++) {
6817
- try {
6818
- precompiled.push({
6819
- expr: parsed[i].expr,
6820
- fn: new Function('state', 'with(state){return (' + parsed[i].expr + ');}')
6821
- });
6822
- } catch(e) {
6823
- precompiled.push({ expr: parsed[i].expr, fn: function() { return ''; } });
6824
- }
6825
- }
6826
- }
6827
- if (node.a) {
6828
- for (var key in node.a) {
6829
- if (_hop.call(node.a, key)) {
6830
- var v = node.a[key];
6831
- if (_is(v, 'string') && v.indexOf('${') >= 0) {
6832
- var parsed2 = bw._parseBindings(v);
6833
- for (var j = 0; j < parsed2.length; j++) {
6834
- try {
6835
- precompiled.push({
6836
- expr: parsed2[j].expr,
6837
- fn: new Function('state', 'with(state){return (' + parsed2[j].expr + ');}')
6838
- });
6839
- } catch(e2) {
6840
- precompiled.push({ expr: parsed2[j].expr, fn: function() { return ''; } });
6841
- }
6842
- }
6843
- }
6844
- }
6845
- }
6846
- }
6847
- if (_isA(node.c)) {
6848
- for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
6849
- } else if (_is(node.c, 'object') && node.c.t) {
6850
- walkExpressions(node.c);
6851
- }
6852
- }
6853
- walkExpressions(taco);
6854
-
6855
- return function(initialState) {
6856
- var handle = new ComponentHandle(taco);
6857
- handle._compile = true;
6858
- handle._precompiledBindings = precompiled;
6859
- if (initialState) {
6860
- for (var k in initialState) {
6861
- if (_hop.call(initialState, k)) {
6862
- handle._state[k] = initialState[k];
6863
- }
6864
- }
6865
- }
6866
- return handle;
6867
- };
6868
- };
5543
+ bw.compile = function() { throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
6869
5544
 
6870
5545
  /**
6871
5546
  * Generate CSS from JavaScript objects.
@@ -8091,8 +6766,8 @@
8091
6766
  };
8092
6767
  }
8093
6768
 
8094
- // Generate unique ID if not provided
8095
- const componentId = taco.o?.id || bw.uuid();
6769
+ // Generate unique UUID class if not provided
6770
+ const componentId = taco.o?.id || bw.uuid('uuid');
8096
6771
 
8097
6772
  // Create DOM element
8098
6773
  let domElement;
@@ -8107,9 +6782,10 @@
8107
6782
  };
8108
6783
  }
8109
6784
 
8110
- // Add component ID to element
8111
- domElement.setAttribute('data-bw_id', componentId);
8112
-
6785
+ // Add component ID as class + lifecycle marker
6786
+ domElement.classList.add(componentId);
6787
+ domElement.classList.add(_BW_LC);
6788
+
8113
6789
  // Insert into DOM based on position
8114
6790
  try {
8115
6791
  switch(position) {
@@ -8183,7 +6859,8 @@
8183
6859
 
8184
6860
  // Re-render
8185
6861
  const newElement = bw.createDOM(this._taco);
8186
- newElement.setAttribute('data-bw_id', componentId);
6862
+ newElement.classList.add(componentId);
6863
+ newElement.classList.add(_BW_LC);
8187
6864
 
8188
6865
  // Replace in DOM
8189
6866
  parent.replaceChild(newElement, this.element);
@@ -8370,13 +7047,12 @@
8370
7047
  // Variant class helper: bw.variantClass('primary') → 'bw_primary'
8371
7048
  bw.variantClass = variantClass;
8372
7049
 
8373
- // Create functions that return handles (plain renderComponent, no Handle overlay)
7050
+ // Create functions that return DOM elements (createCard, createTable, etc.)
8374
7051
  Object.entries(components).forEach(([name, fn]) => {
8375
7052
  if (name.startsWith('make')) {
8376
- const createName = 'create' + name.substring(4); // createCard, createTable, etc.
7053
+ const createName = 'create' + name.substring(4);
8377
7054
  bw[createName] = function(props) {
8378
- const taco = fn(props);
8379
- return bw.renderComponent(taco);
7055
+ return bw.createDOM(fn(props));
8380
7056
  };
8381
7057
  }
8382
7058
  });