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