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
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
4
4
  typeof define === 'function' && define.amd ? define(factory) :
@@ -190,14 +190,14 @@
190
190
  */
191
191
 
192
192
  var VERSION_INFO = {
193
- version: '2.0.18',
193
+ version: '2.0.20',
194
194
  name: 'bitwrench',
195
195
  description: 'A library for javascript UI functions.',
196
196
  license: 'BSD-2-Clause',
197
197
  homepage: 'https://deftio.github.com/bitwrench/pages',
198
198
  repository: 'git+https://github.com/deftio/bitwrench.git',
199
199
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
200
- buildDate: '2026-03-17T00:50:09.505Z'
200
+ buildDate: '2026-03-23T05:19:31.951Z'
201
201
  };
202
202
 
203
203
  /**
@@ -3088,7 +3088,9 @@
3088
3088
  '.bw_modal_header': {
3089
3089
  'display': 'flex',
3090
3090
  'align-items': 'center',
3091
- 'justify-content': 'space-between'
3091
+ 'justify-content': 'space-between',
3092
+ 'padding': '1rem 1.25rem',
3093
+ 'border-bottom': '1px solid transparent'
3092
3094
  },
3093
3095
  '.bw_modal_title': {
3094
3096
  'margin': '0',
@@ -3098,14 +3100,17 @@
3098
3100
  },
3099
3101
  '.bw_modal_body': {
3100
3102
  'position': 'relative',
3101
- 'flex': '1 1 auto'
3103
+ 'flex': '1 1 auto',
3104
+ 'padding': '1rem 1.25rem'
3102
3105
  },
3103
3106
  '.bw_modal_footer': {
3104
3107
  'display': 'flex',
3105
3108
  'flex-wrap': 'wrap',
3106
3109
  'align-items': 'center',
3107
3110
  'justify-content': 'flex-end',
3108
- 'gap': '0.5rem'
3111
+ 'gap': '0.5rem',
3112
+ 'padding': '0.75rem 1.25rem',
3113
+ 'border-top': '1px solid transparent'
3109
3114
  }
3110
3115
  },
3111
3116
  // ---- Toast ----
@@ -3163,9 +3168,12 @@
3163
3168
  'display': 'flex',
3164
3169
  'align-items': 'center',
3165
3170
  'justify-content': 'space-between',
3166
- 'font-size': '0.875rem'
3171
+ 'padding': '0.5rem 0.75rem',
3172
+ 'font-size': '0.875rem',
3173
+ 'border-bottom': '1px solid transparent'
3167
3174
  },
3168
3175
  '.bw_toast_body': {
3176
+ 'padding': '0.5rem 0.75rem',
3169
3177
  'font-size': '0.9375rem'
3170
3178
  }
3171
3179
  },
@@ -3195,6 +3203,7 @@
3195
3203
  'padding': '0.5rem 0',
3196
3204
  'margin': '0.125rem 0 0',
3197
3205
  'background-clip': 'padding-box',
3206
+ 'border': '1px solid transparent',
3198
3207
  'opacity': '0',
3199
3208
  'visibility': 'hidden',
3200
3209
  'pointer-events': 'none'
@@ -3211,6 +3220,7 @@
3211
3220
  '.bw_dropdown_item': {
3212
3221
  'display': 'block',
3213
3222
  'width': '100%',
3223
+ 'padding': '0.4rem 1rem',
3214
3224
  'clear': 'both',
3215
3225
  'font-weight': '400',
3216
3226
  'text-align': 'inherit',
@@ -3218,7 +3228,8 @@
3218
3228
  'white-space': 'nowrap',
3219
3229
  'background-color': 'transparent',
3220
3230
  'border': '0',
3221
- 'font-size': '0.9375rem'
3231
+ 'font-size': '0.9375rem',
3232
+ 'cursor': 'pointer'
3222
3233
  },
3223
3234
  '.bw_dropdown_item:focus-visible': {
3224
3235
  'outline': '2px solid currentColor',
@@ -4104,6 +4115,67 @@
4104
4115
  rules['.bw_text_center'] = {
4105
4116
  'text-align': 'center'
4106
4117
  };
4118
+ rules['.bw_text_justify'] = {
4119
+ 'text-align': 'justify'
4120
+ };
4121
+
4122
+ // Font weight
4123
+ rules['.bw_fw_bold'] = {
4124
+ 'font-weight': '700'
4125
+ };
4126
+ rules['.bw_fw_semibold'] = {
4127
+ 'font-weight': '600'
4128
+ };
4129
+ rules['.bw_fw_normal'] = {
4130
+ 'font-weight': '400'
4131
+ };
4132
+ rules['.bw_fw_light'] = {
4133
+ 'font-weight': '300'
4134
+ };
4135
+
4136
+ // Font style
4137
+ rules['.bw_fst_italic'] = {
4138
+ 'font-style': 'italic'
4139
+ };
4140
+ rules['.bw_fst_normal'] = {
4141
+ 'font-style': 'normal'
4142
+ };
4143
+
4144
+ // Text decoration
4145
+ rules['.bw_text_underline'] = {
4146
+ 'text-decoration': 'underline'
4147
+ };
4148
+ rules['.bw_text_line_through'] = {
4149
+ 'text-decoration': 'line-through'
4150
+ };
4151
+ rules['.bw_text_decoration_none'] = {
4152
+ 'text-decoration': 'none'
4153
+ };
4154
+
4155
+ // Text transform
4156
+ rules['.bw_text_uppercase'] = {
4157
+ 'text-transform': 'uppercase'
4158
+ };
4159
+ rules['.bw_text_lowercase'] = {
4160
+ 'text-transform': 'lowercase'
4161
+ };
4162
+ rules['.bw_text_capitalize'] = {
4163
+ 'text-transform': 'capitalize'
4164
+ };
4165
+
4166
+ // Font size
4167
+ rules['.bw_fs_sm'] = {
4168
+ 'font-size': '0.875rem'
4169
+ };
4170
+ rules['.bw_fs_base'] = {
4171
+ 'font-size': '1rem'
4172
+ };
4173
+ rules['.bw_fs_lg'] = {
4174
+ 'font-size': '1.25rem'
4175
+ };
4176
+ rules['.bw_fs_xl'] = {
4177
+ 'font-size': '1.5rem'
4178
+ };
4107
4179
 
4108
4180
  // Flexbox
4109
4181
  var jc = {
@@ -5284,12 +5356,11 @@
5284
5356
  // monotonic ID for subscriptions
5285
5357
 
5286
5358
  // ── Node reference cache ──────────────────────────────────────────────
5287
- // Fast O(1) lookup for elements by bw_id, id attribute, or bw_uuid.
5359
+ // Fast O(1) lookup for elements by id attribute or bw_uuid_* class.
5288
5360
  //
5289
5361
  // Populated by bw.createDOM() when elements have:
5290
- // - data-bw_id attribute (user-declared addressable elements)
5291
5362
  // - id attribute (standard HTML id)
5292
- // - bw_uuid (internal, for lifecycle-managed elements)
5363
+ // - bw_uuid_* class (lifecycle-managed or explicitly addressed elements)
5293
5364
  //
5294
5365
  // Cleaned up by bw.cleanup() when elements are destroyed via bitwrench APIs.
5295
5366
  // On cache miss, falls back to querySelector/getElementById — never fails,
@@ -5297,7 +5368,7 @@
5297
5368
  // via parentNode === null check (IE11-safe, unlike el.isConnected).
5298
5369
  //
5299
5370
  // Elements created via bw.createDOM() also get el._bw_refs — a local map of
5300
- // child bw_id DOM node ref for fast parentchild access in o.render.
5371
+ // child id/UUID -> DOM node ref for fast parent->child access in o.render.
5301
5372
  // This is the bitwrench equivalent of React's compiled template "holes".
5302
5373
  //
5303
5374
  // Contract: if you remove elements outside of bitwrench APIs (raw el.remove()),
@@ -5376,7 +5447,6 @@
5376
5447
  // _cw console.warn 8
5377
5448
  // _cl console.log 11
5378
5449
  // _ce console.error 4
5379
- // _chp ComponentHandle.prototype 28 (defined after constructor)
5380
5450
  //
5381
5451
  // Note: document.createElement etc. are NOT aliased because they require
5382
5452
  // `this === document` and .bind() would add overhead on every call.
@@ -5560,15 +5630,15 @@
5560
5630
  * 1. Check `bw._nodeMap[id]` — if found and still attached (parentNode !== null), return it
5561
5631
  * 2. If cached ref is detached (parentNode === null), remove stale entry
5562
5632
  * 3. Fall back to `document.getElementById(id)` then `document.querySelector(...)`
5563
- * 4. If fallback finds the element, cache it for next time
5564
- * 5. If not found anywhere, return null
5633
+ * 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
5634
+ * 5. Cache the result for next time
5565
5635
  *
5566
5636
  * Accepts a DOM element directly (pass-through) or a string identifier.
5567
5637
  * String identifiers are tried as: direct map key, getElementById,
5568
5638
  * querySelector (for CSS selectors starting with . or #), and
5569
- * data-bw_id attribute selector.
5639
+ * bw_uuid_* class selector.
5570
5640
  *
5571
- * @param {string|Element} id - Element ID, CSS selector, data-bw_id value, or DOM element
5641
+ * @param {string|Element} id - Element ID, CSS selector, bw_uuid_* class, or DOM element
5572
5642
  * @returns {Element|null} The DOM element, or null if not found
5573
5643
  * @category Internal
5574
5644
  */
@@ -5597,17 +5667,12 @@
5597
5667
  el = document.querySelector(id);
5598
5668
  }
5599
5669
 
5600
- // 4. Try data-bw_id attribute (for bw.uuid-generated IDs)
5601
- if (!el) {
5602
- el = document.querySelector('[data-bw_id="' + id + '"]');
5603
- }
5604
-
5605
- // 5. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
5670
+ // 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
5606
5671
  if (!el && id.indexOf('bw_uuid_') === 0) {
5607
5672
  el = document.querySelector('.' + id);
5608
5673
  }
5609
5674
 
5610
- // 6. Cache the result for next time
5675
+ // 5. Cache the result for next time
5611
5676
  if (el) {
5612
5677
  bw._nodeMap[id] = el;
5613
5678
  }
@@ -5618,17 +5683,17 @@
5618
5683
  * Register a DOM element in the node cache under one or more keys.
5619
5684
  *
5620
5685
  * Called internally by `bw.createDOM()`. Registers elements that have
5621
- * id attributes, data-bw_id attributes, or both.
5686
+ * id attributes, UUID classes, or both.
5622
5687
  *
5623
5688
  * @param {Element} el - DOM element to register
5624
- * @param {string} [bwId] - data-bw_id value to register under
5689
+ * @param {string} [uuid] - bw_uuid_* class token to register under
5625
5690
  * @category Internal
5626
5691
  */
