bitwrench 2.0.18 → 2.0.20

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