5627
- bw._registerNode = function (el, bwId) {
5692
+ bw._registerNode = function (el, uuid) {
5628
5693
  if (!el) return;
5629
- // Register under data-bw_id
5630
- if (bwId) {
5631
- bw._nodeMap[bwId] = el;
5694
+ // Register under UUID class token
5695
+ if (uuid) {
5696
+ bw._nodeMap[uuid] = el;
5632
5697
  }
5633
5698
  // Register under id attribute
5634
5699
  var htmlId = el.getAttribute ? el.getAttribute('id') : null;
@@ -5644,13 +5709,13 @@
5644
5709
  * through bitwrench APIs.
5645
5710
  *
5646
5711
  * @param {Element} el - DOM element to deregister
5647
- * @param {string} [bwId] - data-bw_id value to remove
5712
+ * @param {string} [uuid] - bw_uuid_* class token to remove
5648
5713
  * @category Internal
5649
5714
  */
5650
- bw._deregisterNode = function (el, bwId) {
5651
- // Remove data-bw_id entry
5652
- if (bwId) {
5653
- delete bw._nodeMap[bwId];
5715
+ bw._deregisterNode = function (el, uuid) {
5716
+ // Remove UUID class entry
5717
+ if (uuid) {
5718
+ delete bw._nodeMap[uuid];
5654
5719
  }
5655
5720
  // Remove id attribute entry
5656
5721
  var htmlId = el && el.getAttribute ? el.getAttribute('id') : null;
@@ -5663,6 +5728,13 @@
5663
5728
  // bw.assignUUID() / bw.getUUID() — Explicit UUID addressing for TACO objects
5664
5729
  // ===================================================================================
5665
5730
 
5731
+ /**
5732
+ * Marker class for elements with lifecycle hooks (mounted/unmount/render/state).
5733
+ * Used by cleanup() to find lifecycle-managed elements via querySelectorAll('.bw_lc').
5734
+ * @private
5735
+ */
5736
+ var _BW_LC = 'bw_lc';
5737
+
5666
5738
  /**
5667
5739
  * Regex to match a bw_uuid_* token in a class string.
5668
5740
  * @private
@@ -5848,20 +5920,10 @@
5848
5920
  * // => '<div class="card"><p>Content here</p></div>'
5849
5921
  */
5850
5922
  bw.html = function (taco) {
5851
- var _attrs$class;
5852
5923
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
5853
5924
  // Handle null/undefined
5854
5925
  if (taco == null) return '';
5855
5926
 
5856
- // Handle ComponentHandle — use its .taco
5857
- if (taco && taco._bwComponent === true) {
5858
- var compOptions = Object.assign({}, options);
5859
- if (!compOptions.state && taco._state) {
5860
- compOptions.state = taco._state;
5861
- }
5862
- return bw.html(taco.taco, compOptions);
5863
- }
5864
-
5865
5927
  // Handle arrays of TACOs
5866
5928
  if (_isA(taco)) {
5867
5929
  return taco.map(function (t) {
@@ -5874,24 +5936,6 @@
5874
5936
  return taco.v;
5875
5937
  }
5876
5938
 
5877
- // Handle bw.when() markers
5878
- if (taco && taco._bwWhen && options.state) {
5879
- var whenExpr = taco.expr.replace(/^\$\{|\}$/g, '');
5880
- var whenVal = options.compile ? bw._resolveTemplate('${' + whenExpr + '}', options.state, true) : bw._evaluatePath(options.state, whenExpr);
5881
- var branch = whenVal ? taco.branches[0] : taco.branches[1] || null;
5882
- return branch ? bw.html(branch, options) : '';
5883
- }
5884
-
5885
- // Handle bw.each() markers
5886
- if (taco && taco._bwEach && options.state) {
5887
- var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
5888
- var arr = bw._evaluatePath(options.state, eachExpr);
5889
- if (!_isA(arr)) return '';
5890
- return arr.map(function (item, idx) {
5891
- return bw.html(taco.factory(item, idx), options);
5892
- }).join('');
5893
- }
5894
-
5895
5939
  // Handle primitives and non-TACO objects
5896
5940
  if (!_is(taco, 'object') || !taco.t) {
5897
5941
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
@@ -5965,14 +6009,14 @@
5965
6009
  }
5966
6010
  }
5967
6011
 
5968
- // Add bw_id as a class if lifecycle hooks present
5969
- if ((opts.mounted || opts.unmount) && !((_attrs$class = attrs["class"]) !== null && _attrs$class !== void 0 && _attrs$class.includes('bw_id_'))) {
5970
- var id = opts.bw_id || bw.uuid();
6012
+ // Add bw_uuid + bw_lc classes if lifecycle hooks present
6013
+ if ((opts.mounted || opts.unmount) && !_UUID_RE.test(attrs["class"] || '')) {
6014
+ var uuid = bw.uuid('uuid');
5971
6015
  attrStr = attrStr.replace(/class="([^"]*)"/, function (_match, classes) {
5972
- return "class=\"".concat(classes, " bw_id_").concat(id, "\"").trim();
6016
+ return "class=\"".concat(classes, " ").concat(uuid, " ").concat(_BW_LC, "\"").trim();
5973
6017
  });
5974
6018
  if (!attrStr.includes('class=')) {
5975
- attrStr += " class=\"bw_id_".concat(id, "\"");
6019
+ attrStr += " class=\"".concat(uuid, " ").concat(_BW_LC, "\"");
5976
6020
  }
5977
6021
  }
5978
6022
 
@@ -6199,11 +6243,6 @@
6199
6243
  return frag;
6200
6244
  }
6201
6245
 
6202
- // Handle ComponentHandle — extract .taco for DOM creation
6203
- if (taco && taco._bwComponent === true) {
6204
- return bw.createDOM(taco.taco, options);
6205
- }
6206
-
6207
6246
  // Handle text nodes
6208
6247
  if (!_is(taco, 'object') || !taco.t) {
6209
6248
  return document.createTextNode(String(taco));
@@ -6250,24 +6289,19 @@
6250
6289
  }
6251
6290
 
6252
6291
  // Add children, building _bw_refs for fast parent→child access.
6253
- // Children with data-bw_id or id attributes get local refs on the parent,
6292
+ // Children with id attributes or bw_uuid_* classes get local refs on the parent,
6254
6293
  // so o.render functions can access them without any DOM lookup.
6255
6294
  if (content != null) {
6256
6295
  if (_isA(content)) {
6257
6296
  content.forEach(function (child) {
6258
6297
  if (child != null) {
6259
- // Handle ComponentHandle in content arrays (Level 2 children)
6260
- if (child._bwComponent === true) {
6261
- child.mount(el);
6262
- return;
6263
- }
6264
6298
  var childEl = bw.createDOM(child, options);
6265
6299
  el.appendChild(childEl);
6266
6300
  // Build local refs for addressable children
6267
- var childBwId = child && child.a ? child.a['data-bw_id'] || child.a.id : null;
6268
- if (childBwId) {
6301
+ var childRefId = child && child.a ? child.a.id || bw.getUUID(child) : null;
6302
+ if (childRefId) {
6269
6303
  if (!el._bw_refs) el._bw_refs = {};
6270
- el._bw_refs[childBwId] = childEl;
6304
+ el._bw_refs[childRefId] = childEl;
6271
6305
  }
6272
6306
  // Bubble up grandchild refs (flatten one level)
6273
6307
  if (childEl._bw_refs) {
@@ -6283,16 +6317,13 @@
6283
6317
  } else if (_is(content, 'object') && content.__bw_raw) {
6284
6318
  // Raw HTML content — inject via innerHTML
6285
6319
  el.innerHTML = content.v;
6286
- } else if (content._bwComponent === true) {
6287
- // Single ComponentHandle as content
6288
- content.mount(el);
6289
6320
  } else if (_is(content, 'object') && content.t) {
6290
6321
  var childEl = bw.createDOM(content, options);
6291
6322
  el.appendChild(childEl);
6292
- var childBwId = content.a ? content.a['data-bw_id'] || content.a.id : null;
6293
- if (childBwId) {
6323
+ var childRefId = content.a ? content.a.id || bw.getUUID(content) : null;
6324
+ if (childRefId) {
6294
6325
  if (!el._bw_refs) el._bw_refs = {};
6295
- el._bw_refs[childBwId] = childEl;
6326
+ el._bw_refs[childRefId] = childEl;
6296
6327
  }
6297
6328
  if (childEl._bw_refs) {
6298
6329
  if (!el._bw_refs) el._bw_refs = {};
@@ -6322,56 +6353,87 @@
6322
6353
 
6323
6354
  // Handle lifecycle hooks and state
6324
6355
  if (opts.mounted || opts.unmount || opts.render || opts.state) {
6325
- var id = attrs['data-bw_id'] || bw.uuid();
6326
- el.setAttribute('data-bw_id', id);
6356
+ // Ensure element has a UUID class for identity
6357
+ var uuid = bw.getUUID(el) || bw.uuid('uuid');
6358
+ el.classList.add(uuid);
6359
+ el.classList.add(_BW_LC);
6327
6360
 
6328
- // Register in node cache under data-bw_id
6329
- bw._registerNode(el, id);
6361
+ // Register in node cache under UUID class
6362
+ bw._registerNode(el, uuid);
6330
6363
 
6331
6364
  // Store state
6332
6365
  if (opts.state) {
6333
6366
  el._bw_state = opts.state;
6334
6367
  }
6335
6368
 
6336
- // o.render — first-class render function (replaces mounted boilerplate)
6369
+ // o.render — store the render function for bw.update()
6337
6370
  if (opts.render) {
6338
6371
  el._bw_render = opts.render;
6339
- if (opts.mounted) {
6340
- _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
6341
- }
6372
+ }
6342
6373
 
6343
- // Queue initial render (same timing as mounted)
6344
- if (document.body.contains(el)) {
6345
- opts.render(el, el._bw_state || {});
6346
- } else {
6347
- requestAnimationFrame(function () {
6348
- if (document.body.contains(el)) {
6349
- opts.render(el, el._bw_state || {});
6350
- }
6351
- });
6352
- }
6353
- } else if (opts.mounted) {
6354
- // Queue mounted callback (legacy pattern)
6374
+ // Determine what to call on mount:
6375
+ // - If o.mounted exists, call it (it can call el._bw_render() for initial render)
6376
+ // - Otherwise if o.render exists, auto-call it as a convenience shorthand
6377
+ var mountFn = opts.mounted || (opts.render ? function (mountEl) {
6378
+ opts.render(mountEl, mountEl._bw_state || {});
6379
+ } : null);
6380
+ if (mountFn) {
6355
6381
  if (document.body.contains(el)) {
6356
- opts.mounted(el, el._bw_state || {});
6382
+ mountFn(el, el._bw_state || {});
6357
6383
  } else {
6358
6384
  requestAnimationFrame(function () {
6359
6385
  if (document.body.contains(el)) {
6360
- opts.mounted(el, el._bw_state || {});
6386
+ mountFn(el, el._bw_state || {});
6361
6387
  }
6362
6388
  });
6363
6389
  }
6364
6390
  }
6365
6391
 
6366
- // Store unmount callback
6392
+ // Store unmount callback keyed by UUID class
6367
6393
  if (opts.unmount) {
6368
- bw._unmountCallbacks.set(id, function () {
6394
+ bw._unmountCallbacks.set(uuid, function () {
6369
6395
  opts.unmount(el, el._bw_state || {});
6370
6396
  });
6371
6397
  }
6372
- } else if (attrs['data-bw_id']) {
6373
- // Element has explicit data-bw_id but no lifecycle hooks — still register it
6374
- bw._registerNode(el, attrs['data-bw_id']);
6398
+ }
6399
+
6400
+ // Component handle: attach methods to el.bw namespace
6401
+ if (opts.handle || opts.slots) {
6402
+ if (!el.bw) el.bw = {};
6403
+
6404
+ // Explicit handle methods: fn(el, ...args) -> el.bw.method(...args)
6405
+ if (opts.handle) {
6406
+ for (var hk in opts.handle) {
6407
+ if (_hop.call(opts.handle, hk)) {
6408
+ el.bw[hk] = opts.handle[hk].bind(null, el);
6409
+ }
6410
+ }
6411
+ }
6412
+
6413
+ // Slot declarations: auto-generate setX/getX pairs
6414
+ if (opts.slots) {
6415
+ for (var sk in opts.slots) {
6416
+ if (_hop.call(opts.slots, sk)) {
6417
+ (function (name, selector) {
6418
+ var cap = name.charAt(0).toUpperCase() + name.slice(1);
6419
+ el.bw['set' + cap] = function (value) {
6420
+ var t = el.querySelector(selector);
6421
+ if (!t) return;
6422
+ if (value != null && _typeof(value) === 'object' && value.t) {
6423
+ t.innerHTML = '';
6424
+ t.appendChild(bw.createDOM(value));
6425
+ } else {
6426
+ t.textContent = value != null ? String(value) : '';
6427
+ }
6428
+ };
6429
+ el.bw['get' + cap] = function () {
6430
+ var t = el.querySelector(selector);
6431
+ return t ? t.textContent : '';
6432
+ };
6433
+ })(sk, opts.slots[sk]);
6434
+ }
6435
+ }
6436
+ }
6375
6437
  }
6376
6438
  return el;
6377
6439
  };
@@ -6417,7 +6479,7 @@
6417
6479
  // the target is the mount point, not the content being replaced)
6418
6480
  var savedState = targetEl._bw_state;
6419
6481
  var savedRender = targetEl._bw_render;
6420
- var savedBwId = targetEl.getAttribute('data-bw_id');
6482
+ var savedUuid = bw.getUUID(targetEl);
6421
6483
  var savedSubs = targetEl._bw_subs;
6422
6484
 
6423
6485
  // Temporarily remove _bw_subs so cleanup doesn't call them
@@ -6428,35 +6490,20 @@
6428
6490
  // Restore the target's own state/render/subs after cleanup
6429
6491
  if (savedState !== undefined) targetEl._bw_state = savedState;
6430
6492
  if (savedRender) targetEl._bw_render = savedRender;
6431
- if (savedBwId) {
6432
- targetEl.setAttribute('data-bw_id', savedBwId);
6433
- // Re-register mount point in node cache (cleanup deregistered it)
6434
- bw._registerNode(targetEl, savedBwId);
6493
+ if (savedUuid) {
6494
+ // UUID class stays on element through cleanup; re-register in cache
6495
+ bw._registerNode(targetEl, savedUuid);
6435
6496
  }
6436
6497
  if (savedSubs) targetEl._bw_subs = savedSubs;
6437
6498
 
6438
6499
  // Clear and mount new content
6439
6500
  targetEl.innerHTML = '';
6440
6501
  if (taco != null) {
6441
- // Handle ComponentHandle (reactive components from bw.component())
6442
- if (taco._bwComponent === true) {
6443
- taco.mount(targetEl);
6444
- }
6445
- // Handle component handles (objects with element property)
6446
- else if (taco.element instanceof Element) {
6447
- targetEl.appendChild(taco.element);
6448
- }
6449
6502
  // Handle arrays
6450
- else if (_isA(taco)) {
6503
+ if (_isA(taco)) {
6451
6504
  taco.forEach(function (t) {
6452
6505
  if (t != null) {
6453
- if (t._bwComponent === true) {
6454
- t.mount(targetEl);
6455
- } else if (t.element instanceof Element) {
6456
- targetEl.appendChild(t.element);
6457
- } else {
6458
- targetEl.appendChild(bw.createDOM(t, options));
6459
- }
6506
+ targetEl.appendChild(bw.createDOM(t, options));
6460
6507
  }
6461
6508
  });
6462
6509
  }
@@ -6468,197 +6515,40 @@
6468
6515
  return targetEl;
6469
6516
  };
6470
6517
 
6471
- /**
6472
- * Compile props into getter/setter functions for reactive updates.
6473
- *
6474
- * Used internally by `bw.renderComponent()`. Creates a proxy-like object
6475
- * where setting a property triggers `handle.onPropChange()`.
6476
- *
6477
- * @param {Object} handle - Component handle
6478
- * @param {Object} props - Initial props
6479
- * @returns {Object} Compiled props object with getters/setters
6480
- * @category DOM Generation
6481
- */
6482
- bw.compileProps = function (handle) {
6483
- var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
6484
- var compiledProps = {};
6485
- _keys(props).forEach(function (key) {
6486
- // Create getter/setter for each prop
6487
- Object.defineProperty(compiledProps, key, {
6488
- get: function get() {
6489
- return handle._props[key];
6490
- },
6491
- set: function set(value) {
6492
- var oldValue = handle._props[key];
6493
- if (oldValue !== value) {
6494
- handle._props[key] = value;
6495
- // Trigger update if prop changed
6496
- if (handle.onPropChange) {
6497
- handle.onPropChange(key, value, oldValue);
6498
- }
6499
- }
6500
- },
6501
- enumerable: true,
6502
- configurable: true
6503
- });
6504
- });
6505
- return compiledProps;
6518
+ // Deprecation stubs for removed ComponentHandle APIs
6519
+ bw.compileProps = function () {
6520
+ throw new Error('bw.compileProps() removed in v2.0.19. Use o.handle/o.slots instead.');
6521
+ };
6522
+ bw.renderComponent = function () {
6523
+ throw new Error('bw.renderComponent() removed in v2.0.19. Use bw.mount() with o.handle/o.slots instead.');
6506
6524
  };
6507
6525
 
6508
6526
  /**
6509
- * Render a TACO component and return an enhanced handle object.
6510
- *
6511
- * The handle provides compiled props, state management, child registration,
6512
- * and a destroy method. Used internally by `bw.createCard()`, `bw.createTable()`, etc.
6527
+ * Mount a TACO into a target element and return the created root element.
6528
+ * Like bw.DOM() but returns the root element of the TACO (not the container),
6529
+ * giving direct access to el.bw handle methods.
6513
6530
  *
6514
- * @param {Object} taco - TACO object to render
6515
- * @param {Object} [options] - Render options
6516
- * @returns {Object} Component handle with element, props, state, update(), destroy()
6531
+ * @param {string|Element} target - CSS selector or DOM element
6532
+ * @param {Object} taco - TACO to render
6533
+ * @param {Object} [options] - Mount options
6534
+ * @returns {Element} The created root element
6517
6535
  * @category DOM Generation
6518
- */
6519
- bw.renderComponent = function (taco) {
6520
- var _taco$o3;
6521
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
6522
- var element = bw.createDOM(taco, options);
6523
-
6524
- // Enhanced handle with prop compilation
6525
- var handle = {
6526
- element: element,
6527
- taco: taco,
6528
- _props: _objectSpread2({}, taco.a),
6529
- // Store props internally
6530
- _state: ((_taco$o3 = taco.o) === null || _taco$o3 === void 0 ? void 0 : _taco$o3.state) || {},
6531
- _children: {},
6532
- // Store child component references
6533
-
6534
- // Get compiled props with getters/setters
6535
- get props() {
6536
- if (!this._compiledProps) {
6537
- this._compiledProps = bw.compileProps(this, this._props);
6538
- }
6539
- return this._compiledProps;
6540
- },
6541
- /**
6542
- * Query all matching elements within this component
6543
- * @param {string} selector - CSS selector
6544
- * @returns {NodeList} Matching elements
6545
- */
6546
- $: function $(selector) {
6547
- return this.element.querySelectorAll(selector);
6548
- },
6549
- /**
6550
- * Query the first matching element within this component
6551
- * @param {string} selector - CSS selector
6552
- * @returns {Element|null} First matching element or null
6553
- */
6554
- $first: function $first(selector) {
6555
- return this.element.querySelector(selector);
6556
- },
6557
- /**
6558
- * Update component with new props and re-render in place
6559
- * @param {Object} newProps - Properties to merge into current props
6560
- * @returns {Object} this handle (for chaining)
6561
- */
6562
- update: function update(newProps) {
6563
- // Update internal props
6564
- Object.assign(this._props, newProps);
6565
-
6566
- // Rebuild TACO with new props
6567
- var newTaco = _objectSpread2(_objectSpread2({}, this.taco), {}, {
6568
- a: _objectSpread2(_objectSpread2({}, this.taco.a), newProps)
6569
- });
6570
- var newElement = bw.createDOM(newTaco, options);
6571
-
6572
- // Replace in DOM
6573
- this.element.replaceWith(newElement);
6574
- this.element = newElement;
6575
- this.taco = newTaco;
6576
- return this;
6577
- },
6578
- /**
6579
- * Re-render the component from its current TACO, replacing the DOM element
6580
- * @returns {Object} this handle (for chaining)
6581
- */
6582
- render: function render() {
6583
- var newElement = bw.createDOM(this.taco, options);
6584
- this.element.replaceWith(newElement);
6585
- this.element = newElement;
6586
- return this;
6587
- },
6588
- /**
6589
- * Called when a compiled prop value changes. Override to customize behavior.
6590
- * Default implementation triggers a full re-render.
6591
- * @param {string} key - Property name that changed
6592
- * @param {*} newValue - New property value
6593
- * @param {*} oldValue - Previous property value
6594
- */
6595
- onPropChange: function onPropChange(_key, _newValue, _oldValue) {
6596
- // Auto re-render on prop change by default
6597
- this.render();
6598
- },
6599
- // State management
6600
- get state() {
6601
- return this._state;
6602
- },
6603
- set state(newState) {
6604
- this._state = newState;
6605
- this.render();
6606
- },
6607
- /**
6608
- * Merge state updates and re-render the component
6609
- * @param {Object} updates - State properties to merge
6610
- * @returns {Object} this handle (for chaining)
6611
- */
6612
- setState: function setState(updates) {
6613
- Object.assign(this._state, updates);
6614
- this.render();
6615
- return this;
6616
- },
6617
- /**
6618
- * Register a child component under a name for later retrieval
6619
- * @param {string} name - Child name key
6620
- * @param {Object} component - Child component handle
6621
- * @returns {Object} this handle (for chaining)
6622
- */
6623
- addChild: function addChild(name, component) {
6624
- this._children[name] = component;
6625
- return this;
6626
- },
6627
- /**
6628
- * Retrieve a registered child component by name
6629
- * @param {string} name - Child name key
6630
- * @returns {Object|undefined} Child component handle
6631
- */
6632
- getChild: function getChild(name) {
6633
- return this._children[name];
6634
- },
6635
- /**
6636
- * Destroy this component and all registered children
6637
- *
6638
- * Calls destroy() recursively on children, runs bw.cleanup(),
6639
- * removes the element from DOM, and clears all internal references.
6640
- */
6641
- destroy: function destroy() {
6642
- // Destroy children first
6643
- Object.values(this._children).forEach(function (child) {
6644
- if (child && child.destroy) child.destroy();
6645
- });
6646
-
6647
- // Clean up this component
6648
- bw.cleanup(this.element);
6649
- this.element.remove();
6650
-
6651
- // Clear references
6652
- this._children = {};
6653
- this._props = {};
6654
- this._state = {};
6655
- this._compiledProps = null;
6656
- }
6657
- };
6658
-
6659
- // Store handle reference on element
6660
- element._bwHandle = handle;
6661
- return handle;
6536
+ * @example
6537
+ * var el = bw.mount('#app', bw.makeCarousel({ items: slides }));
6538
+ * el.bw.goToSlide(2);
6539
+ * el.bw.next();
6540
+ */
6541
+ bw.mount = function (target, taco, options) {
6542
+ var container = _is(target, 'string') ? bw.$(target)[0] : target;
6543
+ if (!container) {
6544
+ _cw('bw.mount: target not found');
6545
+ return null;
6546
+ }
6547
+ bw.cleanup(container);
6548
+ container.innerHTML = '';
6549
+ var el = bw.createDOM(taco, options || {});
6550
+ container.appendChild(el);
6551
+ return el;
6662
6552
  };
6663
6553
 
6664
6554
  /**
@@ -6679,32 +6569,27 @@
6679
6569
  bw.cleanup = function (element) {
6680
6570
  if (!bw._isBrowser || !element) return;
6681
6571
 
6682
- // Deregister UUID classes from node cache (element + descendants)
6683
- // Covers elements that have UUID but no data-bw_id
6684
- var selfUuidMatch = element.className && element.className.match(_UUID_RE);
6685
- if (selfUuidMatch) delete bw._nodeMap[selfUuidMatch[0]];
6572
+ // Deregister UUID classes from node cache for non-lifecycle UUID elements
6686
6573
  var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
6687
6574
  uuidEls.forEach(function (uel) {
6688
6575
  var m = uel.className && uel.className.match(_UUID_RE);
6689
6576
  if (m) delete bw._nodeMap[m[0]];
6690
6577
  });
6691
6578
 
6692
- // Find all elements with data-bw_id
6693
- var elements = element.querySelectorAll('[data-bw_id]');
6579
+ // Find all lifecycle-managed elements (have bw_lc marker class)
6580
+ var elements = element.querySelectorAll('.' + _BW_LC);
6694
6581
  elements.forEach(function (el) {
6695
- var id = el.getAttribute('data-bw_id');
6696
- var callback = bw._unmountCallbacks.get(id);
6697
- if (callback) {
6698
- callback();
6699
- bw._unmountCallbacks["delete"](id);
6700
- }
6701
-
6702
- // Deregister from node cache
6703
- bw._deregisterNode(el, id);
6582
+ var uuid = bw.getUUID(el);
6583
+ if (uuid) {
6584
+ var callback = bw._unmountCallbacks.get(uuid);
6585
+ if (callback) {
6586
+ callback();
6587
+ bw._unmountCallbacks["delete"](uuid);
6588
+ }
6704
6589
 
6705
- // Deregister UUID class from node cache
6706
- var uuidMatch = el.className && el.className.match(_UUID_RE);
6707
- if (uuidMatch) delete bw._nodeMap[uuidMatch[0]];
6590
+ // Deregister from node cache
6591
+ bw._deregisterNode(el, uuid);
6592
+ }
6708
6593
 
6709
6594
  // Clean up pub/sub subscriptions tied to this element
6710
6595
  if (el._bw_subs) {
@@ -6721,20 +6606,17 @@
6721
6606
  });
6722
6607
 
6723
6608
  // Check element itself
6724
- var id = element.getAttribute('data-bw_id');
6725
- if (id) {
6726
- var callback = bw._unmountCallbacks.get(id);
6609
+ var selfUuid = bw.getUUID(element);
6610
+ if (selfUuid) {
6611
+ delete bw._nodeMap[selfUuid];
6612
+ var callback = bw._unmountCallbacks.get(selfUuid);
6727
6613
  if (callback) {
6728
6614
  callback();
6729
- bw._unmountCallbacks["delete"](id);
6615
+ bw._unmountCallbacks["delete"](selfUuid);
6730
6616
  }
6731
6617
 
6732
6618
  // Deregister from node cache
6733
- bw._deregisterNode(element, id);
6734
-
6735
- // Deregister UUID class from node cache
6736
- var elemUuidMatch = element.className && element.className.match(_UUID_RE);
6737
- if (elemUuidMatch) delete bw._nodeMap[elemUuidMatch[0]];
6619
+ bw._deregisterNode(element, selfUuid);
6738
6620
 
6739
6621
  // Clean up pub/sub subscriptions tied to element itself
6740
6622
  if (element._bw_subs) {
@@ -6746,12 +6628,13 @@
6746
6628
  delete element._bw_state;
6747
6629
  delete element._bw_render;
6748
6630
  delete element._bw_refs;
6749
-
6750
- // Clean up ComponentHandle back-reference
6751
- if (element._bwComponentHandle) {
6752
- element._bwComponentHandle.mounted = false;
6753
- element._bwComponentHandle.element = null;
6754
- delete element._bwComponentHandle;
6631
+ } else {
6632
+ // No UUID on element itself, but still check for _bw_subs (from bw.sub())
6633
+ if (element._bw_subs) {
6634
+ element._bw_subs.forEach(function (unsub) {
6635
+ unsub();
6636
+ });
6637
+ delete element._bw_subs;
6755
6638
  }
6756
6639
  }
6757
6640
  };
@@ -6767,7 +6650,7 @@
6767
6650
  * Calls `el._bw_render(el, state)` and emits `bw:statechange` so other
6768
6651
  * components can react without tight coupling.
6769
6652
  *
6770
- * @param {string|Element} target - Element ID, data-bw_id, CSS selector, or DOM element
6653
+ * @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element
6771
6654
  * @returns {Element|null} The element, or null if not found / no render function
6772
6655
  * @category State Management
6773
6656
  * @see bw.patch
@@ -6792,7 +6675,7 @@
6792
6675
  * Use `bw.patch()` for lightweight value updates (scores, labels, counters)
6793
6676
  * and `bw.update()` for full structural re-renders.
6794
6677
  *
6795
- * @param {string|Element} id - Element ID, data-bw_id, CSS selector, or DOM element.
6678
+ * @param {string|Element} id - Element ID, bw_uuid_* class, CSS selector, or DOM element.
6796
6679
  * Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
6797
6680
  * @param {string|Object} content - New text content, or TACO object to replace children
6798
6681
  * @param {string} [attr] - If provided, sets this attribute instead of content
@@ -6866,7 +6749,7 @@
6866
6749
  * bubble by default so ancestor elements can listen. Use with `bw.on()` for
6867
6750
  * DOM-scoped communication between components.
6868
6751
  *
6869
- * @param {string|Element} target - Element ID, data-bw_id, CSS selector, or DOM element.
6752
+ * @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
6870
6753
  * Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
6871
6754
  * @param {string} eventName - Event name (will be prefixed with 'bw:')
6872
6755
  * @param {*} [detail] - Data to pass with the event
@@ -6893,7 +6776,7 @@
6893
6776
  * is the first argument so you don't need to destructure `e.detail`.
6894
6777
  * Events bubble, so you can listen on an ancestor element.
6895
6778
  *
6896
- * @param {string|Element} target - Element ID, data-bw_id, CSS selector, or DOM element.
6779
+ * @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
6897
6780
  * Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
6898
6781
  * @param {string} eventName - Event name (will be prefixed with 'bw:')
6899
6782
  * @param {Function} handler - Called with (detail, event)
@@ -6995,10 +6878,12 @@
6995
6878
  if (el) {
6996
6879
  if (!el._bw_subs) el._bw_subs = [];
6997
6880
  el._bw_subs.push(unsub);
6998
- // Ensure element has data-bw_id so bw.cleanup() finds it
6999
- if (!el.getAttribute('data-bw_id')) {
7000
- var bwId = 'bw_sub_' + id;
7001
- el.setAttribute('data-bw_id', bwId);
6881
+ // Ensure element has UUID + bw_lc so bw.cleanup() finds it
6882
+ if (!bw.getUUID(el)) {
6883
+ el.classList.add(bw.uuid('uuid'));
6884
+ }
6885
+ if (!el.classList.contains(_BW_LC)) {
6886
+ el.classList.add(_BW_LC);
7002
6887
  }
7003
6888
  }
7004
6889
  return unsub;
@@ -7221,1193 +7106,100 @@
7221
7106
  return result;
7222
7107
  };
7223
7108
 
7224
- /**
7225
- * Extract top-level state keys that an expression depends on.
7226
- * @param {string} expr - Expression string
7227
- * @param {string[]} stateKeys - Declared state keys
7228
- * @returns {string[]} Matching dependency keys
7229
- * @private
7230
- */
7231
- bw._extractDeps = function (expr, stateKeys) {
7232
- var deps = [];
7233
- for (var i = 0; i < stateKeys.length; i++) {
7234
- var key = stateKeys[i];
7235
- // Match word boundary: key must be preceded by start/non-word and followed by non-word/end
7236
- var re = new RegExp('(?:^|[^\\w$.])' + key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:[^\\w$]|$)');
7237
- if (re.test(expr) || expr === key || expr.indexOf(key + '.') === 0) {
7238
- deps.push(key);
7239
- }
7240
- }
7241
- return deps;
7242
- };
7243
-
7244
7109
  // ===================================================================================
7245
- // Microtask Batching
7110
+ // Deprecation stubs for removed ComponentHandle APIs (v2.0.19)
7246
7111
  // ===================================================================================
7247
7112
 
7248
- bw._dirtyComponents = [];
7249
- bw._flushScheduled = false;
7113
+ bw._extractDeps = undefined;
7114
+ bw._dirtyComponents = undefined;
7115
+ bw._flushScheduled = undefined;
7116
+ bw._scheduleFlush = undefined;
7117
+ bw._doFlush = undefined;
7118
+ bw._ComponentHandle = undefined;
7250
7119
 
7251
7120
  /**
7252
- * Schedule a microtask flush for dirty components.
7253
- * @private
7121
+ * No-op flush (ComponentHandle removed in v2.0.19).
7122
+ * Kept as no-op for backward compatibility.
7123
+ * @category Component
7254
7124
  */
7255
- bw._scheduleFlush = function () {
7256
- if (bw._flushScheduled) return;
7257
- bw._flushScheduled = true;
7258
- if (typeof Promise !== 'undefined') {
7259
- Promise.resolve().then(bw._doFlush);
7260
- } else {
7261
- setTimeout(bw._doFlush, 0);
7262
- }
7125
+ bw.flush = function () {};
7126
+ bw.when = function () {
7127
+ throw new Error('bw.when() removed in v2.0.19. Use conditional logic in o.render instead.');
7263
7128
  };
7264
-
7265
- /**
7266
- * Flush all dirty components. Deduplicates by _bwId.
7267
- * @private
7268
- */
7269
- bw._doFlush = function () {
7270
- bw._flushScheduled = false;
7271
- var queue = bw._dirtyComponents.slice();
7272
- bw._dirtyComponents = [];
7273
- // Deduplicate by _bwId
7274
- var seen = {};
7275
- for (var i = 0; i < queue.length; i++) {
7276
- var comp = queue[i];
7277
- if (!seen[comp._bwId]) {
7278
- seen[comp._bwId] = true;
7279
- comp._flush();
7280
- }
7281
- }
7129
+ bw.each = function () {
7130
+ throw new Error('bw.each() removed in v2.0.19. Use array mapping in o.render instead.');
7282
7131
  };
7283
-
7284
- /**
7285
- * Synchronous flush for testing and imperative code.
7286
- * Forces immediate re-render of all dirty components.
7287
- *
7288
- * @category Component
7289
- */
7290
- bw.flush = function () {
7291
- bw._doFlush();
7132
+ bw.component = function () {
7133
+ throw new Error('bw.component() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.');
7292
7134
  };
7293
7135
 
7294
7136
  // ===================================================================================
7295
- // ComponentHandleunified reactive component (Phase 1)
7137
+ // bw.message()SendMessage() for the web
7296
7138
  // ===================================================================================
7297
7139
 
7298
7140
  /**
7299
- * ComponentHandle constructor.
7300
- * Wraps a TACO definition with reactive state, lifecycle hooks,
7301
- * template bindings, and named actions.
7141
+ * Dispatch a message to a component by UUID, CSS class, or selector.
7142
+ * Finds the element, looks up el.bw, and calls the named method.
7143
+ * This is the bitwrench equivalent of Win32 SendMessage(hwnd, msg, wParam, lParam).
7302
7144
  *
7303
- * @param {Object} taco - TACO definition {t, a, c, o}
7304
- * @constructor
7305
- * @private
7306
- */
7307
- function ComponentHandle(taco) {
7308
- this._bwComponent = true; // duck-type marker
7309
- this._bwId = bw.uuid('comp');
7310
- this.taco = taco;
7311
- this.element = null;
7312
- this.mounted = false;
7313
- var o = taco.o || {};
7314
- // Copy initial state
7315
- this._state = {};
7316
- if (o.state) {
7317
- for (var k in o.state) {
7318
- if (_hop.call(o.state, k)) {
7319
- this._state[k] = o.state[k];
7320
- }
7321
- }
7322
- }
7323
- // Copy actions
7324
- this._actions = {};
7325
- if (o.actions) {
7326
- for (var k2 in o.actions) {
7327
- if (_hop.call(o.actions, k2)) {
7328
- this._actions[k2] = o.actions[k2];
7329
- }
7330
- }
7331
- }
7332
- // Promote o.methods to handle API (MFC/Qt pattern: component owns its methods)
7333
- this._methods = {};
7334
- if (o.methods) {
7335
- var self = this;
7336
- for (var k3 in o.methods) {
7337
- if (_hop.call(o.methods, k3)) {
7338
- this._methods[k3] = o.methods[k3];
7339
- (function (methodName, methodFn) {
7340
- self[methodName] = function () {
7341
- var args = [self].concat(Array.prototype.slice.call(arguments));
7342
- return methodFn.apply(null, args);
7343
- };
7344
- })(k3, o.methods[k3]);
7345
- }
7346
- }
7347
- }
7348
- // User tag for addressing via bw.message()
7349
- this._userTag = null;
7350
- // Lifecycle hooks
7351
- this._hooks = {
7352
- willMount: o.willMount || null,
7353
- mounted: o.mounted || null,
7354
- willUpdate: o.willUpdate || null,
7355
- onUpdate: o.onUpdate || o.updated || null,
7356
- unmount: o.unmount || null,
7357
- willDestroy: o.willDestroy || null
7358
- };
7359
- // Binding tracking
7360
- this._bindings = [];
7361
- this._dirtyKeys = {};
7362
- this._scheduled = false;
7363
- this._subs = [];
7364
- this._eventListeners = [];
7365
- this._registeredActions = [];
7366
- this._prevValues = {};
7367
- this._compile = !!o.compile;
7368
- this._bw_refs = {};
7369
- this._refCounter = 0;
7370
- // Child component ownership (Bug #5)
7371
- this._children = [];
7372
- this._parent = null;
7373
- // Factory metadata for BCCL rebuild (Bug #6)
7374
- this._factory = taco._bwFactory || null;
7375
- }
7376
-
7377
- // Short alias for ComponentHandle.prototype (see alias block at top of file).
7378
- // 28 method definitions × 25 chars = ~700B raw savings in minified output.
7379
- var _chp = ComponentHandle.prototype;
7380
-
7381
- // ── State Methods ──
7382
-
7383
- /**
7384
- * Get a state value. Dot-path supported: `get('user.name')`
7385
- */
7386
- _chp.get = function (key) {
7387
- return bw._evaluatePath(this._state, key);
7388
- };
7389
-
7390
- /**
7391
- * Set a state value. Dot-path supported. Schedules re-render.
7392
- * @param {string} key - State key (dot-path)
7393
- * @param {*} value - New value
7394
- * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
7395
- */
7396
- _chp.set = function (key, value, opts) {
7397
- // Dot-path set
7398
- var parts = key.split('.');
7399
- var obj = this._state;
7400
- for (var i = 0; i < parts.length - 1; i++) {
7401
- if (!_is(obj[parts[i]], 'object')) {
7402
- if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
7403
- obj[parts[i]] = {};
7404
- }
7405
- obj = obj[parts[i]];
7406
- }
7407
- obj[parts[parts.length - 1]] = value;
7408
- // Mark top-level key dirty
7409
- this._dirtyKeys[parts[0]] = true;
7410
- if (this.mounted) {
7411
- if (opts && opts.sync) {
7412
- this._flush();
7413
- } else {
7414
- this._scheduleDirty();
7415
- }
7416
- }
7417
- };
7418
-
7419
- /**
7420
- * Get a shallow clone of the full state.
7145
+ * @param {string} target - Component UUID (bw_uuid_*), CSS class, or selector
7146
+ * @param {string} action - Method name to call on el.bw
7147
+ * @param {*} data - Data to pass to the method
7148
+ * @returns {boolean} True if message was dispatched successfully
7149
+ * @category Component
7150
+ * @example
7151
+ * bw.message('my_carousel', 'goToSlide', 2);
7152
+ * // Or from SSE handler:
7153
+ * es.onmessage = function(e) {
7154
+ * var msg = JSON.parse(e.data);
7155
+ * bw.message(msg.target, msg.action, msg.data);
7156
+ * };
7421
7157
  */
7422
- _chp.getState = function () {
7423
- var clone = {};
7424
- for (var k in this._state) {
7425
- if (_hop.call(this._state, k)) {
7426
- clone[k] = this._state[k];
7427
- }
7158
+ bw.message = function (target, action, data) {
7159
+ var el = bw._el(target);
7160
+ if (!el) el = bw.$('.' + target)[0];
7161
+ if (!el || !el.bw || typeof el.bw[action] !== 'function') {
7162
+ _cw('bw.message: no handle method "' + action + '" on ' + target);
7163
+ return false;
7428
7164
  }
7429
- return clone;
7165
+ el.bw[action](data);
7166
+ return true;
7430
7167
  };
7431
7168
 
7432
- /**
7433
- * Merge multiple state keys. Schedules re-render.
7434
- * @param {Object} updates - Key-value pairs to merge
7435
- * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
7436
- */
7437
- _chp.setState = function (updates, opts) {
7438
- for (var k in updates) {
7439
- if (_hop.call(updates, k)) {
7440
- this._state[k] = updates[k];
7441
- this._dirtyKeys[k] = true;
7442
- }
7443
- }
7444
- if (this.mounted) {
7445
- if (opts && opts.sync) {
7446
- this._flush();
7447
- } else {
7448
- this._scheduleDirty();
7449
- }
7450
- }
7451
- };
7169
+ // ===================================================================================
7170
+ // bw.apply() / bw.parseJSONFlex() Server-driven UI protocol
7171
+ // ===================================================================================
7452
7172
 
7453
7173
  /**
7454
- * Push a value onto an array in state. Clones the array.
7174
+ * Registry of named functions sent via register messages.
7175
+ * Populated by bw.apply({ type: 'register', name, body }).
7176
+ * Invoked by bw.apply({ type: 'call', name, args }).
7177
+ * @private
7455
7178
  */
7456
- _chp.push = function (key, val) {
7457
- var arr = this.get(key);
7458
- var newArr = _isA(arr) ? arr.slice() : [];
7459
- newArr.push(val);
7460
- this.set(key, newArr);
7461
- };
7179
+ bw._clientFunctions = {};
7462
7180
 
7463
7181
  /**
7464
- * Splice an array in state. Clones the array.
7182
+ * Whether exec messages are allowed. Set by bwclient connect opts.allowExec.
7183
+ * Default false — exec messages are rejected unless explicitly opted in.
7184
+ * @private
7465
7185
  */
7466
- _chp.splice = function (key, start, deleteCount) {
7467
- var arr = this.get(key);
7468
- var newArr = _isA(arr) ? arr.slice() : [];
7469
- var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
7470
- Array.prototype.splice.apply(newArr, args);
7471
- this.set(key, newArr);
7472
- };
7473
-
7474
- // ── Scheduling ──
7475
-
7476
- _chp._scheduleDirty = function () {
7477
- if (!this._scheduled) {
7478
- this._scheduled = true;
7479
- bw._dirtyComponents.push(this);
7480
- bw._scheduleFlush();
7481
- }
7482
- };
7483
-
7484
- // ── Binding Compilation ──
7186
+ bw._allowExec = false;
7485
7187
 
7486
7188
  /**
7487
- * Walk the TACO tree and extract ${expr} bindings.
7488
- * Creates binding descriptors with refIds for targeted DOM updates.
7489
- * @private
7490
- */
7491
- _chp._compileBindings = function () {
7492
- this._bindings = [];
7493
- this._refCounter = 0;
7494
- var stateKeys = _keys(this._state);
7495
- var self = this;
7496
- function walkTaco(taco, path) {
7497
- if (!_is(taco, 'object') || !taco.t) return taco;
7498
-
7499
- // Check content for bindings
7500
- if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
7501
- var refId = 'bw_ref_' + self._refCounter++;
7502
- var parsed = bw._parseBindings(taco.c);
7503
- var deps = [];
7504
- for (var j = 0; j < parsed.length; j++) {
7505
- deps = deps.concat(bw._extractDeps(parsed[j].expr, stateKeys));
7506
- }
7507
- self._bindings.push({
7508
- expr: taco.c,
7509
- type: 'content',
7510
- refId: refId,
7511
- deps: deps,
7512
- template: taco.c
7513
- });
7514
- // Inject data-bw_ref on the TACO for createDOM to pick up
7515
- if (!taco.a) taco.a = {};
7516
- taco.a['data-bw_ref'] = refId;
7517
- }
7518
-
7519
- // Check attributes for bindings
7520
- if (taco.a) {
7521
- for (var attrName in taco.a) {
7522
- if (!_hop.call(taco.a, attrName)) continue;
7523
- if (attrName === 'data-bw_ref') continue;
7524
- var attrVal = taco.a[attrName];
7525
- if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
7526
- var refId2 = 'bw_ref_' + self._refCounter++;
7527
- var parsed2 = bw._parseBindings(attrVal);
7528
- var deps2 = [];
7529
- for (var j2 = 0; j2 < parsed2.length; j2++) {
7530
- deps2 = deps2.concat(bw._extractDeps(parsed2[j2].expr, stateKeys));
7531
- }
7532
- self._bindings.push({
7533
- expr: attrVal,
7534
- type: 'attribute',
7535
- attrName: attrName,
7536
- refId: refId2,
7537
- deps: deps2,
7538
- template: attrVal
7539
- });
7540
- if (!taco.a) taco.a = {};
7541
- taco.a['data-bw_ref'] = taco.a['data-bw_ref'] || refId2;
7542
- // If multiple attribute bindings on same element, store additional marker
7543
- if (taco.a['data-bw_ref'] !== refId2) {
7544
- taco.a['data-bw_ref_' + attrName] = refId2;
7545
- }
7546
- }
7547
- }
7548
- }
7549
-
7550
- // Recurse into children
7551
- if (_isA(taco.c)) {
7552
- for (var i = 0; i < taco.c.length; i++) {
7553
- // Wrap string children with ${expr} in a span so patches target the span, not the parent
7554
- if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
7555
- var mixedRefId = 'bw_ref_' + self._refCounter++;
7556
- var mixedParsed = bw._parseBindings(taco.c[i]);
7557
- var mixedDeps = [];
7558
- for (var mi = 0; mi < mixedParsed.length; mi++) {
7559
- mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
7560
- }
7561
- self._bindings.push({
7562
- expr: taco.c[i],
7563
- type: 'content',
7564
- refId: mixedRefId,
7565
- deps: mixedDeps,
7566
- template: taco.c[i]
7567
- });
7568
- // Replace string with a span wrapper so textContent targets the span only
7569
- taco.c[i] = {
7570
- t: 'span',
7571
- a: {
7572
- 'data-bw_ref': mixedRefId,
7573
- style: 'display:contents'
7574
- },
7575
- c: taco.c[i]
7576
- };
7577
- }
7578
- if (_is(taco.c[i], 'object') && taco.c[i].t) {
7579
- walkTaco(taco.c[i], path.concat(i));
7580
- }
7581
- // Handle bw.when/bw.each markers
7582
- if (taco.c[i] && taco.c[i]._bwWhen) {
7583
- var whenRefId = 'bw_ref_' + self._refCounter++;
7584
- var whenDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
7585
- self._bindings.push({
7586
- expr: taco.c[i].expr,
7587
- type: 'structural',
7588
- subtype: 'when',
7589
- refId: whenRefId,
7590
- deps: whenDeps,
7591
- branches: taco.c[i].branches,
7592
- index: i,
7593
- parentPath: path
7594
- });
7595
- taco.c[i]._refId = whenRefId;
7596
- }
7597
- if (taco.c[i] && taco.c[i]._bwEach) {
7598
- var eachRefId = 'bw_ref_' + self._refCounter++;
7599
- var eachDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
7600
- self._bindings.push({
7601
- expr: taco.c[i].expr,
7602
- type: 'structural',
7603
- subtype: 'each',
7604
- refId: eachRefId,
7605
- deps: eachDeps,
7606
- factory: taco.c[i].factory,
7607
- index: i,
7608
- parentPath: path
7609
- });
7610
- taco.c[i]._refId = eachRefId;
7611
- }
7612
- }
7613
- } else if (_is(taco.c, 'object') && taco.c.t) {
7614
- walkTaco(taco.c, path.concat(0));
7615
- }
7616
- return taco;
7617
- }
7618
- walkTaco(this.taco, []);
7619
- };
7620
-
7621
- // ── DOM Reference Collection ──
7622
-
7623
- /**
7624
- * Build ref map from the live DOM after createDOM.
7625
- * @private
7626
- */
7627
- _chp._collectRefs = function () {
7628
- this._bw_refs = {};
7629
- if (!this.element) return;
7630
- var els = this.element.querySelectorAll('[data-bw_ref]');
7631
- for (var i = 0; i < els.length; i++) {
7632
- this._bw_refs[els[i].getAttribute('data-bw_ref')] = els[i];
7633
- }
7634
- // Also check root element
7635
- var rootRef = this.element.getAttribute && this.element.getAttribute('data-bw_ref');
7636
- if (rootRef) {
7637
- this._bw_refs[rootRef] = this.element;
7638
- }
7639
- };
7640
-
7641
- // ── Lifecycle ──
7642
-
7643
- /**
7644
- * Mount the component into a parent DOM element.
7645
- * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
7646
- * @param {Element} parentEl - DOM element to mount into
7647
- */
7648
- _chp.mount = function (parentEl) {
7649
- // willMount hook
7650
- if (this._hooks.willMount) this._hooks.willMount(this);
7651
-
7652
- // Save original TACO for re-renders (structural changes clone from this)
7653
- if (!this._originalTaco) {
7654
- this._originalTaco = this.taco;
7655
- }
7656
-
7657
- // Deep-clone TACO so binding annotations don't mutate original.
7658
- // Custom clone to preserve _bwWhen/_bwEach markers and their factory functions.
7659
- this.taco = this._deepCloneTaco(this._originalTaco);
7660
-
7661
- // Compile bindings (annotates TACO with data-bw_ref attributes)
7662
- this._compileBindings();
7663
-
7664
- // Prepare TACO: resolve initial binding values, evaluate when/each
7665
- this._prepareTaco(this.taco);
7666
-
7667
- // Register named actions in function registry
7668
- var self = this;
7669
- for (var actionName in this._actions) {
7670
- if (_hop.call(this._actions, actionName)) {
7671
- var registeredName = this._bwId + '_' + actionName;
7672
- (function (aName) {
7673
- bw.funcRegister(function (evt) {
7674
- self._actions[aName](self, evt);
7675
- }, registeredName);
7676
- })(actionName);
7677
- this._registeredActions.push(registeredName);
7678
- }
7679
- }
7680
-
7681
- // Wire action names in onclick etc. to dispatch strings
7682
- this._wireActions(this.taco);
7683
-
7684
- // Create DOM (strip o before createDOM to prevent double lifecycle)
7685
- var tacoForDOM = this._tacoForDOM(this.taco);
7686
- this.element = bw.createDOM(tacoForDOM);
7687
- this.element._bwComponentHandle = this;
7688
- this.element.setAttribute('data-bw_comp_id', this._bwId);
7689
-
7690
- // Restore o.render from original TACO (stripped by _tacoForDOM)
7691
- if (this.taco.o && this.taco.o.render) {
7692
- this.element._bw_render = this.taco.o.render;
7693
- }
7694
- if (this._userTag) {
7695
- this.element.classList.add(this._userTag);
7696
- }
7697
-
7698
- // Append to parent
7699
- parentEl.appendChild(this.element);
7700
-
7701
- // Collect refs from live DOM
7702
- this._collectRefs();
7703
-
7704
- // Resolve initial bindings and apply to DOM
7705
- this._resolveAndApplyAll();
7706
- this.mounted = true;
7707
-
7708
- // Scan for child ComponentHandles and link parent/child (Bug #5)
7709
- var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
7710
- for (var ci = 0; ci < childEls.length; ci++) {
7711
- var ch = childEls[ci]._bwComponentHandle;
7712
- if (ch && ch !== this && !ch._parent) {
7713
- ch._parent = this;
7714
- this._children.push(ch);
7715
- }
7716
- }
7717
-
7718
- // mounted hook (backward compat: fn.length === 2 wraps (el, state))
7719
- if (this._hooks.mounted) {
7720
- if (this._hooks.mounted.length === 2) {
7721
- this._hooks.mounted(this.element, this.getState());
7722
- } else {
7723
- this._hooks.mounted(this);
7724
- }
7725
- }
7726
-
7727
- // Invoke o.render on initial mount (if present)
7728
- if (this.element._bw_render) {
7729
- this.element._bw_render(this.element, this._state);
7730
- }
7731
- };
7732
-
7733
- /**
7734
- * Prepare TACO for initial render: resolve when/each markers.
7735
- * @private
7736
- */
7737
- _chp._prepareTaco = function (taco) {
7738
- if (!_is(taco, 'object')) return;
7739
- if (_isA(taco.c)) {
7740
- for (var i = taco.c.length - 1; i >= 0; i--) {
7741
- var child = taco.c[i];
7742
- if (child && child._bwWhen) {
7743
- var exprStr = child.expr.replace(/^\$\{|\}$/g, '');
7744
- var val;
7745
- if (this._compile) {
7746
- try {
7747
- val = new Function('state', 'with(state){return (' + exprStr + ');}')(this._state);
7748
- } catch (e) {
7749
- val = false;
7750
- }
7751
- } else {
7752
- val = bw._evaluatePath(this._state, exprStr);
7753
- }
7754
- var branch = val ? child.branches[0] : child.branches[1] || null;
7755
- if (branch) {
7756
- // Wrap in a container so we can track it
7757
- taco.c[i] = {
7758
- t: 'span',
7759
- a: {
7760
- 'data-bw_when': child._refId,
7761
- style: 'display:contents'
7762
- },
7763
- c: branch
7764
- };
7765
- } else {
7766
- taco.c[i] = {
7767
- t: 'span',
7768
- a: {
7769
- 'data-bw_when': child._refId,
7770
- style: 'display:contents'
7771
- },
7772
- c: ''
7773
- };
7774
- }
7775
- }
7776
- if (child && child._bwEach) {
7777
- var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
7778
- var arr = bw._evaluatePath(this._state, eachExprStr);
7779
- var items = [];
7780
- if (_isA(arr)) {
7781
- for (var j = 0; j < arr.length; j++) {
7782
- items.push(child.factory(arr[j], j));
7783
- }
7784
- }
7785
- taco.c[i] = {
7786
- t: 'span',
7787
- a: {
7788
- 'data-bw_each': child._refId,
7789
- style: 'display:contents'
7790
- },
7791
- c: items
7792
- };
7793
- }
7794
- if (_is(taco.c[i], 'object') && taco.c[i].t) {
7795
- this._prepareTaco(taco.c[i]);
7796
- }
7797
- }
7798
- } else if (_is(taco.c, 'object') && taco.c.t) {
7799
- this._prepareTaco(taco.c);
7800
- }
7801
- };
7802
-
7803
- /**
7804
- * Wire action name strings (in onclick etc.) to dispatch function calls.
7805
- * @private
7806
- */
7807
- _chp._wireActions = function (taco) {
7808
- if (!_is(taco, 'object') || !taco.t) return;
7809
- if (taco.a) {
7810
- for (var key in taco.a) {
7811
- if (!_hop.call(taco.a, key)) continue;
7812
- if (key.startsWith('on') && _is(taco.a[key], 'string')) {
7813
- var actionName = taco.a[key];
7814
- if (actionName in this._actions) {
7815
- var registeredName = this._bwId + '_' + actionName;
7816
- // Replace string with actual function for createDOM event binding
7817
- (function (rName) {
7818
- taco.a[key] = function (evt) {
7819
- bw.funcGetById(rName)(evt);
7820
- };
7821
- })(registeredName);
7822
- }
7823
- }
7824
- }
7825
- }
7826
- if (_isA(taco.c)) {
7827
- for (var i = 0; i < taco.c.length; i++) {
7828
- this._wireActions(taco.c[i]);
7829
- }
7830
- } else if (_is(taco.c, 'object') && taco.c.t) {
7831
- this._wireActions(taco.c);
7832
- }
7833
- };
7834
-
7835
- /**
7836
- * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
7837
- * @private
7838
- */
7839
- _chp._deepCloneTaco = function (taco) {
7840
- if (taco == null) return taco;
7841
- // Preserve _bwWhen / _bwEach markers (contain functions)
7842
- if (taco._bwWhen) {
7843
- return {
7844
- _bwWhen: true,
7845
- expr: taco.expr,
7846
- branches: [this._deepCloneTaco(taco.branches[0]), taco.branches[1] ? this._deepCloneTaco(taco.branches[1]) : null],
7847
- _refId: taco._refId
7848
- };
7849
- }
7850
- if (taco._bwEach) {
7851
- return {
7852
- _bwEach: true,
7853
- expr: taco.expr,
7854
- factory: taco.factory,
7855
- _refId: taco._refId
7856
- };
7857
- }
7858
- if (!_is(taco, 'object') || !taco.t) return taco;
7859
- var result = {
7860
- t: taco.t
7861
- };
7862
- if (taco.a) {
7863
- result.a = {};
7864
- for (var k in taco.a) {
7865
- if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
7866
- }
7867
- }
7868
- if (taco.c != null) {
7869
- if (_isA(taco.c)) {
7870
- result.c = taco.c.map(function (child) {
7871
- return this._deepCloneTaco(child);
7872
- }.bind(this));
7873
- } else if (_is(taco.c, 'object')) {
7874
- result.c = this._deepCloneTaco(taco.c);
7875
- } else {
7876
- result.c = taco.c;
7877
- }
7878
- }
7879
- if (taco.o) result.o = taco.o; // Keep o reference (not deep-cloned; hooks are functions)
7880
- return result;
7881
- };
7882
-
7883
- /**
7884
- * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
7885
- * @private
7886
- */
7887
- _chp._tacoForDOM = function (taco) {
7888
- if (!_is(taco, 'object') || !taco.t) return taco;
7889
- var result = {
7890
- t: taco.t
7891
- };
7892
- if (taco.a) result.a = taco.a;
7893
- if (taco.c != null) {
7894
- if (_isA(taco.c)) {
7895
- result.c = taco.c.map(function (child) {
7896
- return this._tacoForDOM(child);
7897
- }.bind(this));
7898
- } else if (_is(taco.c, 'object') && taco.c.t) {
7899
- result.c = this._tacoForDOM(taco.c);
7900
- } else {
7901
- result.c = taco.c;
7902
- }
7903
- }
7904
- // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
7905
- if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
7906
- _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t + '>. Use onclick attribute or bw.component() for child interactivity.');
7907
- }
7908
- return result;
7909
- };
7910
-
7911
- /**
7912
- * Unmount: remove from DOM, deactivate, preserve state for re-mount.
7913
- */
7914
- _chp.unmount = function () {
7915
- if (!this.mounted) return;
7916
-
7917
- // unmount hook
7918
- if (this._hooks.unmount) {
7919
- this._hooks.unmount(this);
7920
- }
7921
-
7922
- // Remove DOM event listeners
7923
- for (var i = 0; i < this._eventListeners.length; i++) {
7924
- var l = this._eventListeners[i];
7925
- if (this.element) {
7926
- this.element.removeEventListener(l.event, l.handler);
7927
- }
7928
- }
7929
- this._eventListeners = [];
7930
-
7931
- // Unsubscribe pub/sub
7932
- for (var j = 0; j < this._subs.length; j++) {
7933
- this._subs[j]();
7934
- }
7935
- this._subs = [];
7936
-
7937
- // Remove from DOM
7938
- if (this.element && this.element.parentNode) {
7939
- this.element.parentNode.removeChild(this.element);
7940
- }
7941
- this.mounted = false;
7942
- // State preserved — can re-mount
7943
- };
7944
-
7945
- /**
7946
- * Destroy: unmount + clear state + unregister actions.
7947
- */
7948
- _chp.destroy = function () {
7949
- // willDestroy hook
7950
- if (this._hooks.willDestroy) {
7951
- this._hooks.willDestroy(this);
7952
- }
7953
-
7954
- // Cascade destroy to children depth-first (Bug #5)
7955
- for (var ci = this._children.length - 1; ci >= 0; ci--) {
7956
- this._children[ci].destroy();
7957
- }
7958
- this._children = [];
7959
- if (this._parent) {
7960
- var idx = this._parent._children.indexOf(this);
7961
- if (idx >= 0) this._parent._children.splice(idx, 1);
7962
- this._parent = null;
7963
- }
7964
- this.unmount();
7965
-
7966
- // Unregister actions from function registry
7967
- for (var i = 0; i < this._registeredActions.length; i++) {
7968
- bw.funcUnregister(this._registeredActions[i]);
7969
- }
7970
- this._registeredActions = [];
7971
-
7972
- // Clear state
7973
- this._state = {};
7974
- this._bindings = [];
7975
- this._bw_refs = {};
7976
- this._prevValues = {};
7977
- this._dirtyKeys = {};
7978
- if (this.element) {
7979
- delete this.element._bwComponentHandle;
7980
- this.element = null;
7981
- }
7982
- };
7983
-
7984
- // ── Flush & Binding Resolution ──
7985
-
7986
- /**
7987
- * Flush dirty state: resolve changed bindings and apply to DOM.
7988
- * @private
7989
- */
7990
- _chp._flush = function () {
7991
- this._scheduled = false;
7992
- var changedKeys = _keys(this._dirtyKeys);
7993
- this._dirtyKeys = {};
7994
- if (changedKeys.length === 0 || !this.mounted) return;
7995
-
7996
- // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
7997
- // rebuild the TACO from the factory with merged state (Bug #6)
7998
- if (this._factory) {
7999
- var rebuildNeeded = false;
8000
- for (var fi = 0; fi < changedKeys.length; fi++) {
8001
- if (_hop.call(this._factory.props, changedKeys[fi])) {
8002
- rebuildNeeded = true;
8003
- break;
8004
- }
8005
- }
8006
- if (rebuildNeeded) {
8007
- var merged = {};
8008
- for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
8009
- for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
8010
- this._factory.props = merged;
8011
- var newTaco = bw.make(this._factory.type, merged);
8012
- newTaco._bwFactory = this._factory;
8013
- this.taco = newTaco;
8014
- this._originalTaco = this._deepCloneTaco(newTaco);
8015
- this._render();
8016
- if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
8017
- return;
8018
- }
8019
- }
8020
-
8021
- // willUpdate hook
8022
- if (this._hooks.willUpdate) {
8023
- this._hooks.willUpdate(this, changedKeys);
8024
- }
8025
-
8026
- // Check if any structural bindings are affected
8027
- var needsFullRender = false;
8028
- for (var i = 0; i < this._bindings.length; i++) {
8029
- var b = this._bindings[i];
8030
- if (b.type === 'structural') {
8031
- for (var j = 0; j < b.deps.length; j++) {
8032
- if (changedKeys.indexOf(b.deps[j]) >= 0) {
8033
- needsFullRender = true;
8034
- break;
8035
- }
8036
- }
8037
- if (needsFullRender) break;
8038
- }
8039
- }
8040
- if (needsFullRender) {
8041
- this._render();
8042
- } else {
8043
- var patches = this._resolveBindings(changedKeys);
8044
- this._applyPatches(patches);
8045
- }
8046
-
8047
- // onUpdate hook
8048
- if (this._hooks.onUpdate) {
8049
- this._hooks.onUpdate(this, changedKeys);
8050
- }
8051
- };
8052
-
8053
- /**
8054
- * Resolve bindings whose deps intersect with changedKeys.
8055
- * Returns list of patches to apply.
8056
- * @private
8057
- */
8058
- _chp._resolveBindings = function (changedKeys) {
8059
- var patches = [];
8060
- for (var i = 0; i < this._bindings.length; i++) {
8061
- var b = this._bindings[i];
8062
- if (b.type === 'structural') continue;
8063
-
8064
- // Check if any dep matches
8065
- var affected = false;
8066
- for (var j = 0; j < b.deps.length; j++) {
8067
- if (changedKeys.indexOf(b.deps[j]) >= 0) {
8068
- affected = true;
8069
- break;
8070
- }
8071
- }
8072
- if (!affected) continue;
8073
-
8074
- // Evaluate
8075
- var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
8076
- var prevKey = b.refId + '_' + (b.attrName || 'content');
8077
- if (this._prevValues[prevKey] !== newVal) {
8078
- this._prevValues[prevKey] = newVal;
8079
- patches.push({
8080
- refId: b.refId,
8081
- type: b.type,
8082
- attrName: b.attrName,
8083
- value: newVal
8084
- });
8085
- }
8086
- }
8087
- return patches;
8088
- };
8089
-
8090
- /**
8091
- * Apply patches to DOM.
8092
- * @private
8093
- */
8094
- _chp._applyPatches = function (patches) {
8095
- for (var i = 0; i < patches.length; i++) {
8096
- var p = patches[i];
8097
- var el = this._bw_refs[p.refId];
8098
- if (!el) {
8099
- if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
8100
- continue;
8101
- }
8102
- if (p.type === 'content') {
8103
- el.textContent = p.value;
8104
- } else if (p.type === 'attribute') {
8105
- if (p.attrName === 'class') {
8106
- el.className = p.value;
8107
- } else {
8108
- el.setAttribute(p.attrName, p.value);
8109
- }
8110
- }
8111
- }
8112
- };
8113
-
8114
- /**
8115
- * Resolve all bindings and apply (used for initial render).
8116
- * @private
8117
- */
8118
- _chp._resolveAndApplyAll = function () {
8119
- var patches = [];
8120
- for (var i = 0; i < this._bindings.length; i++) {
8121
- var b = this._bindings[i];
8122
- if (b.type === 'structural') continue;
8123
- var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
8124
- var prevKey = b.refId + '_' + (b.attrName || 'content');
8125
- this._prevValues[prevKey] = newVal;
8126
- patches.push({
8127
- refId: b.refId,
8128
- type: b.type,
8129
- attrName: b.attrName,
8130
- value: newVal
8131
- });
8132
- }
8133
- this._applyPatches(patches);
8134
- };
8135
-
8136
- /**
8137
- * Full re-render for structural changes (when/each branch switches).
8138
- * @private
8139
- */
8140
- _chp._render = function () {
8141
- if (!this.element || !this.element.parentNode) return;
8142
- var parent = this.element.parentNode;
8143
- var nextSibling = this.element.nextSibling;
8144
-
8145
- // Remove old DOM
8146
- parent.removeChild(this.element);
8147
-
8148
- // Re-prepare TACO with current state (deep clone preserving functions)
8149
- this.taco = this._deepCloneTaco(this._originalTaco || this.taco);
8150
-
8151
- // Re-compile bindings and prepare
8152
- this._compileBindings();
8153
- this._prepareTaco(this.taco);
8154
- this._wireActions(this.taco);
8155
- var tacoForDOM = this._tacoForDOM(this.taco);
8156
- this.element = bw.createDOM(tacoForDOM);
8157
- this.element._bwComponentHandle = this;
8158
- this.element.setAttribute('data-bw_comp_id', this._bwId);
8159
-
8160
- // Re-insert at same position
8161
- if (nextSibling) {
8162
- parent.insertBefore(this.element, nextSibling);
8163
- } else {
8164
- parent.appendChild(this.element);
8165
- }
8166
-
8167
- // Re-collect refs and apply all bindings
8168
- this._collectRefs();
8169
- this._resolveAndApplyAll();
8170
- };
8171
-
8172
- // ── Event & Pub/Sub Methods ──
8173
-
8174
- /**
8175
- * Add a DOM event listener on the component's root element.
8176
- * @param {string} event - Event name (e.g., 'click')
8177
- * @param {Function} handler - Event handler
8178
- */
8179
- _chp.on = function (event, handler) {
8180
- if (this.element) {
8181
- this.element.addEventListener(event, handler);
8182
- }
8183
- this._eventListeners.push({
8184
- event: event,
8185
- handler: handler
8186
- });
8187
- };
8188
-
8189
- /**
8190
- * Remove a DOM event listener.
8191
- * @param {string} event - Event name
8192
- * @param {Function} handler - Handler to remove
8193
- */
8194
- _chp.off = function (event, handler) {
8195
- if (this.element) {
8196
- this.element.removeEventListener(event, handler);
8197
- }
8198
- this._eventListeners = this._eventListeners.filter(function (l) {
8199
- return !(l.event === event && l.handler === handler);
8200
- });
8201
- };
8202
-
8203
- /**
8204
- * Subscribe to a pub/sub topic. Lifecycle-tied: auto-unsubs on destroy.
8205
- * @param {string} topic - Topic name
8206
- * @param {Function} handler - Handler function
8207
- * @returns {Function} Unsubscribe function
8208
- */
8209
- _chp.sub = function (topic, handler) {
8210
- var unsub = bw.sub(topic, handler);
8211
- this._subs.push(unsub);
8212
- return unsub;
8213
- };
8214
-
8215
- /**
8216
- * Call a named action.
8217
- * @param {string} name - Action name
8218
- * @param {...*} args - Arguments passed after comp
8219
- */
8220
- _chp.action = function (name) {
8221
- var fn = this._actions[name];
8222
- if (!fn) {
8223
- _cw('ComponentHandle.action: unknown action "' + name + '"');
8224
- return;
8225
- }
8226
- var args = [this].concat(Array.prototype.slice.call(arguments, 1));
8227
- return fn.apply(null, args);
8228
- };
8229
-
8230
- /**
8231
- * querySelector within the component's DOM.
8232
- * @param {string} sel - CSS selector
8233
- * @returns {Element|null}
8234
- */
8235
- _chp.select = function (sel) {
8236
- return this.element ? this.element.querySelector(sel) : null;
8237
- };
8238
-
8239
- /**
8240
- * querySelectorAll within the component's DOM.
8241
- * @param {string} sel - CSS selector
8242
- * @returns {Element[]}
8243
- */
8244
- _chp.selectAll = function (sel) {
8245
- if (!this.element) return [];
8246
- return Array.prototype.slice.call(this.element.querySelectorAll(sel));
8247
- };
8248
-
8249
- /**
8250
- * Tag this component with a user-defined ID for addressing via bw.message().
8251
- * The tag is added as a CSS class on the root element (DOM IS the registry).
8252
- * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
8253
- * @returns {ComponentHandle} this (for chaining)
8254
- */
8255
- _chp.userTag = function (tag) {
8256
- this._userTag = tag;
8257
- if (this.element) {
8258
- this.element.classList.add(tag);
8259
- }
8260
- return this;
8261
- };
8262
-
8263
- // Expose ComponentHandle on bw (for testing and advanced use)
8264
- bw._ComponentHandle = ComponentHandle;
8265
-
8266
- // ===================================================================================
8267
- // Control Flow Helpers
8268
- // ===================================================================================
8269
-
8270
- /**
8271
- * Conditional rendering helper.
8272
- * Returns a marker object that ComponentHandle detects during binding compilation.
8273
- * In static contexts (bw.html with state), evaluates immediately.
8274
- *
8275
- * @param {string} expr - Expression string like '${loggedIn}'
8276
- * @param {Object} tacoTrue - TACO to render when truthy
8277
- * @param {Object} [tacoFalse] - TACO to render when falsy
8278
- * @returns {Object} Marker object with _bwWhen flag
8279
- * @category Component
8280
- */
8281
- bw.when = function (expr, tacoTrue, tacoFalse) {
8282
- return {
8283
- _bwWhen: true,
8284
- expr: expr,
8285
- branches: [tacoTrue, tacoFalse || null]
8286
- };
8287
- };
8288
-
8289
- /**
8290
- * List rendering helper.
8291
- * Returns a marker object that ComponentHandle detects during binding compilation.
8292
- *
8293
- * @param {string} expr - Expression string like '${items}'
8294
- * @param {Function} fn - Factory function(item, index) returning TACO
8295
- * @returns {Object} Marker object with _bwEach flag
8296
- * @category Component
8297
- */
8298
- bw.each = function (expr, fn) {
8299
- return {
8300
- _bwEach: true,
8301
- expr: expr,
8302
- factory: fn
8303
- };
8304
- };
8305
-
8306
- // ===================================================================================
8307
- // bw.component() — Factory for ComponentHandle
8308
- // ===================================================================================
8309
-
8310
- /**
8311
- * Create a ComponentHandle from a TACO definition.
8312
- * The returned handle has .get(), .set(), .mount(), .destroy(), etc.
8313
- *
8314
- * @param {Object} taco - TACO definition with {t, a, c, o}
8315
- * @returns {ComponentHandle} Reactive component handle
8316
- * @category Component
8317
- * @see bw.DOM
8318
- * @example
8319
- * var counter = bw.component({
8320
- * t: 'div', c: [{ t: 'h3', c: 'Count: ${count}' }],
8321
- * o: { state: { count: 0 } }
8322
- * });
8323
- * bw.DOM('#app', counter);
8324
- * counter.set('count', 42); // DOM auto-updates
8325
- */
8326
- bw.component = function (taco) {
8327
- return new ComponentHandle(taco);
8328
- };
8329
-
8330
- // ===================================================================================
8331
- // bw.message() — SendMessage() for the web
8332
- // ===================================================================================
8333
-
8334
- /**
8335
- * Dispatch a message to a component by UUID or user tag.
8336
- * Finds the component's DOM element, looks up its ComponentHandle,
8337
- * and calls the named method. This is the bitwrench equivalent of
8338
- * Win32 SendMessage(hwnd, msg, wParam, lParam).
8339
- *
8340
- * @param {string} target - Component UUID (bw_uuid_*), comp ID (data-bw_comp_id), or user tag (CSS class)
8341
- * @param {string} action - Method name to call on the component
8342
- * @param {*} data - Data to pass to the method
8343
- * @returns {boolean} True if message was dispatched successfully
8344
- * @category Component
8345
- * @example
8346
- * // Tag a component
8347
- * myDash.userTag('dashboard_prod');
8348
- * // Dispatch locally
8349
- * bw.message('dashboard_prod', 'addAlert', { severity: 'warning', text: 'CPU spike' });
8350
- * // Or from SSE handler:
8351
- * es.onmessage = function(e) {
8352
- * var msg = JSON.parse(e.data);
8353
- * bw.message(msg.target, msg.action, msg.data);
8354
- * };
8355
- */
8356
- bw.message = function (target, action, data) {
8357
- // Try bw._el() first (handles UUID class, nodeMap cache, getElementById)
8358
- var el = bw._el(target);
8359
- // Then try data-bw_comp_id attribute
8360
- if (!el || !el._bwComponentHandle) {
8361
- el = bw.$('[data-bw_comp_id="' + target + '"]')[0];
8362
- }
8363
- // Then try CSS class (user tag)
8364
- if (!el || !el._bwComponentHandle) {
8365
- el = bw.$('.' + target)[0];
8366
- }
8367
- if (!el || !el._bwComponentHandle) return false;
8368
- var comp = el._bwComponentHandle;
8369
- if (!_is(comp[action], 'function')) {
8370
- _cw('bw.message: unknown action "' + action + '" on component ' + target);
8371
- return false;
8372
- }
8373
- comp[action](data);
8374
- return true;
8375
- };
8376
-
8377
- // ===================================================================================
8378
- // bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
8379
- // ===================================================================================
8380
-
8381
- /**
8382
- * Registry of named functions sent via register messages.
8383
- * Populated by bw.apply({ type: 'register', name, body }).
8384
- * Invoked by bw.apply({ type: 'call', name, args }).
8385
- * @private
8386
- */
8387
- bw._clientFunctions = {};
8388
-
8389
- /**
8390
- * Whether exec messages are allowed. Set by bwclient connect opts.allowExec.
8391
- * Default false — exec messages are rejected unless explicitly opted in.
8392
- * @private
8393
- */
8394
- bw._allowExec = false;
8395
-
8396
- /**
8397
- * Parse a bwserve protocol message string, supporting both strict JSON
8398
- * and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
8399
- *
8400
- * The r-prefix format is designed for C/C++ string literals where
8401
- * double-quote escaping is painful. The parser is a state machine
8402
- * that walks character by character — not a regex replace.
8403
- *
8404
- * Escaping: apostrophes inside single-quoted values must be escaped
8405
- * with backslash: r{'name':'Barry\'s room'}
8406
- *
8407
- * @param {string} str - JSON or r-prefixed relaxed JSON string
8408
- * @returns {Object} Parsed message object
8409
- * @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
8410
- * @category Core
7189
+ * Parse a bwserve protocol message string, supporting both strict JSON
7190
+ * and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
7191
+ *
7192
+ * The r-prefix format is designed for C/C++ string literals where
7193
+ * double-quote escaping is painful. The parser is a state machine
7194
+ * that walks character by character — not a regex replace.
7195
+ *
7196
+ * Escaping: apostrophes inside single-quoted values must be escaped
7197
+ * with backslash: r{'name':'Barry\'s room'}
7198
+ *
7199
+ * @param {string} str - JSON or r-prefixed relaxed JSON string
7200
+ * @returns {Object} Parsed message object
7201
+ * @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
7202
+ * @category Core
8411
7203
  */
8412
7204
  bw.parseJSONFlex = function (str) {
8413
7205
  str = (str || '').trim();
@@ -8577,142 +7369,32 @@
8577
7369
  // ===================================================================================
8578
7370
 
8579
7371
  /**
8580
- * Inspect a component's state, bindings, methods, and metadata.
8581
- * Works with DOM elements, CSS selectors, or ComponentHandle objects.
8582
- * Returns the ComponentHandle for console chaining.
7372
+ * Inspect a DOM element's bitwrench state, handle methods, and metadata.
7373
+ * Works with DOM elements or CSS selectors.
8583
7374
  *
8584
- * @param {string|Element|ComponentHandle} target - Selector, element, or handle
8585
- * @returns {ComponentHandle|null} The component handle, or null if not found
7375
+ * @param {string|Element} target - Selector or DOM element
7376
+ * @returns {Element|null} The element, or null if not found
8586
7377
  * @category Component
8587
7378
  * @example
8588
- * // In browser console, click element in Elements panel then:
7379
+ * bw.inspect('#my-carousel');
8589
7380
  * bw.inspect($0);
8590
- * // Or by selector:
8591
- * var h = bw.inspect('#my-dashboard');
8592
- * h.set('count', 99); // chain from returned handle
8593
7381
  */
8594
7382
  bw.inspect = function (target) {
8595
- var el = target;
8596
- var comp;
8597
- if (target && target._bwComponent === true) {
8598
- el = target.element;
8599
- comp = target;
8600
- } else {
8601
- if (_is(target, 'string')) {
8602
- el = bw.$(target)[0];
8603
- }
8604
- if (!el) {
8605
- _cw('bw.inspect: element not found');
8606
- return null;
8607
- }
8608
- comp = el._bwComponentHandle;
8609
- }
8610
- if (!comp) {
8611
- _cl('bw.inspect: no ComponentHandle on this element');
8612
- _cl(' Tag:', el.tagName);
8613
- _cl(' Classes:', el.className);
8614
- _cl(' _bw_state:', el._bw_state || '(none)');
7383
+ var el = _is(target, 'string') ? bw.$(target)[0] : target;
7384
+ if (!el) {
7385
+ _cw('bw.inspect: element not found');
8615
7386
  return null;
8616
7387
  }
8617
- var deps = comp._bindings.reduce(function (s, b) {
8618
- return s.concat(b.deps || []);
8619
- }, []).filter(function (v, i, a) {
8620
- return a.indexOf(v) === i;
8621
- });
8622
- console.group('Component: ' + comp._bwId);
8623
- _cl('State:', comp._state);
8624
- _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
8625
- _cl('Methods:', _keys(comp._methods));
8626
- _cl('Actions:', _keys(comp._actions));
8627
- _cl('User tag:', comp._userTag || '(none)');
8628
- _cl('Mounted:', comp.mounted);
8629
- _cl('Element:', comp.element);
7388
+ console.group('Element: ' + (bw.getUUID(el) || el.id || el.tagName));
7389
+ _cl('State:', el._bw_state || '(none)');
7390
+ _cl('Handle:', el.bw ? _keys(el.bw) : '(none)');
7391
+ _cl('Classes:', el.className);
7392
+ _cl('Refs:', el._bw_refs || '(none)');
8630
7393
  console.groupEnd();
8631
- return comp;
7394
+ return el;
8632
7395
  };
8633
-
8634
- // ===================================================================================
8635
- // bw.compile() — Pre-compile TACO into optimized factory
8636
- // ===================================================================================
8637
-
8638
- /**
8639
- * Pre-compile a TACO definition into a factory function.
8640
- * The factory produces ComponentHandles with pre-compiled binding evaluators.
8641
- *
8642
- * Phase 1: validates API surface. Template cloning optimization deferred.
8643
- *
8644
- * @param {Object} taco - TACO definition
8645
- * @returns {Function} Factory function(initialState?) → ComponentHandle
8646
- * @category Component
8647
- */
8648
- bw.compile = function (taco) {
8649
- // Pre-extract all binding expressions
8650
- var precompiled = [];
8651
- function walkExpressions(node) {
8652
- if (!_is(node, 'object')) return;
8653
- if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
8654
- var parsed = bw._parseBindings(node.c);
8655
- for (var i = 0; i < parsed.length; i++) {
8656
- try {
8657
- precompiled.push({
8658
- expr: parsed[i].expr,
8659
- fn: new Function('state', 'with(state){return (' + parsed[i].expr + ');}')
8660
- });
8661
- } catch (e) {
8662
- precompiled.push({
8663
- expr: parsed[i].expr,
8664
- fn: function fn() {
8665
- return '';
8666
- }
8667
- });
8668
- }
8669
- }
8670
- }
8671
- if (node.a) {
8672
- for (var key in node.a) {
8673
- if (_hop.call(node.a, key)) {
8674
- var v = node.a[key];
8675
- if (_is(v, 'string') && v.indexOf('${') >= 0) {
8676
- var parsed2 = bw._parseBindings(v);
8677
- for (var j = 0; j < parsed2.length; j++) {
8678
- try {
8679
- precompiled.push({
8680
- expr: parsed2[j].expr,
8681
- fn: new Function('state', 'with(state){return (' + parsed2[j].expr + ');}')
8682
- });
8683
- } catch (e2) {
8684
- precompiled.push({
8685
- expr: parsed2[j].expr,
8686
- fn: function fn() {
8687
- return '';
8688
- }
8689
- });
8690
- }
8691
- }
8692
- }
8693
- }
8694
- }
8695
- }
8696
- if (_isA(node.c)) {
8697
- for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
8698
- } else if (_is(node.c, 'object') && node.c.t) {
8699
- walkExpressions(node.c);
8700
- }
8701
- }
8702
- walkExpressions(taco);
8703
- return function (initialState) {
8704
- var handle = new ComponentHandle(taco);
8705
- handle._compile = true;
8706
- handle._precompiledBindings = precompiled;
8707
- if (initialState) {
8708
- for (var k in initialState) {
8709
- if (_hop.call(initialState, k)) {
8710
- handle._state[k] = initialState[k];
8711
- }
8712
- }
8713
- }
8714
- return handle;
8715
- };
7396
+ bw.compile = function () {
7397
+ throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.');
8716
7398
  };
8717
7399
 
8718
7400
  /**
@@ -10018,7 +8700,7 @@
10018
8700
  * handle.destroy();
10019
8701
  */
10020
8702
  bw.render = function (element, position, taco) {
10021
- var _taco$o4, _taco$o5, _taco$o6;
8703
+ var _taco$o3, _taco$o4, _taco$o5;
10022
8704
  // Get target element
10023
8705
  var targetEl = _is(element, 'string') ? document.querySelector(element) : element;
10024
8706
  if (!targetEl) {
@@ -10030,8 +8712,8 @@
10030
8712
  };
10031
8713
  }
10032
8714
 
10033
- // Generate unique ID if not provided
10034
- var componentId = ((_taco$o4 = taco.o) === null || _taco$o4 === void 0 ? void 0 : _taco$o4.id) || bw.uuid();
8715
+ // Generate unique UUID class if not provided
8716
+ var componentId = ((_taco$o3 = taco.o) === null || _taco$o3 === void 0 ? void 0 : _taco$o3.id) || bw.uuid('uuid');
10035
8717
 
10036
8718
  // Create DOM element
10037
8719
  var domElement;
@@ -10046,8 +8728,9 @@
10046
8728
  };
10047
8729
  }
10048
8730
 
10049
- // Add component ID to element
10050
- domElement.setAttribute('data-bw_id', componentId);
8731
+ // Add component ID as class + lifecycle marker
8732
+ domElement.classList.add(componentId);
8733
+ domElement.classList.add(_BW_LC);
10051
8734
 
10052
8735
  // Insert into DOM based on position
10053
8736
  try {
@@ -10087,7 +8770,7 @@
10087
8770
  status_code: 'success',
10088
8771
  // Reference to original TACO
10089
8772
  _taco: _objectSpread2({}, taco),
10090
- _state: _objectSpread2({}, ((_taco$o5 = taco.o) === null || _taco$o5 === void 0 ? void 0 : _taco$o5.state) || {}),
8773
+ _state: _objectSpread2({}, ((_taco$o4 = taco.o) === null || _taco$o4 === void 0 ? void 0 : _taco$o4.state) || {}),
10091
8774
  _mounted: true,
10092
8775
  // Get DOM element
10093
8776
  get element() {
@@ -10118,7 +8801,8 @@
10118
8801
 
10119
8802
  // Re-render
10120
8803
  var newElement = bw.createDOM(this._taco);
10121
- newElement.setAttribute('data-bw_id', componentId);
8804
+ newElement.classList.add(componentId);
8805
+ newElement.classList.add(_BW_LC);
10122
8806
 
10123
8807
  // Replace in DOM
10124
8808
  parent.replaceChild(newElement, this.element);
@@ -10245,7 +8929,7 @@
10245
8929
  bw._componentRegistry.set(componentId, handle);
10246
8930
 
10247
8931
  // Call mounted lifecycle
10248
- if ((_taco$o6 = taco.o) !== null && _taco$o6 !== void 0 && _taco$o6.mounted) {
8932
+ if ((_taco$o5 = taco.o) !== null && _taco$o5 !== void 0 && _taco$o5.mounted) {
10249
8933
  taco.o.mounted(domElement, handle);
10250
8934
  }
10251
8935
  return handle;
@@ -10293,16 +8977,15 @@
10293
8977
  // Variant class helper: bw.variantClass('primary') → 'bw_primary'
10294
8978
  bw.variantClass = variantClass;
10295
8979
 
10296
- // Create functions that return handles (plain renderComponent, no Handle overlay)
8980
+ // Create functions that return DOM elements (createCard, createTable, etc.)
10297
8981
  Object.entries(components).forEach(function (_ref11) {
10298
8982
  var _ref12 = _slicedToArray(_ref11, 2),
10299
8983
  name = _ref12[0],
10300
8984
  fn = _ref12[1];
10301
8985
  if (name.startsWith('make')) {
10302
- var createName = 'create' + name.substring(4); // createCard, createTable, etc.
8986
+ var createName = 'create' + name.substring(4);
10303
8987
  bw[createName] = function (props) {
10304
- var taco = fn(props);
10305
- return bw.renderComponent(taco);
8988
+ return bw.createDOM(fn(props));
10306
8989
  };
10307
8990
  }
10308
8991
  });