bitwrench 2.0.18 → 2.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -81
- package/dist/bitwrench-bccl.cjs.js +221 -48
- package/dist/bitwrench-bccl.cjs.min.js +3 -3
- package/dist/bitwrench-bccl.esm.js +221 -48
- package/dist/bitwrench-bccl.esm.min.js +3 -3
- package/dist/bitwrench-bccl.umd.js +221 -48
- package/dist/bitwrench-bccl.umd.min.js +3 -3
- package/dist/bitwrench-code-edit.cjs.js +7 -9
- package/dist/bitwrench-code-edit.cjs.min.js +5 -7
- package/dist/bitwrench-code-edit.es5.js +6 -8
- package/dist/bitwrench-code-edit.es5.min.js +5 -7
- package/dist/bitwrench-code-edit.esm.js +7 -9
- package/dist/bitwrench-code-edit.esm.min.js +5 -7
- package/dist/bitwrench-code-edit.umd.js +7 -9
- package/dist/bitwrench-code-edit.umd.min.js +5 -7
- package/dist/bitwrench-debug.js +268 -0
- package/dist/bitwrench-debug.min.js +3 -0
- package/dist/bitwrench-lean.cjs.js +250 -1574
- package/dist/bitwrench-lean.cjs.min.js +6 -6
- package/dist/bitwrench-lean.es5.js +344 -1661
- package/dist/bitwrench-lean.es5.min.js +4 -4
- package/dist/bitwrench-lean.esm.js +250 -1574
- package/dist/bitwrench-lean.esm.min.js +6 -6
- package/dist/bitwrench-lean.umd.js +250 -1574
- package/dist/bitwrench-lean.umd.min.js +6 -6
- package/dist/bitwrench-util-css.cjs.js +1 -1
- package/dist/bitwrench-util-css.cjs.min.js +1 -1
- package/dist/bitwrench-util-css.es5.js +1 -1
- package/dist/bitwrench-util-css.es5.min.js +1 -1
- package/dist/bitwrench-util-css.esm.js +1 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -1
- package/dist/bitwrench-util-css.umd.js +1 -1
- package/dist/bitwrench-util-css.umd.min.js +1 -1
- package/dist/bitwrench.cjs.js +510 -1660
- package/dist/bitwrench.cjs.min.js +7 -7
- package/dist/bitwrench.css +80 -33
- package/dist/bitwrench.es5.js +569 -1694
- package/dist/bitwrench.es5.min.js +5 -5
- package/dist/bitwrench.esm.js +510 -1660
- package/dist/bitwrench.esm.min.js +7 -7
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +510 -1660
- package/dist/bitwrench.umd.min.js +7 -7
- package/dist/builds.json +133 -111
- package/dist/bwserve.cjs.js +2 -2
- package/dist/bwserve.esm.js +2 -2
- package/dist/sri.json +46 -44
- package/package.json +5 -3
- package/readme.html +86 -75
- package/src/bitwrench-bccl-entry.js +3 -4
- package/src/bitwrench-bccl.js +217 -43
- package/src/bitwrench-code-edit.js +6 -8
- package/src/bitwrench-debug.js +245 -0
- package/src/bitwrench-styles.js +35 -8
- package/src/bitwrench.js +212 -1563
- package/src/cli/attach.js +53 -21
- package/src/cli/serve.js +179 -3
- package/src/version.js +3 -3
package/src/bitwrench.js
CHANGED
|
@@ -56,12 +56,11 @@ const bw = {
|
|
|
56
56
|
_subIdCounter: 0, // monotonic ID for subscriptions
|
|
57
57
|
|
|
58
58
|
// ── Node reference cache ──────────────────────────────────────────────
|
|
59
|
-
// Fast O(1) lookup for elements by
|
|
59
|
+
// Fast O(1) lookup for elements by id attribute or bw_uuid_* class.
|
|
60
60
|
//
|
|
61
61
|
// Populated by bw.createDOM() when elements have:
|
|
62
|
-
// - data-bw_id attribute (user-declared addressable elements)
|
|
63
62
|
// - id attribute (standard HTML id)
|
|
64
|
-
// -
|
|
63
|
+
// - bw_uuid_* class (lifecycle-managed or explicitly addressed elements)
|
|
65
64
|
//
|
|
66
65
|
// Cleaned up by bw.cleanup() when elements are destroyed via bitwrench APIs.
|
|
67
66
|
// On cache miss, falls back to querySelector/getElementById — never fails,
|
|
@@ -69,7 +68,7 @@ const bw = {
|
|
|
69
68
|
// via parentNode === null check (IE11-safe, unlike el.isConnected).
|
|
70
69
|
//
|
|
71
70
|
// Elements created via bw.createDOM() also get el._bw_refs — a local map of
|
|
72
|
-
// child
|
|
71
|
+
// child id/UUID -> DOM node ref for fast parent->child access in o.render.
|
|
73
72
|
// This is the bitwrench equivalent of React's compiled template "holes".
|
|
74
73
|
//
|
|
75
74
|
// Contract: if you remove elements outside of bitwrench APIs (raw el.remove()),
|
|
@@ -149,7 +148,6 @@ Object.defineProperty(bw, '_isBrowser', {
|
|
|
149
148
|
// _cw console.warn 8
|
|
150
149
|
// _cl console.log 11
|
|
151
150
|
// _ce console.error 4
|
|
152
|
-
// _chp ComponentHandle.prototype 28 (defined after constructor)
|
|
153
151
|
//
|
|
154
152
|
// Note: document.createElement etc. are NOT aliased because they require
|
|
155
153
|
// `this === document` and .bind() would add overhead on every call.
|
|
@@ -322,15 +320,15 @@ bw.uuid = function(prefix) {
|
|
|
322
320
|
* 1. Check `bw._nodeMap[id]` — if found and still attached (parentNode !== null), return it
|
|
323
321
|
* 2. If cached ref is detached (parentNode === null), remove stale entry
|
|
324
322
|
* 3. Fall back to `document.getElementById(id)` then `document.querySelector(...)`
|
|
325
|
-
* 4.
|
|
326
|
-
* 5.
|
|
323
|
+
* 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
324
|
+
* 5. Cache the result for next time
|
|
327
325
|
*
|
|
328
326
|
* Accepts a DOM element directly (pass-through) or a string identifier.
|
|
329
327
|
* String identifiers are tried as: direct map key, getElementById,
|
|
330
328
|
* querySelector (for CSS selectors starting with . or #), and
|
|
331
|
-
*
|
|
329
|
+
* bw_uuid_* class selector.
|
|
332
330
|
*
|
|
333
|
-
* @param {string|Element} id - Element ID, CSS selector,
|
|
331
|
+
* @param {string|Element} id - Element ID, CSS selector, bw_uuid_* class, or DOM element
|
|
334
332
|
* @returns {Element|null} The DOM element, or null if not found
|
|
335
333
|
* @category Internal
|
|
336
334
|
*/
|
|
@@ -359,17 +357,12 @@ bw._el = function(id) {
|
|
|
359
357
|
el = document.querySelector(id);
|
|
360
358
|
}
|
|
361
359
|
|
|
362
|
-
// 4. Try
|
|
363
|
-
if (!el) {
|
|
364
|
-
el = document.querySelector('[data-bw_id="' + id + '"]');
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// 5. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
360
|
+
// 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
368
361
|
if (!el && id.indexOf('bw_uuid_') === 0) {
|
|
369
362
|
el = document.querySelector('.' + id);
|
|
370
363
|
}
|
|
371
364
|
|
|
372
|
-
//
|
|
365
|
+
// 5. Cache the result for next time
|
|
373
366
|
if (el) {
|
|
374
367
|
bw._nodeMap[id] = el;
|
|
375
368
|
}
|
|
@@ -381,17 +374,17 @@ bw._el = function(id) {
|
|
|
381
374
|
* Register a DOM element in the node cache under one or more keys.
|
|
382
375
|
*
|
|
383
376
|
* Called internally by `bw.createDOM()`. Registers elements that have
|
|
384
|
-
* id attributes,
|
|
377
|
+
* id attributes, UUID classes, or both.
|
|
385
378
|
*
|
|
386
379
|
* @param {Element} el - DOM element to register
|
|
387
|
-
* @param {string} [
|
|
380
|
+
* @param {string} [uuid] - bw_uuid_* class token to register under
|
|
388
381
|
* @category Internal
|
|
389
382
|
*/
|
|
390
|
-
bw._registerNode = function(el,
|
|
383
|
+
bw._registerNode = function(el, uuid) {
|
|
391
384
|
if (!el) return;
|
|
392
|
-
// Register under
|
|
393
|
-
if (
|
|
394
|
-
bw._nodeMap[
|
|
385
|
+
// Register under UUID class token
|
|
386
|
+
if (uuid) {
|
|
387
|
+
bw._nodeMap[uuid] = el;
|
|
395
388
|
}
|
|
396
389
|
// Register under id attribute
|
|
397
390
|
var htmlId = el.getAttribute ? el.getAttribute('id') : null;
|
|
@@ -407,13 +400,13 @@ bw._registerNode = function(el, bwId) {
|
|
|
407
400
|
* through bitwrench APIs.
|
|
408
401
|
*
|
|
409
402
|
* @param {Element} el - DOM element to deregister
|
|
410
|
-
* @param {string} [
|
|
403
|
+
* @param {string} [uuid] - bw_uuid_* class token to remove
|
|
411
404
|
* @category Internal
|
|
412
405
|
*/
|
|
413
|
-
bw._deregisterNode = function(el,
|
|
414
|
-
// Remove
|
|
415
|
-
if (
|
|
416
|
-
delete bw._nodeMap[
|
|
406
|
+
bw._deregisterNode = function(el, uuid) {
|
|
407
|
+
// Remove UUID class entry
|
|
408
|
+
if (uuid) {
|
|
409
|
+
delete bw._nodeMap[uuid];
|
|
417
410
|
}
|
|
418
411
|
// Remove id attribute entry
|
|
419
412
|
var htmlId = el && el.getAttribute ? el.getAttribute('id') : null;
|
|
@@ -426,6 +419,13 @@ bw._deregisterNode = function(el, bwId) {
|
|
|
426
419
|
// bw.assignUUID() / bw.getUUID() — Explicit UUID addressing for TACO objects
|
|
427
420
|
// ===================================================================================
|
|
428
421
|
|
|
422
|
+
/**
|
|
423
|
+
* Marker class for elements with lifecycle hooks (mounted/unmount/render/state).
|
|
424
|
+
* Used by cleanup() to find lifecycle-managed elements via querySelectorAll('.bw_lc').
|
|
425
|
+
* @private
|
|
426
|
+
*/
|
|
427
|
+
var _BW_LC = 'bw_lc';
|
|
428
|
+
|
|
429
429
|
/**
|
|
430
430
|
* Regex to match a bw_uuid_* token in a class string.
|
|
431
431
|
* @private
|
|
@@ -614,15 +614,6 @@ bw.html = function(taco, options = {}) {
|
|
|
614
614
|
// Handle null/undefined
|
|
615
615
|
if (taco == null) return '';
|
|
616
616
|
|
|
617
|
-
// Handle ComponentHandle — use its .taco
|
|
618
|
-
if (taco && taco._bwComponent === true) {
|
|
619
|
-
var compOptions = Object.assign({}, options);
|
|
620
|
-
if (!compOptions.state && taco._state) {
|
|
621
|
-
compOptions.state = taco._state;
|
|
622
|
-
}
|
|
623
|
-
return bw.html(taco.taco, compOptions);
|
|
624
|
-
}
|
|
625
|
-
|
|
626
617
|
// Handle arrays of TACOs
|
|
627
618
|
if (_isA(taco)) {
|
|
628
619
|
return taco.map(t => bw.html(t, options)).join('');
|
|
@@ -633,24 +624,6 @@ bw.html = function(taco, options = {}) {
|
|
|
633
624
|
return taco.v;
|
|
634
625
|
}
|
|
635
626
|
|
|
636
|
-
// Handle bw.when() markers
|
|
637
|
-
if (taco && taco._bwWhen && options.state) {
|
|
638
|
-
var whenExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
639
|
-
var whenVal = options.compile
|
|
640
|
-
? bw._resolveTemplate('${' + whenExpr + '}', options.state, true)
|
|
641
|
-
: bw._evaluatePath(options.state, whenExpr);
|
|
642
|
-
var branch = whenVal ? taco.branches[0] : (taco.branches[1] || null);
|
|
643
|
-
return branch ? bw.html(branch, options) : '';
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// Handle bw.each() markers
|
|
647
|
-
if (taco && taco._bwEach && options.state) {
|
|
648
|
-
var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
649
|
-
var arr = bw._evaluatePath(options.state, eachExpr);
|
|
650
|
-
if (!_isA(arr)) return '';
|
|
651
|
-
return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
|
|
652
|
-
}
|
|
653
|
-
|
|
654
627
|
// Handle primitives and non-TACO objects
|
|
655
628
|
if (!_is(taco, 'object') || !taco.t) {
|
|
656
629
|
var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
|
|
@@ -714,14 +687,14 @@ bw.html = function(taco, options = {}) {
|
|
|
714
687
|
}
|
|
715
688
|
}
|
|
716
689
|
|
|
717
|
-
// Add
|
|
718
|
-
if ((opts.mounted || opts.unmount) && !attrs.class
|
|
719
|
-
const
|
|
690
|
+
// Add bw_uuid + bw_lc classes if lifecycle hooks present
|
|
691
|
+
if ((opts.mounted || opts.unmount) && !_UUID_RE.test(attrs.class || '')) {
|
|
692
|
+
const uuid = bw.uuid('uuid');
|
|
720
693
|
attrStr = attrStr.replace(/class="([^"]*)"/, (_match, classes) => {
|
|
721
|
-
return `class="${classes}
|
|
694
|
+
return `class="${classes} ${uuid} ${_BW_LC}"`.trim();
|
|
722
695
|
});
|
|
723
696
|
if (!attrStr.includes('class=')) {
|
|
724
|
-
attrStr += ` class="
|
|
697
|
+
attrStr += ` class="${uuid} ${_BW_LC}"`;
|
|
725
698
|
}
|
|
726
699
|
}
|
|
727
700
|
|
|
@@ -949,11 +922,6 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
949
922
|
return frag;
|
|
950
923
|
}
|
|
951
924
|
|
|
952
|
-
// Handle ComponentHandle — extract .taco for DOM creation
|
|
953
|
-
if (taco && taco._bwComponent === true) {
|
|
954
|
-
return bw.createDOM(taco.taco, options);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
925
|
// Handle text nodes
|
|
958
926
|
if (!_is(taco, 'object') || !taco.t) {
|
|
959
927
|
return document.createTextNode(String(taco));
|
|
@@ -994,24 +962,19 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
994
962
|
}
|
|
995
963
|
|
|
996
964
|
// Add children, building _bw_refs for fast parent→child access.
|
|
997
|
-
// Children with
|
|
965
|
+
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
998
966
|
// so o.render functions can access them without any DOM lookup.
|
|
999
967
|
if (content != null) {
|
|
1000
968
|
if (_isA(content)) {
|
|
1001
969
|
content.forEach(child => {
|
|
1002
970
|
if (child != null) {
|
|
1003
|
-
// Handle ComponentHandle in content arrays (Level 2 children)
|
|
1004
|
-
if (child._bwComponent === true) {
|
|
1005
|
-
child.mount(el);
|
|
1006
|
-
return;
|
|
1007
|
-
}
|
|
1008
971
|
var childEl = bw.createDOM(child, options);
|
|
1009
972
|
el.appendChild(childEl);
|
|
1010
973
|
// Build local refs for addressable children
|
|
1011
|
-
var
|
|
1012
|
-
if (
|
|
974
|
+
var childRefId = (child && child.a) ? (child.a.id || bw.getUUID(child)) : null;
|
|
975
|
+
if (childRefId) {
|
|
1013
976
|
if (!el._bw_refs) el._bw_refs = {};
|
|
1014
|
-
el._bw_refs[
|
|
977
|
+
el._bw_refs[childRefId] = childEl;
|
|
1015
978
|
}
|
|
1016
979
|
// Bubble up grandchild refs (flatten one level)
|
|
1017
980
|
if (childEl._bw_refs) {
|
|
@@ -1027,16 +990,13 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
1027
990
|
} else if (_is(content, 'object') && content.__bw_raw) {
|
|
1028
991
|
// Raw HTML content — inject via innerHTML
|
|
1029
992
|
el.innerHTML = content.v;
|
|
1030
|
-
} else if (content._bwComponent === true) {
|
|
1031
|
-
// Single ComponentHandle as content
|
|
1032
|
-
content.mount(el);
|
|
1033
993
|
} else if (_is(content, 'object') && content.t) {
|
|
1034
994
|
var childEl = bw.createDOM(content, options);
|
|
1035
995
|
el.appendChild(childEl);
|
|
1036
|
-
var
|
|
1037
|
-
if (
|
|
996
|
+
var childRefId = content.a ? (content.a.id || bw.getUUID(content)) : null;
|
|
997
|
+
if (childRefId) {
|
|
1038
998
|
if (!el._bw_refs) el._bw_refs = {};
|
|
1039
|
-
el._bw_refs[
|
|
999
|
+
el._bw_refs[childRefId] = childEl;
|
|
1040
1000
|
}
|
|
1041
1001
|
if (childEl._bw_refs) {
|
|
1042
1002
|
if (!el._bw_refs) el._bw_refs = {};
|
|
@@ -1066,57 +1026,88 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
1066
1026
|
|
|
1067
1027
|
// Handle lifecycle hooks and state
|
|
1068
1028
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
1069
|
-
|
|
1070
|
-
el.
|
|
1029
|
+
// Ensure element has a UUID class for identity
|
|
1030
|
+
var uuid = bw.getUUID(el) || bw.uuid('uuid');
|
|
1031
|
+
el.classList.add(uuid);
|
|
1032
|
+
el.classList.add(_BW_LC);
|
|
1071
1033
|
|
|
1072
|
-
// Register in node cache under
|
|
1073
|
-
bw._registerNode(el,
|
|
1034
|
+
// Register in node cache under UUID class
|
|
1035
|
+
bw._registerNode(el, uuid);
|
|
1074
1036
|
|
|
1075
1037
|
// Store state
|
|
1076
1038
|
if (opts.state) {
|
|
1077
1039
|
el._bw_state = opts.state;
|
|
1078
1040
|
}
|
|
1079
1041
|
|
|
1080
|
-
// o.render —
|
|
1042
|
+
// o.render — store the render function for bw.update()
|
|
1081
1043
|
if (opts.render) {
|
|
1082
1044
|
el._bw_render = opts.render;
|
|
1045
|
+
}
|
|
1083
1046
|
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1047
|
+
// Determine what to call on mount:
|
|
1048
|
+
// - If o.mounted exists, call it (it can call el._bw_render() for initial render)
|
|
1049
|
+
// - Otherwise if o.render exists, auto-call it as a convenience shorthand
|
|
1050
|
+
var mountFn = opts.mounted || (opts.render ? function(mountEl) {
|
|
1051
|
+
opts.render(mountEl, mountEl._bw_state || {});
|
|
1052
|
+
} : null);
|
|
1087
1053
|
|
|
1088
|
-
|
|
1089
|
-
if (document.body.contains(el)) {
|
|
1090
|
-
opts.render(el, el._bw_state || {});
|
|
1091
|
-
} else {
|
|
1092
|
-
requestAnimationFrame(() => {
|
|
1093
|
-
if (document.body.contains(el)) {
|
|
1094
|
-
opts.render(el, el._bw_state || {});
|
|
1095
|
-
}
|
|
1096
|
-
});
|
|
1097
|
-
}
|
|
1098
|
-
} else if (opts.mounted) {
|
|
1099
|
-
// Queue mounted callback (legacy pattern)
|
|
1054
|
+
if (mountFn) {
|
|
1100
1055
|
if (document.body.contains(el)) {
|
|
1101
|
-
|
|
1056
|
+
mountFn(el, el._bw_state || {});
|
|
1102
1057
|
} else {
|
|
1103
1058
|
requestAnimationFrame(() => {
|
|
1104
1059
|
if (document.body.contains(el)) {
|
|
1105
|
-
|
|
1060
|
+
mountFn(el, el._bw_state || {});
|
|
1106
1061
|
}
|
|
1107
1062
|
});
|
|
1108
1063
|
}
|
|
1109
1064
|
}
|
|
1110
1065
|
|
|
1111
|
-
// Store unmount callback
|
|
1066
|
+
// Store unmount callback keyed by UUID class
|
|
1112
1067
|
if (opts.unmount) {
|
|
1113
|
-
bw._unmountCallbacks.set(
|
|
1068
|
+
bw._unmountCallbacks.set(uuid, () => {
|
|
1114
1069
|
opts.unmount(el, el._bw_state || {});
|
|
1115
1070
|
});
|
|
1116
1071
|
}
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Component handle: attach methods to el.bw namespace
|
|
1075
|
+
if (opts.handle || opts.slots) {
|
|
1076
|
+
if (!el.bw) el.bw = {};
|
|
1077
|
+
|
|
1078
|
+
// Explicit handle methods: fn(el, ...args) -> el.bw.method(...args)
|
|
1079
|
+
if (opts.handle) {
|
|
1080
|
+
for (var hk in opts.handle) {
|
|
1081
|
+
if (_hop.call(opts.handle, hk)) {
|
|
1082
|
+
el.bw[hk] = opts.handle[hk].bind(null, el);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Slot declarations: auto-generate setX/getX pairs
|
|
1088
|
+
if (opts.slots) {
|
|
1089
|
+
for (var sk in opts.slots) {
|
|
1090
|
+
if (_hop.call(opts.slots, sk)) {
|
|
1091
|
+
(function(name, selector) {
|
|
1092
|
+
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1093
|
+
el.bw['set' + cap] = function(value) {
|
|
1094
|
+
var t = el.querySelector(selector);
|
|
1095
|
+
if (!t) return;
|
|
1096
|
+
if (value != null && typeof value === 'object' && value.t) {
|
|
1097
|
+
t.innerHTML = '';
|
|
1098
|
+
t.appendChild(bw.createDOM(value));
|
|
1099
|
+
} else {
|
|
1100
|
+
t.textContent = (value != null) ? String(value) : '';
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
el.bw['get' + cap] = function() {
|
|
1104
|
+
var t = el.querySelector(selector);
|
|
1105
|
+
return t ? t.textContent : '';
|
|
1106
|
+
};
|
|
1107
|
+
})(sk, opts.slots[sk]);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1120
1111
|
}
|
|
1121
1112
|
|
|
1122
1113
|
return el;
|
|
@@ -1163,7 +1154,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
1163
1154
|
// the target is the mount point, not the content being replaced)
|
|
1164
1155
|
const savedState = targetEl._bw_state;
|
|
1165
1156
|
const savedRender = targetEl._bw_render;
|
|
1166
|
-
const
|
|
1157
|
+
const savedUuid = bw.getUUID(targetEl);
|
|
1167
1158
|
const savedSubs = targetEl._bw_subs;
|
|
1168
1159
|
|
|
1169
1160
|
// Temporarily remove _bw_subs so cleanup doesn't call them
|
|
@@ -1175,10 +1166,9 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
1175
1166
|
// Restore the target's own state/render/subs after cleanup
|
|
1176
1167
|
if (savedState !== undefined) targetEl._bw_state = savedState;
|
|
1177
1168
|
if (savedRender) targetEl._bw_render = savedRender;
|
|
1178
|
-
if (
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
bw._registerNode(targetEl, savedBwId);
|
|
1169
|
+
if (savedUuid) {
|
|
1170
|
+
// UUID class stays on element through cleanup; re-register in cache
|
|
1171
|
+
bw._registerNode(targetEl, savedUuid);
|
|
1182
1172
|
}
|
|
1183
1173
|
if (savedSubs) targetEl._bw_subs = savedSubs;
|
|
1184
1174
|
|
|
@@ -1186,25 +1176,11 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
1186
1176
|
targetEl.innerHTML = '';
|
|
1187
1177
|
|
|
1188
1178
|
if (taco != null) {
|
|
1189
|
-
// Handle ComponentHandle (reactive components from bw.component())
|
|
1190
|
-
if (taco._bwComponent === true) {
|
|
1191
|
-
taco.mount(targetEl);
|
|
1192
|
-
}
|
|
1193
|
-
// Handle component handles (objects with element property)
|
|
1194
|
-
else if (taco.element instanceof Element) {
|
|
1195
|
-
targetEl.appendChild(taco.element);
|
|
1196
|
-
}
|
|
1197
1179
|
// Handle arrays
|
|
1198
|
-
|
|
1180
|
+
if (_isA(taco)) {
|
|
1199
1181
|
taco.forEach(t => {
|
|
1200
1182
|
if (t != null) {
|
|
1201
|
-
|
|
1202
|
-
t.mount(targetEl);
|
|
1203
|
-
} else if (t.element instanceof Element) {
|
|
1204
|
-
targetEl.appendChild(t.element);
|
|
1205
|
-
} else {
|
|
1206
|
-
targetEl.appendChild(bw.createDOM(t, options));
|
|
1207
|
-
}
|
|
1183
|
+
targetEl.appendChild(bw.createDOM(t, options));
|
|
1208
1184
|
}
|
|
1209
1185
|
});
|
|
1210
1186
|
}
|
|
@@ -1217,205 +1193,36 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
1217
1193
|
return targetEl;
|
|
1218
1194
|
};
|
|
1219
1195
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
* Used internally by `bw.renderComponent()`. Creates a proxy-like object
|
|
1224
|
-
* where setting a property triggers `handle.onPropChange()`.
|
|
1225
|
-
*
|
|
1226
|
-
* @param {Object} handle - Component handle
|
|
1227
|
-
* @param {Object} props - Initial props
|
|
1228
|
-
* @returns {Object} Compiled props object with getters/setters
|
|
1229
|
-
* @category DOM Generation
|
|
1230
|
-
*/
|
|
1231
|
-
bw.compileProps = function(handle, props = {}) {
|
|
1232
|
-
const compiledProps = {};
|
|
1233
|
-
|
|
1234
|
-
_keys(props).forEach(key => {
|
|
1235
|
-
// Create getter/setter for each prop
|
|
1236
|
-
Object.defineProperty(compiledProps, key, {
|
|
1237
|
-
get() {
|
|
1238
|
-
return handle._props[key];
|
|
1239
|
-
},
|
|
1240
|
-
set(value) {
|
|
1241
|
-
const oldValue = handle._props[key];
|
|
1242
|
-
if (oldValue !== value) {
|
|
1243
|
-
handle._props[key] = value;
|
|
1244
|
-
// Trigger update if prop changed
|
|
1245
|
-
if (handle.onPropChange) {
|
|
1246
|
-
handle.onPropChange(key, value, oldValue);
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
},
|
|
1250
|
-
enumerable: true,
|
|
1251
|
-
configurable: true
|
|
1252
|
-
});
|
|
1253
|
-
});
|
|
1254
|
-
|
|
1255
|
-
return compiledProps;
|
|
1256
|
-
};
|
|
1196
|
+
// Deprecation stubs for removed ComponentHandle APIs
|
|
1197
|
+
bw.compileProps = function() { throw new Error('bw.compileProps() removed in v2.0.19. Use o.handle/o.slots instead.'); };
|
|
1198
|
+
bw.renderComponent = function() { throw new Error('bw.renderComponent() removed in v2.0.19. Use bw.mount() with o.handle/o.slots instead.'); };
|
|
1257
1199
|
|
|
1258
1200
|
/**
|
|
1259
|
-
*
|
|
1201
|
+
* Mount a TACO into a target element and return the created root element.
|
|
1202
|
+
* Like bw.DOM() but returns the root element of the TACO (not the container),
|
|
1203
|
+
* giving direct access to el.bw handle methods.
|
|
1260
1204
|
*
|
|
1261
|
-
*
|
|
1262
|
-
*
|
|
1263
|
-
*
|
|
1264
|
-
* @
|
|
1265
|
-
* @param {Object} [options] - Render options
|
|
1266
|
-
* @returns {Object} Component handle with element, props, state, update(), destroy()
|
|
1205
|
+
* @param {string|Element} target - CSS selector or DOM element
|
|
1206
|
+
* @param {Object} taco - TACO to render
|
|
1207
|
+
* @param {Object} [options] - Mount options
|
|
1208
|
+
* @returns {Element} The created root element
|
|
1267
1209
|
* @category DOM Generation
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
return this._compiledProps;
|
|
1286
|
-
},
|
|
1287
|
-
|
|
1288
|
-
/**
|
|
1289
|
-
* Query all matching elements within this component
|
|
1290
|
-
* @param {string} selector - CSS selector
|
|
1291
|
-
* @returns {NodeList} Matching elements
|
|
1292
|
-
*/
|
|
1293
|
-
$(selector) {
|
|
1294
|
-
return this.element.querySelectorAll(selector);
|
|
1295
|
-
},
|
|
1296
|
-
|
|
1297
|
-
/**
|
|
1298
|
-
* Query the first matching element within this component
|
|
1299
|
-
* @param {string} selector - CSS selector
|
|
1300
|
-
* @returns {Element|null} First matching element or null
|
|
1301
|
-
*/
|
|
1302
|
-
$first(selector) {
|
|
1303
|
-
return this.element.querySelector(selector);
|
|
1304
|
-
},
|
|
1305
|
-
|
|
1306
|
-
/**
|
|
1307
|
-
* Update component with new props and re-render in place
|
|
1308
|
-
* @param {Object} newProps - Properties to merge into current props
|
|
1309
|
-
* @returns {Object} this handle (for chaining)
|
|
1310
|
-
*/
|
|
1311
|
-
update(newProps) {
|
|
1312
|
-
// Update internal props
|
|
1313
|
-
Object.assign(this._props, newProps);
|
|
1314
|
-
|
|
1315
|
-
// Rebuild TACO with new props
|
|
1316
|
-
const newTaco = { ...this.taco, a: { ...this.taco.a, ...newProps } };
|
|
1317
|
-
const newElement = bw.createDOM(newTaco, options);
|
|
1318
|
-
|
|
1319
|
-
// Replace in DOM
|
|
1320
|
-
this.element.replaceWith(newElement);
|
|
1321
|
-
this.element = newElement;
|
|
1322
|
-
this.taco = newTaco;
|
|
1323
|
-
|
|
1324
|
-
return this;
|
|
1325
|
-
},
|
|
1326
|
-
|
|
1327
|
-
/**
|
|
1328
|
-
* Re-render the component from its current TACO, replacing the DOM element
|
|
1329
|
-
* @returns {Object} this handle (for chaining)
|
|
1330
|
-
*/
|
|
1331
|
-
render() {
|
|
1332
|
-
const newElement = bw.createDOM(this.taco, options);
|
|
1333
|
-
this.element.replaceWith(newElement);
|
|
1334
|
-
this.element = newElement;
|
|
1335
|
-
return this;
|
|
1336
|
-
},
|
|
1337
|
-
|
|
1338
|
-
/**
|
|
1339
|
-
* Called when a compiled prop value changes. Override to customize behavior.
|
|
1340
|
-
* Default implementation triggers a full re-render.
|
|
1341
|
-
* @param {string} key - Property name that changed
|
|
1342
|
-
* @param {*} newValue - New property value
|
|
1343
|
-
* @param {*} oldValue - Previous property value
|
|
1344
|
-
*/
|
|
1345
|
-
onPropChange(_key, _newValue, _oldValue) {
|
|
1346
|
-
// Auto re-render on prop change by default
|
|
1347
|
-
this.render();
|
|
1348
|
-
},
|
|
1349
|
-
|
|
1350
|
-
// State management
|
|
1351
|
-
get state() {
|
|
1352
|
-
return this._state;
|
|
1353
|
-
},
|
|
1354
|
-
|
|
1355
|
-
set state(newState) {
|
|
1356
|
-
this._state = newState;
|
|
1357
|
-
this.render();
|
|
1358
|
-
},
|
|
1359
|
-
|
|
1360
|
-
/**
|
|
1361
|
-
* Merge state updates and re-render the component
|
|
1362
|
-
* @param {Object} updates - State properties to merge
|
|
1363
|
-
* @returns {Object} this handle (for chaining)
|
|
1364
|
-
*/
|
|
1365
|
-
setState(updates) {
|
|
1366
|
-
Object.assign(this._state, updates);
|
|
1367
|
-
this.render();
|
|
1368
|
-
return this;
|
|
1369
|
-
},
|
|
1370
|
-
|
|
1371
|
-
/**
|
|
1372
|
-
* Register a child component under a name for later retrieval
|
|
1373
|
-
* @param {string} name - Child name key
|
|
1374
|
-
* @param {Object} component - Child component handle
|
|
1375
|
-
* @returns {Object} this handle (for chaining)
|
|
1376
|
-
*/
|
|
1377
|
-
addChild(name, component) {
|
|
1378
|
-
this._children[name] = component;
|
|
1379
|
-
return this;
|
|
1380
|
-
},
|
|
1381
|
-
|
|
1382
|
-
/**
|
|
1383
|
-
* Retrieve a registered child component by name
|
|
1384
|
-
* @param {string} name - Child name key
|
|
1385
|
-
* @returns {Object|undefined} Child component handle
|
|
1386
|
-
*/
|
|
1387
|
-
getChild(name) {
|
|
1388
|
-
return this._children[name];
|
|
1389
|
-
},
|
|
1390
|
-
|
|
1391
|
-
/**
|
|
1392
|
-
* Destroy this component and all registered children
|
|
1393
|
-
*
|
|
1394
|
-
* Calls destroy() recursively on children, runs bw.cleanup(),
|
|
1395
|
-
* removes the element from DOM, and clears all internal references.
|
|
1396
|
-
*/
|
|
1397
|
-
destroy() {
|
|
1398
|
-
// Destroy children first
|
|
1399
|
-
Object.values(this._children).forEach(child => {
|
|
1400
|
-
if (child && child.destroy) child.destroy();
|
|
1401
|
-
});
|
|
1402
|
-
|
|
1403
|
-
// Clean up this component
|
|
1404
|
-
bw.cleanup(this.element);
|
|
1405
|
-
this.element.remove();
|
|
1406
|
-
|
|
1407
|
-
// Clear references
|
|
1408
|
-
this._children = {};
|
|
1409
|
-
this._props = {};
|
|
1410
|
-
this._state = {};
|
|
1411
|
-
this._compiledProps = null;
|
|
1412
|
-
}
|
|
1413
|
-
};
|
|
1414
|
-
|
|
1415
|
-
// Store handle reference on element
|
|
1416
|
-
element._bwHandle = handle;
|
|
1417
|
-
|
|
1418
|
-
return handle;
|
|
1210
|
+
* @example
|
|
1211
|
+
* var el = bw.mount('#app', bw.makeCarousel({ items: slides }));
|
|
1212
|
+
* el.bw.goToSlide(2);
|
|
1213
|
+
* el.bw.next();
|
|
1214
|
+
*/
|
|
1215
|
+
bw.mount = function(target, taco, options) {
|
|
1216
|
+
var container = _is(target, 'string') ? bw.$(target)[0] : target;
|
|
1217
|
+
if (!container) {
|
|
1218
|
+
_cw('bw.mount: target not found');
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
bw.cleanup(container);
|
|
1222
|
+
container.innerHTML = '';
|
|
1223
|
+
var el = bw.createDOM(taco, options || {});
|
|
1224
|
+
container.appendChild(el);
|
|
1225
|
+
return el;
|
|
1419
1226
|
};
|
|
1420
1227
|
|
|
1421
1228
|
/**
|
|
@@ -1436,34 +1243,29 @@ bw.renderComponent = function(taco, options = {}) {
|
|
|
1436
1243
|
bw.cleanup = function(element) {
|
|
1437
1244
|
if (!bw._isBrowser || !element) return;
|
|
1438
1245
|
|
|
1439
|
-
// Deregister UUID classes from node cache
|
|
1440
|
-
// Covers elements that have UUID but no data-bw_id
|
|
1441
|
-
var selfUuidMatch = element.className && element.className.match(_UUID_RE);
|
|
1442
|
-
if (selfUuidMatch) delete bw._nodeMap[selfUuidMatch[0]];
|
|
1246
|
+
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
1443
1247
|
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
1444
1248
|
uuidEls.forEach(function(uel) {
|
|
1445
1249
|
var m = uel.className && uel.className.match(_UUID_RE);
|
|
1446
1250
|
if (m) delete bw._nodeMap[m[0]];
|
|
1447
1251
|
});
|
|
1448
1252
|
|
|
1449
|
-
// Find all elements
|
|
1450
|
-
const elements = element.querySelectorAll('
|
|
1253
|
+
// Find all lifecycle-managed elements (have bw_lc marker class)
|
|
1254
|
+
const elements = element.querySelectorAll('.' + _BW_LC);
|
|
1451
1255
|
|
|
1452
1256
|
elements.forEach(el => {
|
|
1453
|
-
|
|
1454
|
-
const callback = bw._unmountCallbacks.get(id);
|
|
1455
|
-
|
|
1456
|
-
if (callback) {
|
|
1457
|
-
callback();
|
|
1458
|
-
bw._unmountCallbacks.delete(id);
|
|
1459
|
-
}
|
|
1257
|
+
var uuid = bw.getUUID(el);
|
|
1460
1258
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1259
|
+
if (uuid) {
|
|
1260
|
+
const callback = bw._unmountCallbacks.get(uuid);
|
|
1261
|
+
if (callback) {
|
|
1262
|
+
callback();
|
|
1263
|
+
bw._unmountCallbacks.delete(uuid);
|
|
1264
|
+
}
|
|
1463
1265
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1266
|
+
// Deregister from node cache
|
|
1267
|
+
bw._deregisterNode(el, uuid);
|
|
1268
|
+
}
|
|
1467
1269
|
|
|
1468
1270
|
// Clean up pub/sub subscriptions tied to this element
|
|
1469
1271
|
if (el._bw_subs) {
|
|
@@ -1478,20 +1280,18 @@ bw.cleanup = function(element) {
|
|
|
1478
1280
|
});
|
|
1479
1281
|
|
|
1480
1282
|
// Check element itself
|
|
1481
|
-
|
|
1482
|
-
if (
|
|
1483
|
-
|
|
1283
|
+
var selfUuid = bw.getUUID(element);
|
|
1284
|
+
if (selfUuid) {
|
|
1285
|
+
delete bw._nodeMap[selfUuid];
|
|
1286
|
+
|
|
1287
|
+
const callback = bw._unmountCallbacks.get(selfUuid);
|
|
1484
1288
|
if (callback) {
|
|
1485
1289
|
callback();
|
|
1486
|
-
bw._unmountCallbacks.delete(
|
|
1290
|
+
bw._unmountCallbacks.delete(selfUuid);
|
|
1487
1291
|
}
|
|
1488
1292
|
|
|
1489
1293
|
// Deregister from node cache
|
|
1490
|
-
bw._deregisterNode(element,
|
|
1491
|
-
|
|
1492
|
-
// Deregister UUID class from node cache
|
|
1493
|
-
var elemUuidMatch = element.className && element.className.match(_UUID_RE);
|
|
1494
|
-
if (elemUuidMatch) delete bw._nodeMap[elemUuidMatch[0]];
|
|
1294
|
+
bw._deregisterNode(element, selfUuid);
|
|
1495
1295
|
|
|
1496
1296
|
// Clean up pub/sub subscriptions tied to element itself
|
|
1497
1297
|
if (element._bw_subs) {
|
|
@@ -1502,11 +1302,11 @@ bw.cleanup = function(element) {
|
|
|
1502
1302
|
delete element._bw_render;
|
|
1503
1303
|
delete element._bw_refs;
|
|
1504
1304
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
element.
|
|
1509
|
-
delete element.
|
|
1305
|
+
} else {
|
|
1306
|
+
// No UUID on element itself, but still check for _bw_subs (from bw.sub())
|
|
1307
|
+
if (element._bw_subs) {
|
|
1308
|
+
element._bw_subs.forEach(function(unsub) { unsub(); });
|
|
1309
|
+
delete element._bw_subs;
|
|
1510
1310
|
}
|
|
1511
1311
|
}
|
|
1512
1312
|
};
|
|
@@ -1522,7 +1322,7 @@ bw.cleanup = function(element) {
|
|
|
1522
1322
|
* Calls `el._bw_render(el, state)` and emits `bw:statechange` so other
|
|
1523
1323
|
* components can react without tight coupling.
|
|
1524
1324
|
*
|
|
1525
|
-
* @param {string|Element} target - Element ID,
|
|
1325
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element
|
|
1526
1326
|
* @returns {Element|null} The element, or null if not found / no render function
|
|
1527
1327
|
* @category State Management
|
|
1528
1328
|
* @see bw.patch
|
|
@@ -1547,7 +1347,7 @@ bw.update = function(target) {
|
|
|
1547
1347
|
* Use `bw.patch()` for lightweight value updates (scores, labels, counters)
|
|
1548
1348
|
* and `bw.update()` for full structural re-renders.
|
|
1549
1349
|
*
|
|
1550
|
-
* @param {string|Element} id - Element ID,
|
|
1350
|
+
* @param {string|Element} id - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
1551
1351
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
1552
1352
|
* @param {string|Object} content - New text content, or TACO object to replace children
|
|
1553
1353
|
* @param {string} [attr] - If provided, sets this attribute instead of content
|
|
@@ -1622,7 +1422,7 @@ bw.patchAll = function(patches) {
|
|
|
1622
1422
|
* bubble by default so ancestor elements can listen. Use with `bw.on()` for
|
|
1623
1423
|
* DOM-scoped communication between components.
|
|
1624
1424
|
*
|
|
1625
|
-
* @param {string|Element} target - Element ID,
|
|
1425
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
1626
1426
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
1627
1427
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
1628
1428
|
* @param {*} [detail] - Data to pass with the event
|
|
@@ -1649,7 +1449,7 @@ bw.emit = function(target, eventName, detail) {
|
|
|
1649
1449
|
* is the first argument so you don't need to destructure `e.detail`.
|
|
1650
1450
|
* Events bubble, so you can listen on an ancestor element.
|
|
1651
1451
|
*
|
|
1652
|
-
* @param {string|Element} target - Element ID,
|
|
1452
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
1653
1453
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
1654
1454
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
1655
1455
|
* @param {Function} handler - Called with (detail, event)
|
|
@@ -1747,10 +1547,12 @@ bw.sub = function(topic, handler, el) {
|
|
|
1747
1547
|
if (el) {
|
|
1748
1548
|
if (!el._bw_subs) el._bw_subs = [];
|
|
1749
1549
|
el._bw_subs.push(unsub);
|
|
1750
|
-
// Ensure element has
|
|
1751
|
-
if (!
|
|
1752
|
-
|
|
1753
|
-
|
|
1550
|
+
// Ensure element has UUID + bw_lc so bw.cleanup() finds it
|
|
1551
|
+
if (!bw.getUUID(el)) {
|
|
1552
|
+
el.classList.add(bw.uuid('uuid'));
|
|
1553
|
+
}
|
|
1554
|
+
if (!el.classList.contains(_BW_LC)) {
|
|
1555
|
+
el.classList.add(_BW_LC);
|
|
1754
1556
|
}
|
|
1755
1557
|
}
|
|
1756
1558
|
|
|
@@ -1972,1087 +1774,46 @@ bw._resolveTemplate = function(str, state, compile) {
|
|
|
1972
1774
|
return result;
|
|
1973
1775
|
};
|
|
1974
1776
|
|
|
1975
|
-
/**
|
|
1976
|
-
* Extract top-level state keys that an expression depends on.
|
|
1977
|
-
* @param {string} expr - Expression string
|
|
1978
|
-
* @param {string[]} stateKeys - Declared state keys
|
|
1979
|
-
* @returns {string[]} Matching dependency keys
|
|
1980
|
-
* @private
|
|
1981
|
-
*/
|
|
1982
|
-
bw._extractDeps = function(expr, stateKeys) {
|
|
1983
|
-
var deps = [];
|
|
1984
|
-
for (var i = 0; i < stateKeys.length; i++) {
|
|
1985
|
-
var key = stateKeys[i];
|
|
1986
|
-
// Match word boundary: key must be preceded by start/non-word and followed by non-word/end
|
|
1987
|
-
var re = new RegExp('(?:^|[^\\w$.])' + key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:[^\\w$]|$)');
|
|
1988
|
-
if (re.test(expr) || expr === key || expr.indexOf(key + '.') === 0) {
|
|
1989
|
-
deps.push(key);
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
return deps;
|
|
1993
|
-
};
|
|
1994
|
-
|
|
1995
1777
|
// ===================================================================================
|
|
1996
|
-
//
|
|
1778
|
+
// Deprecation stubs for removed ComponentHandle APIs (v2.0.19)
|
|
1997
1779
|
// ===================================================================================
|
|
1998
1780
|
|
|
1999
|
-
bw.
|
|
2000
|
-
bw.
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
*/
|
|
2006
|
-
bw._scheduleFlush = function() {
|
|
2007
|
-
if (bw._flushScheduled) return;
|
|
2008
|
-
bw._flushScheduled = true;
|
|
2009
|
-
if (typeof Promise !== 'undefined') {
|
|
2010
|
-
Promise.resolve().then(bw._doFlush);
|
|
2011
|
-
} else {
|
|
2012
|
-
setTimeout(bw._doFlush, 0);
|
|
2013
|
-
}
|
|
2014
|
-
};
|
|
2015
|
-
|
|
2016
|
-
/**
|
|
2017
|
-
* Flush all dirty components. Deduplicates by _bwId.
|
|
2018
|
-
* @private
|
|
2019
|
-
*/
|
|
2020
|
-
bw._doFlush = function() {
|
|
2021
|
-
bw._flushScheduled = false;
|
|
2022
|
-
var queue = bw._dirtyComponents.slice();
|
|
2023
|
-
bw._dirtyComponents = [];
|
|
2024
|
-
// Deduplicate by _bwId
|
|
2025
|
-
var seen = {};
|
|
2026
|
-
for (var i = 0; i < queue.length; i++) {
|
|
2027
|
-
var comp = queue[i];
|
|
2028
|
-
if (!seen[comp._bwId]) {
|
|
2029
|
-
seen[comp._bwId] = true;
|
|
2030
|
-
comp._flush();
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
};
|
|
1781
|
+
bw._extractDeps = undefined;
|
|
1782
|
+
bw._dirtyComponents = undefined;
|
|
1783
|
+
bw._flushScheduled = undefined;
|
|
1784
|
+
bw._scheduleFlush = undefined;
|
|
1785
|
+
bw._doFlush = undefined;
|
|
1786
|
+
bw._ComponentHandle = undefined;
|
|
2034
1787
|
|
|
2035
1788
|
/**
|
|
2036
|
-
*
|
|
2037
|
-
*
|
|
2038
|
-
*
|
|
1789
|
+
* No-op flush (ComponentHandle removed in v2.0.19).
|
|
1790
|
+
* Kept as no-op for backward compatibility.
|
|
2039
1791
|
* @category Component
|
|
2040
1792
|
*/
|
|
2041
|
-
bw.flush = function() {
|
|
2042
|
-
bw._doFlush();
|
|
2043
|
-
};
|
|
2044
|
-
|
|
2045
|
-
// ===================================================================================
|
|
2046
|
-
// ComponentHandle — unified reactive component (Phase 1)
|
|
2047
|
-
// ===================================================================================
|
|
1793
|
+
bw.flush = function() {};
|
|
2048
1794
|
|
|
2049
|
-
/**
|
|
2050
|
-
* ComponentHandle constructor.
|
|
2051
|
-
* Wraps a TACO definition with reactive state, lifecycle hooks,
|
|
2052
|
-
* template bindings, and named actions.
|
|
2053
|
-
*
|
|
2054
|
-
* @param {Object} taco - TACO definition {t, a, c, o}
|
|
2055
|
-
* @constructor
|
|
2056
|
-
* @private
|
|
2057
|
-
*/
|
|
2058
|
-
function ComponentHandle(taco) {
|
|
2059
|
-
this._bwComponent = true; // duck-type marker
|
|
2060
|
-
this._bwId = bw.uuid('comp');
|
|
2061
|
-
this.taco = taco;
|
|
2062
|
-
this.element = null;
|
|
2063
|
-
this.mounted = false;
|
|
2064
|
-
|
|
2065
|
-
var o = taco.o || {};
|
|
2066
|
-
// Copy initial state
|
|
2067
|
-
this._state = {};
|
|
2068
|
-
if (o.state) {
|
|
2069
|
-
for (var k in o.state) {
|
|
2070
|
-
if (_hop.call(o.state, k)) {
|
|
2071
|
-
this._state[k] = o.state[k];
|
|
2072
|
-
}
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
// Copy actions
|
|
2076
|
-
this._actions = {};
|
|
2077
|
-
if (o.actions) {
|
|
2078
|
-
for (var k2 in o.actions) {
|
|
2079
|
-
if (_hop.call(o.actions, k2)) {
|
|
2080
|
-
this._actions[k2] = o.actions[k2];
|
|
2081
|
-
}
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
// Promote o.methods to handle API (MFC/Qt pattern: component owns its methods)
|
|
2085
|
-
this._methods = {};
|
|
2086
|
-
if (o.methods) {
|
|
2087
|
-
var self = this;
|
|
2088
|
-
for (var k3 in o.methods) {
|
|
2089
|
-
if (_hop.call(o.methods, k3)) {
|
|
2090
|
-
this._methods[k3] = o.methods[k3];
|
|
2091
|
-
(function(methodName, methodFn) {
|
|
2092
|
-
self[methodName] = function() {
|
|
2093
|
-
var args = [self].concat(Array.prototype.slice.call(arguments));
|
|
2094
|
-
return methodFn.apply(null, args);
|
|
2095
|
-
};
|
|
2096
|
-
})(k3, o.methods[k3]);
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
// User tag for addressing via bw.message()
|
|
2101
|
-
this._userTag = null;
|
|
2102
|
-
// Lifecycle hooks
|
|
2103
|
-
this._hooks = {
|
|
2104
|
-
willMount: o.willMount || null,
|
|
2105
|
-
mounted: o.mounted || null,
|
|
2106
|
-
willUpdate: o.willUpdate || null,
|
|
2107
|
-
onUpdate: o.onUpdate || o.updated || null,
|
|
2108
|
-
unmount: o.unmount || null,
|
|
2109
|
-
willDestroy: o.willDestroy || null
|
|
2110
|
-
};
|
|
2111
|
-
// Binding tracking
|
|
2112
|
-
this._bindings = [];
|
|
2113
|
-
this._dirtyKeys = {};
|
|
2114
|
-
this._scheduled = false;
|
|
2115
|
-
this._subs = [];
|
|
2116
|
-
this._eventListeners = [];
|
|
2117
|
-
this._registeredActions = [];
|
|
2118
|
-
this._prevValues = {};
|
|
2119
|
-
this._compile = !!o.compile;
|
|
2120
|
-
this._bw_refs = {};
|
|
2121
|
-
this._refCounter = 0;
|
|
2122
|
-
// Child component ownership (Bug #5)
|
|
2123
|
-
this._children = [];
|
|
2124
|
-
this._parent = null;
|
|
2125
|
-
// Factory metadata for BCCL rebuild (Bug #6)
|
|
2126
|
-
this._factory = taco._bwFactory || null;
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
// Short alias for ComponentHandle.prototype (see alias block at top of file).
|
|
2130
|
-
// 28 method definitions × 25 chars = ~700B raw savings in minified output.
|
|
2131
|
-
var _chp = ComponentHandle.prototype;
|
|
2132
|
-
|
|
2133
|
-
// ── State Methods ──
|
|
2134
|
-
|
|
2135
|
-
/**
|
|
2136
|
-
* Get a state value. Dot-path supported: `get('user.name')`
|
|
2137
|
-
*/
|
|
2138
|
-
_chp.get = function(key) {
|
|
2139
|
-
return bw._evaluatePath(this._state, key);
|
|
2140
|
-
};
|
|
2141
|
-
|
|
2142
|
-
/**
|
|
2143
|
-
* Set a state value. Dot-path supported. Schedules re-render.
|
|
2144
|
-
* @param {string} key - State key (dot-path)
|
|
2145
|
-
* @param {*} value - New value
|
|
2146
|
-
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
2147
|
-
*/
|
|
2148
|
-
_chp.set = function(key, value, opts) {
|
|
2149
|
-
// Dot-path set
|
|
2150
|
-
var parts = key.split('.');
|
|
2151
|
-
var obj = this._state;
|
|
2152
|
-
for (var i = 0; i < parts.length - 1; i++) {
|
|
2153
|
-
if (!_is(obj[parts[i]], 'object')) {
|
|
2154
|
-
if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
|
|
2155
|
-
obj[parts[i]] = {};
|
|
2156
|
-
}
|
|
2157
|
-
obj = obj[parts[i]];
|
|
2158
|
-
}
|
|
2159
|
-
obj[parts[parts.length - 1]] = value;
|
|
2160
|
-
// Mark top-level key dirty
|
|
2161
|
-
this._dirtyKeys[parts[0]] = true;
|
|
2162
|
-
if (this.mounted) {
|
|
2163
|
-
if (opts && opts.sync) {
|
|
2164
|
-
this._flush();
|
|
2165
|
-
} else {
|
|
2166
|
-
this._scheduleDirty();
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
};
|
|
2170
|
-
|
|
2171
|
-
/**
|
|
2172
|
-
* Get a shallow clone of the full state.
|
|
2173
|
-
*/
|
|
2174
|
-
_chp.getState = function() {
|
|
2175
|
-
var clone = {};
|
|
2176
|
-
for (var k in this._state) {
|
|
2177
|
-
if (_hop.call(this._state, k)) {
|
|
2178
|
-
clone[k] = this._state[k];
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
return clone;
|
|
2182
|
-
};
|
|
2183
|
-
|
|
2184
|
-
/**
|
|
2185
|
-
* Merge multiple state keys. Schedules re-render.
|
|
2186
|
-
* @param {Object} updates - Key-value pairs to merge
|
|
2187
|
-
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
2188
|
-
*/
|
|
2189
|
-
_chp.setState = function(updates, opts) {
|
|
2190
|
-
for (var k in updates) {
|
|
2191
|
-
if (_hop.call(updates, k)) {
|
|
2192
|
-
this._state[k] = updates[k];
|
|
2193
|
-
this._dirtyKeys[k] = true;
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
2196
|
-
if (this.mounted) {
|
|
2197
|
-
if (opts && opts.sync) {
|
|
2198
|
-
this._flush();
|
|
2199
|
-
} else {
|
|
2200
|
-
this._scheduleDirty();
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
};
|
|
2204
|
-
|
|
2205
|
-
/**
|
|
2206
|
-
* Push a value onto an array in state. Clones the array.
|
|
2207
|
-
*/
|
|
2208
|
-
_chp.push = function(key, val) {
|
|
2209
|
-
var arr = this.get(key);
|
|
2210
|
-
var newArr = _isA(arr) ? arr.slice() : [];
|
|
2211
|
-
newArr.push(val);
|
|
2212
|
-
this.set(key, newArr);
|
|
2213
|
-
};
|
|
2214
|
-
|
|
2215
|
-
/**
|
|
2216
|
-
* Splice an array in state. Clones the array.
|
|
2217
|
-
*/
|
|
2218
|
-
_chp.splice = function(key, start, deleteCount) {
|
|
2219
|
-
var arr = this.get(key);
|
|
2220
|
-
var newArr = _isA(arr) ? arr.slice() : [];
|
|
2221
|
-
var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
|
|
2222
|
-
Array.prototype.splice.apply(newArr, args);
|
|
2223
|
-
this.set(key, newArr);
|
|
2224
|
-
};
|
|
2225
|
-
|
|
2226
|
-
// ── Scheduling ──
|
|
2227
|
-
|
|
2228
|
-
_chp._scheduleDirty = function() {
|
|
2229
|
-
if (!this._scheduled) {
|
|
2230
|
-
this._scheduled = true;
|
|
2231
|
-
bw._dirtyComponents.push(this);
|
|
2232
|
-
bw._scheduleFlush();
|
|
2233
|
-
}
|
|
2234
|
-
};
|
|
2235
|
-
|
|
2236
|
-
// ── Binding Compilation ──
|
|
2237
|
-
|
|
2238
|
-
/**
|
|
2239
|
-
* Walk the TACO tree and extract ${expr} bindings.
|
|
2240
|
-
* Creates binding descriptors with refIds for targeted DOM updates.
|
|
2241
|
-
* @private
|
|
2242
|
-
*/
|
|
2243
|
-
_chp._compileBindings = function() {
|
|
2244
|
-
this._bindings = [];
|
|
2245
|
-
this._refCounter = 0;
|
|
2246
|
-
var stateKeys = _keys(this._state);
|
|
2247
|
-
var self = this;
|
|
2248
|
-
|
|
2249
|
-
function walkTaco(taco, path) {
|
|
2250
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
2251
|
-
|
|
2252
|
-
// Check content for bindings
|
|
2253
|
-
if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
|
|
2254
|
-
var refId = 'bw_ref_' + self._refCounter++;
|
|
2255
|
-
var parsed = bw._parseBindings(taco.c);
|
|
2256
|
-
var deps = [];
|
|
2257
|
-
for (var j = 0; j < parsed.length; j++) {
|
|
2258
|
-
deps = deps.concat(bw._extractDeps(parsed[j].expr, stateKeys));
|
|
2259
|
-
}
|
|
2260
|
-
self._bindings.push({
|
|
2261
|
-
expr: taco.c,
|
|
2262
|
-
type: 'content',
|
|
2263
|
-
refId: refId,
|
|
2264
|
-
deps: deps,
|
|
2265
|
-
template: taco.c
|
|
2266
|
-
});
|
|
2267
|
-
// Inject data-bw_ref on the TACO for createDOM to pick up
|
|
2268
|
-
if (!taco.a) taco.a = {};
|
|
2269
|
-
taco.a['data-bw_ref'] = refId;
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
// Check attributes for bindings
|
|
2273
|
-
if (taco.a) {
|
|
2274
|
-
for (var attrName in taco.a) {
|
|
2275
|
-
if (!_hop.call(taco.a, attrName)) continue;
|
|
2276
|
-
if (attrName === 'data-bw_ref') continue;
|
|
2277
|
-
var attrVal = taco.a[attrName];
|
|
2278
|
-
if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
|
|
2279
|
-
var refId2 = 'bw_ref_' + self._refCounter++;
|
|
2280
|
-
var parsed2 = bw._parseBindings(attrVal);
|
|
2281
|
-
var deps2 = [];
|
|
2282
|
-
for (var j2 = 0; j2 < parsed2.length; j2++) {
|
|
2283
|
-
deps2 = deps2.concat(bw._extractDeps(parsed2[j2].expr, stateKeys));
|
|
2284
|
-
}
|
|
2285
|
-
self._bindings.push({
|
|
2286
|
-
expr: attrVal,
|
|
2287
|
-
type: 'attribute',
|
|
2288
|
-
attrName: attrName,
|
|
2289
|
-
refId: refId2,
|
|
2290
|
-
deps: deps2,
|
|
2291
|
-
template: attrVal
|
|
2292
|
-
});
|
|
2293
|
-
if (!taco.a) taco.a = {};
|
|
2294
|
-
taco.a['data-bw_ref'] = taco.a['data-bw_ref'] || refId2;
|
|
2295
|
-
// If multiple attribute bindings on same element, store additional marker
|
|
2296
|
-
if (taco.a['data-bw_ref'] !== refId2) {
|
|
2297
|
-
taco.a['data-bw_ref_' + attrName] = refId2;
|
|
2298
|
-
}
|
|
2299
|
-
}
|
|
2300
|
-
}
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
// Recurse into children
|
|
2304
|
-
if (_isA(taco.c)) {
|
|
2305
|
-
for (var i = 0; i < taco.c.length; i++) {
|
|
2306
|
-
// Wrap string children with ${expr} in a span so patches target the span, not the parent
|
|
2307
|
-
if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
|
|
2308
|
-
var mixedRefId = 'bw_ref_' + self._refCounter++;
|
|
2309
|
-
var mixedParsed = bw._parseBindings(taco.c[i]);
|
|
2310
|
-
var mixedDeps = [];
|
|
2311
|
-
for (var mi = 0; mi < mixedParsed.length; mi++) {
|
|
2312
|
-
mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
|
|
2313
|
-
}
|
|
2314
|
-
self._bindings.push({
|
|
2315
|
-
expr: taco.c[i],
|
|
2316
|
-
type: 'content',
|
|
2317
|
-
refId: mixedRefId,
|
|
2318
|
-
deps: mixedDeps,
|
|
2319
|
-
template: taco.c[i]
|
|
2320
|
-
});
|
|
2321
|
-
// Replace string with a span wrapper so textContent targets the span only
|
|
2322
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
|
|
2323
|
-
}
|
|
2324
|
-
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
2325
|
-
walkTaco(taco.c[i], path.concat(i));
|
|
2326
|
-
}
|
|
2327
|
-
// Handle bw.when/bw.each markers
|
|
2328
|
-
if (taco.c[i] && taco.c[i]._bwWhen) {
|
|
2329
|
-
var whenRefId = 'bw_ref_' + self._refCounter++;
|
|
2330
|
-
var whenDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
|
|
2331
|
-
self._bindings.push({
|
|
2332
|
-
expr: taco.c[i].expr,
|
|
2333
|
-
type: 'structural',
|
|
2334
|
-
subtype: 'when',
|
|
2335
|
-
refId: whenRefId,
|
|
2336
|
-
deps: whenDeps,
|
|
2337
|
-
branches: taco.c[i].branches,
|
|
2338
|
-
index: i,
|
|
2339
|
-
parentPath: path
|
|
2340
|
-
});
|
|
2341
|
-
taco.c[i]._refId = whenRefId;
|
|
2342
|
-
}
|
|
2343
|
-
if (taco.c[i] && taco.c[i]._bwEach) {
|
|
2344
|
-
var eachRefId = 'bw_ref_' + self._refCounter++;
|
|
2345
|
-
var eachDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
|
|
2346
|
-
self._bindings.push({
|
|
2347
|
-
expr: taco.c[i].expr,
|
|
2348
|
-
type: 'structural',
|
|
2349
|
-
subtype: 'each',
|
|
2350
|
-
refId: eachRefId,
|
|
2351
|
-
deps: eachDeps,
|
|
2352
|
-
factory: taco.c[i].factory,
|
|
2353
|
-
index: i,
|
|
2354
|
-
parentPath: path
|
|
2355
|
-
});
|
|
2356
|
-
taco.c[i]._refId = eachRefId;
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
2360
|
-
walkTaco(taco.c, path.concat(0));
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
return taco;
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
walkTaco(this.taco, []);
|
|
2367
|
-
};
|
|
2368
|
-
|
|
2369
|
-
// ── DOM Reference Collection ──
|
|
2370
|
-
|
|
2371
|
-
/**
|
|
2372
|
-
* Build ref map from the live DOM after createDOM.
|
|
2373
|
-
* @private
|
|
2374
|
-
*/
|
|
2375
|
-
_chp._collectRefs = function() {
|
|
2376
|
-
this._bw_refs = {};
|
|
2377
|
-
if (!this.element) return;
|
|
2378
|
-
var els = this.element.querySelectorAll('[data-bw_ref]');
|
|
2379
|
-
for (var i = 0; i < els.length; i++) {
|
|
2380
|
-
this._bw_refs[els[i].getAttribute('data-bw_ref')] = els[i];
|
|
2381
|
-
}
|
|
2382
|
-
// Also check root element
|
|
2383
|
-
var rootRef = this.element.getAttribute && this.element.getAttribute('data-bw_ref');
|
|
2384
|
-
if (rootRef) {
|
|
2385
|
-
this._bw_refs[rootRef] = this.element;
|
|
2386
|
-
}
|
|
2387
|
-
};
|
|
2388
|
-
|
|
2389
|
-
// ── Lifecycle ──
|
|
2390
|
-
|
|
2391
|
-
/**
|
|
2392
|
-
* Mount the component into a parent DOM element.
|
|
2393
|
-
* Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
|
|
2394
|
-
* @param {Element} parentEl - DOM element to mount into
|
|
2395
|
-
*/
|
|
2396
|
-
_chp.mount = function(parentEl) {
|
|
2397
|
-
// willMount hook
|
|
2398
|
-
if (this._hooks.willMount) this._hooks.willMount(this);
|
|
2399
|
-
|
|
2400
|
-
// Save original TACO for re-renders (structural changes clone from this)
|
|
2401
|
-
if (!this._originalTaco) {
|
|
2402
|
-
this._originalTaco = this.taco;
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
// Deep-clone TACO so binding annotations don't mutate original.
|
|
2406
|
-
// Custom clone to preserve _bwWhen/_bwEach markers and their factory functions.
|
|
2407
|
-
this.taco = this._deepCloneTaco(this._originalTaco);
|
|
2408
|
-
|
|
2409
|
-
// Compile bindings (annotates TACO with data-bw_ref attributes)
|
|
2410
|
-
this._compileBindings();
|
|
2411
|
-
|
|
2412
|
-
// Prepare TACO: resolve initial binding values, evaluate when/each
|
|
2413
|
-
this._prepareTaco(this.taco);
|
|
2414
|
-
|
|
2415
|
-
// Register named actions in function registry
|
|
2416
|
-
var self = this;
|
|
2417
|
-
for (var actionName in this._actions) {
|
|
2418
|
-
if (_hop.call(this._actions, actionName)) {
|
|
2419
|
-
var registeredName = this._bwId + '_' + actionName;
|
|
2420
|
-
(function(aName) {
|
|
2421
|
-
bw.funcRegister(function(evt) {
|
|
2422
|
-
self._actions[aName](self, evt);
|
|
2423
|
-
}, registeredName);
|
|
2424
|
-
})(actionName);
|
|
2425
|
-
this._registeredActions.push(registeredName);
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
|
|
2429
|
-
// Wire action names in onclick etc. to dispatch strings
|
|
2430
|
-
this._wireActions(this.taco);
|
|
2431
|
-
|
|
2432
|
-
// Create DOM (strip o before createDOM to prevent double lifecycle)
|
|
2433
|
-
var tacoForDOM = this._tacoForDOM(this.taco);
|
|
2434
|
-
this.element = bw.createDOM(tacoForDOM);
|
|
2435
|
-
this.element._bwComponentHandle = this;
|
|
2436
|
-
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
2437
|
-
|
|
2438
|
-
// Restore o.render from original TACO (stripped by _tacoForDOM)
|
|
2439
|
-
if (this.taco.o && this.taco.o.render) {
|
|
2440
|
-
this.element._bw_render = this.taco.o.render;
|
|
2441
|
-
}
|
|
2442
|
-
if (this._userTag) {
|
|
2443
|
-
this.element.classList.add(this._userTag);
|
|
2444
|
-
}
|
|
2445
1795
|
|
|
2446
|
-
|
|
2447
|
-
|
|
1796
|
+
bw.when = function() { throw new Error('bw.when() removed in v2.0.19. Use conditional logic in o.render instead.'); };
|
|
1797
|
+
bw.each = function() { throw new Error('bw.each() removed in v2.0.19. Use array mapping in o.render instead.'); };
|
|
1798
|
+
bw.component = function() { throw new Error('bw.component() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
2448
1799
|
|
|
2449
|
-
// Collect refs from live DOM
|
|
2450
|
-
this._collectRefs();
|
|
2451
|
-
|
|
2452
|
-
// Resolve initial bindings and apply to DOM
|
|
2453
|
-
this._resolveAndApplyAll();
|
|
2454
|
-
|
|
2455
|
-
this.mounted = true;
|
|
2456
|
-
|
|
2457
|
-
// Scan for child ComponentHandles and link parent/child (Bug #5)
|
|
2458
|
-
var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
|
|
2459
|
-
for (var ci = 0; ci < childEls.length; ci++) {
|
|
2460
|
-
var ch = childEls[ci]._bwComponentHandle;
|
|
2461
|
-
if (ch && ch !== this && !ch._parent) {
|
|
2462
|
-
ch._parent = this;
|
|
2463
|
-
this._children.push(ch);
|
|
2464
|
-
}
|
|
2465
|
-
}
|
|
2466
|
-
|
|
2467
|
-
// mounted hook (backward compat: fn.length === 2 wraps (el, state))
|
|
2468
|
-
if (this._hooks.mounted) {
|
|
2469
|
-
if (this._hooks.mounted.length === 2) {
|
|
2470
|
-
this._hooks.mounted(this.element, this.getState());
|
|
2471
|
-
} else {
|
|
2472
|
-
this._hooks.mounted(this);
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
|
|
2476
|
-
// Invoke o.render on initial mount (if present)
|
|
2477
|
-
if (this.element._bw_render) {
|
|
2478
|
-
this.element._bw_render(this.element, this._state);
|
|
2479
|
-
}
|
|
2480
|
-
};
|
|
2481
|
-
|
|
2482
|
-
/**
|
|
2483
|
-
* Prepare TACO for initial render: resolve when/each markers.
|
|
2484
|
-
* @private
|
|
2485
|
-
*/
|
|
2486
|
-
_chp._prepareTaco = function(taco) {
|
|
2487
|
-
if (!_is(taco, 'object')) return;
|
|
2488
|
-
|
|
2489
|
-
if (_isA(taco.c)) {
|
|
2490
|
-
for (var i = taco.c.length - 1; i >= 0; i--) {
|
|
2491
|
-
var child = taco.c[i];
|
|
2492
|
-
if (child && child._bwWhen) {
|
|
2493
|
-
var exprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
2494
|
-
var val;
|
|
2495
|
-
if (this._compile) {
|
|
2496
|
-
try {
|
|
2497
|
-
val = (new Function('state', 'with(state){return (' + exprStr + ');}'))(this._state);
|
|
2498
|
-
} catch(e) { val = false; }
|
|
2499
|
-
} else {
|
|
2500
|
-
val = bw._evaluatePath(this._state, exprStr);
|
|
2501
|
-
}
|
|
2502
|
-
var branch = val ? child.branches[0] : (child.branches[1] || null);
|
|
2503
|
-
if (branch) {
|
|
2504
|
-
// Wrap in a container so we can track it
|
|
2505
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_when': child._refId, style: 'display:contents' }, c: branch };
|
|
2506
|
-
} else {
|
|
2507
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_when': child._refId, style: 'display:contents' }, c: '' };
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
if (child && child._bwEach) {
|
|
2511
|
-
var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
2512
|
-
var arr = bw._evaluatePath(this._state, eachExprStr);
|
|
2513
|
-
var items = [];
|
|
2514
|
-
if (_isA(arr)) {
|
|
2515
|
-
for (var j = 0; j < arr.length; j++) {
|
|
2516
|
-
items.push(child.factory(arr[j], j));
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2519
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
|
|
2520
|
-
}
|
|
2521
|
-
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
2522
|
-
this._prepareTaco(taco.c[i]);
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
2526
|
-
this._prepareTaco(taco.c);
|
|
2527
|
-
}
|
|
2528
|
-
};
|
|
2529
|
-
|
|
2530
|
-
/**
|
|
2531
|
-
* Wire action name strings (in onclick etc.) to dispatch function calls.
|
|
2532
|
-
* @private
|
|
2533
|
-
*/
|
|
2534
|
-
_chp._wireActions = function(taco) {
|
|
2535
|
-
if (!_is(taco, 'object') || !taco.t) return;
|
|
2536
|
-
if (taco.a) {
|
|
2537
|
-
for (var key in taco.a) {
|
|
2538
|
-
if (!_hop.call(taco.a, key)) continue;
|
|
2539
|
-
if (key.startsWith('on') && _is(taco.a[key], 'string')) {
|
|
2540
|
-
var actionName = taco.a[key];
|
|
2541
|
-
if (actionName in this._actions) {
|
|
2542
|
-
var registeredName = this._bwId + '_' + actionName;
|
|
2543
|
-
// Replace string with actual function for createDOM event binding
|
|
2544
|
-
(function(rName) {
|
|
2545
|
-
taco.a[key] = function(evt) {
|
|
2546
|
-
bw.funcGetById(rName)(evt);
|
|
2547
|
-
};
|
|
2548
|
-
})(registeredName);
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
if (_isA(taco.c)) {
|
|
2554
|
-
for (var i = 0; i < taco.c.length; i++) {
|
|
2555
|
-
this._wireActions(taco.c[i]);
|
|
2556
|
-
}
|
|
2557
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
2558
|
-
this._wireActions(taco.c);
|
|
2559
|
-
}
|
|
2560
|
-
};
|
|
2561
|
-
|
|
2562
|
-
/**
|
|
2563
|
-
* Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
|
|
2564
|
-
* @private
|
|
2565
|
-
*/
|
|
2566
|
-
_chp._deepCloneTaco = function(taco) {
|
|
2567
|
-
if (taco == null) return taco;
|
|
2568
|
-
// Preserve _bwWhen / _bwEach markers (contain functions)
|
|
2569
|
-
if (taco._bwWhen) {
|
|
2570
|
-
return { _bwWhen: true, expr: taco.expr, branches: [
|
|
2571
|
-
this._deepCloneTaco(taco.branches[0]),
|
|
2572
|
-
taco.branches[1] ? this._deepCloneTaco(taco.branches[1]) : null
|
|
2573
|
-
], _refId: taco._refId };
|
|
2574
|
-
}
|
|
2575
|
-
if (taco._bwEach) {
|
|
2576
|
-
return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
|
|
2577
|
-
}
|
|
2578
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
2579
|
-
var result = { t: taco.t };
|
|
2580
|
-
if (taco.a) {
|
|
2581
|
-
result.a = {};
|
|
2582
|
-
for (var k in taco.a) {
|
|
2583
|
-
if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
if (taco.c != null) {
|
|
2587
|
-
if (_isA(taco.c)) {
|
|
2588
|
-
result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
|
|
2589
|
-
} else if (_is(taco.c, 'object')) {
|
|
2590
|
-
result.c = this._deepCloneTaco(taco.c);
|
|
2591
|
-
} else {
|
|
2592
|
-
result.c = taco.c;
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
if (taco.o) result.o = taco.o; // Keep o reference (not deep-cloned; hooks are functions)
|
|
2596
|
-
return result;
|
|
2597
|
-
};
|
|
2598
|
-
|
|
2599
|
-
/**
|
|
2600
|
-
* Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
|
|
2601
|
-
* @private
|
|
2602
|
-
*/
|
|
2603
|
-
_chp._tacoForDOM = function(taco) {
|
|
2604
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
2605
|
-
var result = { t: taco.t };
|
|
2606
|
-
if (taco.a) result.a = taco.a;
|
|
2607
|
-
if (taco.c != null) {
|
|
2608
|
-
if (_isA(taco.c)) {
|
|
2609
|
-
result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
|
|
2610
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
2611
|
-
result.c = this._tacoForDOM(taco.c);
|
|
2612
|
-
} else {
|
|
2613
|
-
result.c = taco.c;
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
// Intentionally strip o (no mounted/unmount/state/render on sub-elements)
|
|
2617
|
-
if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
|
|
2618
|
-
_cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
|
|
2619
|
-
'>. Use onclick attribute or bw.component() for child interactivity.');
|
|
2620
|
-
}
|
|
2621
|
-
return result;
|
|
2622
|
-
};
|
|
2623
|
-
|
|
2624
|
-
/**
|
|
2625
|
-
* Unmount: remove from DOM, deactivate, preserve state for re-mount.
|
|
2626
|
-
*/
|
|
2627
|
-
_chp.unmount = function() {
|
|
2628
|
-
if (!this.mounted) return;
|
|
2629
|
-
|
|
2630
|
-
// unmount hook
|
|
2631
|
-
if (this._hooks.unmount) {
|
|
2632
|
-
this._hooks.unmount(this);
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
// Remove DOM event listeners
|
|
2636
|
-
for (var i = 0; i < this._eventListeners.length; i++) {
|
|
2637
|
-
var l = this._eventListeners[i];
|
|
2638
|
-
if (this.element) {
|
|
2639
|
-
this.element.removeEventListener(l.event, l.handler);
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
this._eventListeners = [];
|
|
2643
|
-
|
|
2644
|
-
// Unsubscribe pub/sub
|
|
2645
|
-
for (var j = 0; j < this._subs.length; j++) {
|
|
2646
|
-
this._subs[j]();
|
|
2647
|
-
}
|
|
2648
|
-
this._subs = [];
|
|
2649
|
-
|
|
2650
|
-
// Remove from DOM
|
|
2651
|
-
if (this.element && this.element.parentNode) {
|
|
2652
|
-
this.element.parentNode.removeChild(this.element);
|
|
2653
|
-
}
|
|
2654
|
-
|
|
2655
|
-
this.mounted = false;
|
|
2656
|
-
// State preserved — can re-mount
|
|
2657
|
-
};
|
|
2658
|
-
|
|
2659
|
-
/**
|
|
2660
|
-
* Destroy: unmount + clear state + unregister actions.
|
|
2661
|
-
*/
|
|
2662
|
-
_chp.destroy = function() {
|
|
2663
|
-
// willDestroy hook
|
|
2664
|
-
if (this._hooks.willDestroy) {
|
|
2665
|
-
this._hooks.willDestroy(this);
|
|
2666
|
-
}
|
|
2667
|
-
|
|
2668
|
-
// Cascade destroy to children depth-first (Bug #5)
|
|
2669
|
-
for (var ci = this._children.length - 1; ci >= 0; ci--) {
|
|
2670
|
-
this._children[ci].destroy();
|
|
2671
|
-
}
|
|
2672
|
-
this._children = [];
|
|
2673
|
-
if (this._parent) {
|
|
2674
|
-
var idx = this._parent._children.indexOf(this);
|
|
2675
|
-
if (idx >= 0) this._parent._children.splice(idx, 1);
|
|
2676
|
-
this._parent = null;
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
this.unmount();
|
|
2680
|
-
|
|
2681
|
-
// Unregister actions from function registry
|
|
2682
|
-
for (var i = 0; i < this._registeredActions.length; i++) {
|
|
2683
|
-
bw.funcUnregister(this._registeredActions[i]);
|
|
2684
|
-
}
|
|
2685
|
-
this._registeredActions = [];
|
|
2686
|
-
|
|
2687
|
-
// Clear state
|
|
2688
|
-
this._state = {};
|
|
2689
|
-
this._bindings = [];
|
|
2690
|
-
this._bw_refs = {};
|
|
2691
|
-
this._prevValues = {};
|
|
2692
|
-
this._dirtyKeys = {};
|
|
2693
|
-
if (this.element) {
|
|
2694
|
-
delete this.element._bwComponentHandle;
|
|
2695
|
-
this.element = null;
|
|
2696
|
-
}
|
|
2697
|
-
};
|
|
2698
|
-
|
|
2699
|
-
// ── Flush & Binding Resolution ──
|
|
2700
|
-
|
|
2701
|
-
/**
|
|
2702
|
-
* Flush dirty state: resolve changed bindings and apply to DOM.
|
|
2703
|
-
* @private
|
|
2704
|
-
*/
|
|
2705
|
-
_chp._flush = function() {
|
|
2706
|
-
this._scheduled = false;
|
|
2707
|
-
var changedKeys = _keys(this._dirtyKeys);
|
|
2708
|
-
this._dirtyKeys = {};
|
|
2709
|
-
if (changedKeys.length === 0 || !this.mounted) return;
|
|
2710
|
-
|
|
2711
|
-
// Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
|
|
2712
|
-
// rebuild the TACO from the factory with merged state (Bug #6)
|
|
2713
|
-
if (this._factory) {
|
|
2714
|
-
var rebuildNeeded = false;
|
|
2715
|
-
for (var fi = 0; fi < changedKeys.length; fi++) {
|
|
2716
|
-
if (_hop.call(this._factory.props, changedKeys[fi])) {
|
|
2717
|
-
rebuildNeeded = true; break;
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
if (rebuildNeeded) {
|
|
2721
|
-
var merged = {};
|
|
2722
|
-
for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
|
|
2723
|
-
for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
|
|
2724
|
-
this._factory.props = merged;
|
|
2725
|
-
var newTaco = bw.make(this._factory.type, merged);
|
|
2726
|
-
newTaco._bwFactory = this._factory;
|
|
2727
|
-
this.taco = newTaco;
|
|
2728
|
-
this._originalTaco = this._deepCloneTaco(newTaco);
|
|
2729
|
-
this._render();
|
|
2730
|
-
if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
|
|
2731
|
-
return;
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
// willUpdate hook
|
|
2736
|
-
if (this._hooks.willUpdate) {
|
|
2737
|
-
this._hooks.willUpdate(this, changedKeys);
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
// Check if any structural bindings are affected
|
|
2741
|
-
var needsFullRender = false;
|
|
2742
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
2743
|
-
var b = this._bindings[i];
|
|
2744
|
-
if (b.type === 'structural') {
|
|
2745
|
-
for (var j = 0; j < b.deps.length; j++) {
|
|
2746
|
-
if (changedKeys.indexOf(b.deps[j]) >= 0) {
|
|
2747
|
-
needsFullRender = true;
|
|
2748
|
-
break;
|
|
2749
|
-
}
|
|
2750
|
-
}
|
|
2751
|
-
if (needsFullRender) break;
|
|
2752
|
-
}
|
|
2753
|
-
}
|
|
2754
|
-
|
|
2755
|
-
if (needsFullRender) {
|
|
2756
|
-
this._render();
|
|
2757
|
-
} else {
|
|
2758
|
-
var patches = this._resolveBindings(changedKeys);
|
|
2759
|
-
this._applyPatches(patches);
|
|
2760
|
-
}
|
|
2761
|
-
|
|
2762
|
-
// onUpdate hook
|
|
2763
|
-
if (this._hooks.onUpdate) {
|
|
2764
|
-
this._hooks.onUpdate(this, changedKeys);
|
|
2765
|
-
}
|
|
2766
|
-
};
|
|
2767
|
-
|
|
2768
|
-
/**
|
|
2769
|
-
* Resolve bindings whose deps intersect with changedKeys.
|
|
2770
|
-
* Returns list of patches to apply.
|
|
2771
|
-
* @private
|
|
2772
|
-
*/
|
|
2773
|
-
_chp._resolveBindings = function(changedKeys) {
|
|
2774
|
-
var patches = [];
|
|
2775
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
2776
|
-
var b = this._bindings[i];
|
|
2777
|
-
if (b.type === 'structural') continue;
|
|
2778
|
-
|
|
2779
|
-
// Check if any dep matches
|
|
2780
|
-
var affected = false;
|
|
2781
|
-
for (var j = 0; j < b.deps.length; j++) {
|
|
2782
|
-
if (changedKeys.indexOf(b.deps[j]) >= 0) {
|
|
2783
|
-
affected = true;
|
|
2784
|
-
break;
|
|
2785
|
-
}
|
|
2786
|
-
}
|
|
2787
|
-
if (!affected) continue;
|
|
2788
|
-
|
|
2789
|
-
// Evaluate
|
|
2790
|
-
var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
|
|
2791
|
-
var prevKey = b.refId + '_' + (b.attrName || 'content');
|
|
2792
|
-
if (this._prevValues[prevKey] !== newVal) {
|
|
2793
|
-
this._prevValues[prevKey] = newVal;
|
|
2794
|
-
patches.push({
|
|
2795
|
-
refId: b.refId,
|
|
2796
|
-
type: b.type,
|
|
2797
|
-
attrName: b.attrName,
|
|
2798
|
-
value: newVal
|
|
2799
|
-
});
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
return patches;
|
|
2803
|
-
};
|
|
2804
|
-
|
|
2805
|
-
/**
|
|
2806
|
-
* Apply patches to DOM.
|
|
2807
|
-
* @private
|
|
2808
|
-
*/
|
|
2809
|
-
_chp._applyPatches = function(patches) {
|
|
2810
|
-
for (var i = 0; i < patches.length; i++) {
|
|
2811
|
-
var p = patches[i];
|
|
2812
|
-
var el = this._bw_refs[p.refId];
|
|
2813
|
-
if (!el) {
|
|
2814
|
-
if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
|
|
2815
|
-
continue;
|
|
2816
|
-
}
|
|
2817
|
-
if (p.type === 'content') {
|
|
2818
|
-
el.textContent = p.value;
|
|
2819
|
-
} else if (p.type === 'attribute') {
|
|
2820
|
-
if (p.attrName === 'class') {
|
|
2821
|
-
el.className = p.value;
|
|
2822
|
-
} else {
|
|
2823
|
-
el.setAttribute(p.attrName, p.value);
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
2826
|
-
}
|
|
2827
|
-
};
|
|
2828
|
-
|
|
2829
|
-
/**
|
|
2830
|
-
* Resolve all bindings and apply (used for initial render).
|
|
2831
|
-
* @private
|
|
2832
|
-
*/
|
|
2833
|
-
_chp._resolveAndApplyAll = function() {
|
|
2834
|
-
var patches = [];
|
|
2835
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
2836
|
-
var b = this._bindings[i];
|
|
2837
|
-
if (b.type === 'structural') continue;
|
|
2838
|
-
|
|
2839
|
-
var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
|
|
2840
|
-
var prevKey = b.refId + '_' + (b.attrName || 'content');
|
|
2841
|
-
this._prevValues[prevKey] = newVal;
|
|
2842
|
-
patches.push({
|
|
2843
|
-
refId: b.refId,
|
|
2844
|
-
type: b.type,
|
|
2845
|
-
attrName: b.attrName,
|
|
2846
|
-
value: newVal
|
|
2847
|
-
});
|
|
2848
|
-
}
|
|
2849
|
-
this._applyPatches(patches);
|
|
2850
|
-
};
|
|
2851
|
-
|
|
2852
|
-
/**
|
|
2853
|
-
* Full re-render for structural changes (when/each branch switches).
|
|
2854
|
-
* @private
|
|
2855
|
-
*/
|
|
2856
|
-
_chp._render = function() {
|
|
2857
|
-
if (!this.element || !this.element.parentNode) return;
|
|
2858
|
-
var parent = this.element.parentNode;
|
|
2859
|
-
var nextSibling = this.element.nextSibling;
|
|
2860
|
-
|
|
2861
|
-
// Remove old DOM
|
|
2862
|
-
parent.removeChild(this.element);
|
|
2863
|
-
|
|
2864
|
-
// Re-prepare TACO with current state (deep clone preserving functions)
|
|
2865
|
-
this.taco = this._deepCloneTaco(this._originalTaco || this.taco);
|
|
2866
|
-
|
|
2867
|
-
// Re-compile bindings and prepare
|
|
2868
|
-
this._compileBindings();
|
|
2869
|
-
this._prepareTaco(this.taco);
|
|
2870
|
-
this._wireActions(this.taco);
|
|
2871
|
-
|
|
2872
|
-
var tacoForDOM = this._tacoForDOM(this.taco);
|
|
2873
|
-
this.element = bw.createDOM(tacoForDOM);
|
|
2874
|
-
this.element._bwComponentHandle = this;
|
|
2875
|
-
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
2876
|
-
|
|
2877
|
-
// Re-insert at same position
|
|
2878
|
-
if (nextSibling) {
|
|
2879
|
-
parent.insertBefore(this.element, nextSibling);
|
|
2880
|
-
} else {
|
|
2881
|
-
parent.appendChild(this.element);
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
// Re-collect refs and apply all bindings
|
|
2885
|
-
this._collectRefs();
|
|
2886
|
-
this._resolveAndApplyAll();
|
|
2887
|
-
};
|
|
2888
|
-
|
|
2889
|
-
// ── Event & Pub/Sub Methods ──
|
|
2890
|
-
|
|
2891
|
-
/**
|
|
2892
|
-
* Add a DOM event listener on the component's root element.
|
|
2893
|
-
* @param {string} event - Event name (e.g., 'click')
|
|
2894
|
-
* @param {Function} handler - Event handler
|
|
2895
|
-
*/
|
|
2896
|
-
_chp.on = function(event, handler) {
|
|
2897
|
-
if (this.element) {
|
|
2898
|
-
this.element.addEventListener(event, handler);
|
|
2899
|
-
}
|
|
2900
|
-
this._eventListeners.push({ event: event, handler: handler });
|
|
2901
|
-
};
|
|
2902
|
-
|
|
2903
|
-
/**
|
|
2904
|
-
* Remove a DOM event listener.
|
|
2905
|
-
* @param {string} event - Event name
|
|
2906
|
-
* @param {Function} handler - Handler to remove
|
|
2907
|
-
*/
|
|
2908
|
-
_chp.off = function(event, handler) {
|
|
2909
|
-
if (this.element) {
|
|
2910
|
-
this.element.removeEventListener(event, handler);
|
|
2911
|
-
}
|
|
2912
|
-
this._eventListeners = this._eventListeners.filter(function(l) {
|
|
2913
|
-
return !(l.event === event && l.handler === handler);
|
|
2914
|
-
});
|
|
2915
|
-
};
|
|
2916
|
-
|
|
2917
|
-
/**
|
|
2918
|
-
* Subscribe to a pub/sub topic. Lifecycle-tied: auto-unsubs on destroy.
|
|
2919
|
-
* @param {string} topic - Topic name
|
|
2920
|
-
* @param {Function} handler - Handler function
|
|
2921
|
-
* @returns {Function} Unsubscribe function
|
|
2922
|
-
*/
|
|
2923
|
-
_chp.sub = function(topic, handler) {
|
|
2924
|
-
var unsub = bw.sub(topic, handler);
|
|
2925
|
-
this._subs.push(unsub);
|
|
2926
|
-
return unsub;
|
|
2927
|
-
};
|
|
2928
|
-
|
|
2929
|
-
/**
|
|
2930
|
-
* Call a named action.
|
|
2931
|
-
* @param {string} name - Action name
|
|
2932
|
-
* @param {...*} args - Arguments passed after comp
|
|
2933
|
-
*/
|
|
2934
|
-
_chp.action = function(name) {
|
|
2935
|
-
var fn = this._actions[name];
|
|
2936
|
-
if (!fn) {
|
|
2937
|
-
_cw('ComponentHandle.action: unknown action "' + name + '"');
|
|
2938
|
-
return;
|
|
2939
|
-
}
|
|
2940
|
-
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
|
|
2941
|
-
return fn.apply(null, args);
|
|
2942
|
-
};
|
|
2943
|
-
|
|
2944
|
-
/**
|
|
2945
|
-
* querySelector within the component's DOM.
|
|
2946
|
-
* @param {string} sel - CSS selector
|
|
2947
|
-
* @returns {Element|null}
|
|
2948
|
-
*/
|
|
2949
|
-
_chp.select = function(sel) {
|
|
2950
|
-
return this.element ? this.element.querySelector(sel) : null;
|
|
2951
|
-
};
|
|
2952
|
-
|
|
2953
|
-
/**
|
|
2954
|
-
* querySelectorAll within the component's DOM.
|
|
2955
|
-
* @param {string} sel - CSS selector
|
|
2956
|
-
* @returns {Element[]}
|
|
2957
|
-
*/
|
|
2958
|
-
_chp.selectAll = function(sel) {
|
|
2959
|
-
if (!this.element) return [];
|
|
2960
|
-
return Array.prototype.slice.call(this.element.querySelectorAll(sel));
|
|
2961
|
-
};
|
|
2962
|
-
|
|
2963
|
-
/**
|
|
2964
|
-
* Tag this component with a user-defined ID for addressing via bw.message().
|
|
2965
|
-
* The tag is added as a CSS class on the root element (DOM IS the registry).
|
|
2966
|
-
* @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
|
|
2967
|
-
* @returns {ComponentHandle} this (for chaining)
|
|
2968
|
-
*/
|
|
2969
|
-
_chp.userTag = function(tag) {
|
|
2970
|
-
this._userTag = tag;
|
|
2971
|
-
if (this.element) {
|
|
2972
|
-
this.element.classList.add(tag);
|
|
2973
|
-
}
|
|
2974
|
-
return this;
|
|
2975
|
-
};
|
|
2976
|
-
|
|
2977
|
-
// Expose ComponentHandle on bw (for testing and advanced use)
|
|
2978
|
-
bw._ComponentHandle = ComponentHandle;
|
|
2979
|
-
|
|
2980
|
-
// ===================================================================================
|
|
2981
|
-
// Control Flow Helpers
|
|
2982
|
-
// ===================================================================================
|
|
2983
|
-
|
|
2984
|
-
/**
|
|
2985
|
-
* Conditional rendering helper.
|
|
2986
|
-
* Returns a marker object that ComponentHandle detects during binding compilation.
|
|
2987
|
-
* In static contexts (bw.html with state), evaluates immediately.
|
|
2988
|
-
*
|
|
2989
|
-
* @param {string} expr - Expression string like '${loggedIn}'
|
|
2990
|
-
* @param {Object} tacoTrue - TACO to render when truthy
|
|
2991
|
-
* @param {Object} [tacoFalse] - TACO to render when falsy
|
|
2992
|
-
* @returns {Object} Marker object with _bwWhen flag
|
|
2993
|
-
* @category Component
|
|
2994
|
-
*/
|
|
2995
|
-
bw.when = function(expr, tacoTrue, tacoFalse) {
|
|
2996
|
-
return { _bwWhen: true, expr: expr, branches: [tacoTrue, tacoFalse || null] };
|
|
2997
|
-
};
|
|
2998
|
-
|
|
2999
|
-
/**
|
|
3000
|
-
* List rendering helper.
|
|
3001
|
-
* Returns a marker object that ComponentHandle detects during binding compilation.
|
|
3002
|
-
*
|
|
3003
|
-
* @param {string} expr - Expression string like '${items}'
|
|
3004
|
-
* @param {Function} fn - Factory function(item, index) returning TACO
|
|
3005
|
-
* @returns {Object} Marker object with _bwEach flag
|
|
3006
|
-
* @category Component
|
|
3007
|
-
*/
|
|
3008
|
-
bw.each = function(expr, fn) {
|
|
3009
|
-
return { _bwEach: true, expr: expr, factory: fn };
|
|
3010
|
-
};
|
|
3011
|
-
|
|
3012
|
-
// ===================================================================================
|
|
3013
|
-
// bw.component() — Factory for ComponentHandle
|
|
3014
|
-
// ===================================================================================
|
|
3015
|
-
|
|
3016
|
-
/**
|
|
3017
|
-
* Create a ComponentHandle from a TACO definition.
|
|
3018
|
-
* The returned handle has .get(), .set(), .mount(), .destroy(), etc.
|
|
3019
|
-
*
|
|
3020
|
-
* @param {Object} taco - TACO definition with {t, a, c, o}
|
|
3021
|
-
* @returns {ComponentHandle} Reactive component handle
|
|
3022
|
-
* @category Component
|
|
3023
|
-
* @see bw.DOM
|
|
3024
|
-
* @example
|
|
3025
|
-
* var counter = bw.component({
|
|
3026
|
-
* t: 'div', c: [{ t: 'h3', c: 'Count: ${count}' }],
|
|
3027
|
-
* o: { state: { count: 0 } }
|
|
3028
|
-
* });
|
|
3029
|
-
* bw.DOM('#app', counter);
|
|
3030
|
-
* counter.set('count', 42); // DOM auto-updates
|
|
3031
|
-
*/
|
|
3032
|
-
bw.component = function(taco) {
|
|
3033
|
-
return new ComponentHandle(taco);
|
|
3034
|
-
};
|
|
3035
1800
|
|
|
3036
1801
|
// ===================================================================================
|
|
3037
1802
|
// bw.message() — SendMessage() for the web
|
|
3038
1803
|
// ===================================================================================
|
|
3039
1804
|
|
|
3040
1805
|
/**
|
|
3041
|
-
* Dispatch a message to a component by UUID or
|
|
3042
|
-
* Finds the
|
|
3043
|
-
*
|
|
3044
|
-
* Win32 SendMessage(hwnd, msg, wParam, lParam).
|
|
1806
|
+
* Dispatch a message to a component by UUID, CSS class, or selector.
|
|
1807
|
+
* Finds the element, looks up el.bw, and calls the named method.
|
|
1808
|
+
* This is the bitwrench equivalent of Win32 SendMessage(hwnd, msg, wParam, lParam).
|
|
3045
1809
|
*
|
|
3046
|
-
* @param {string} target - Component UUID (bw_uuid_*),
|
|
3047
|
-
* @param {string} action - Method name to call on
|
|
1810
|
+
* @param {string} target - Component UUID (bw_uuid_*), CSS class, or selector
|
|
1811
|
+
* @param {string} action - Method name to call on el.bw
|
|
3048
1812
|
* @param {*} data - Data to pass to the method
|
|
3049
1813
|
* @returns {boolean} True if message was dispatched successfully
|
|
3050
1814
|
* @category Component
|
|
3051
1815
|
* @example
|
|
3052
|
-
*
|
|
3053
|
-
* myDash.userTag('dashboard_prod');
|
|
3054
|
-
* // Dispatch locally
|
|
3055
|
-
* bw.message('dashboard_prod', 'addAlert', { severity: 'warning', text: 'CPU spike' });
|
|
1816
|
+
* bw.message('my_carousel', 'goToSlide', 2);
|
|
3056
1817
|
* // Or from SSE handler:
|
|
3057
1818
|
* es.onmessage = function(e) {
|
|
3058
1819
|
* var msg = JSON.parse(e.data);
|
|
@@ -3060,23 +1821,13 @@ bw.component = function(taco) {
|
|
|
3060
1821
|
* };
|
|
3061
1822
|
*/
|
|
3062
1823
|
bw.message = function(target, action, data) {
|
|
3063
|
-
// Try bw._el() first (handles UUID class, nodeMap cache, getElementById)
|
|
3064
1824
|
var el = bw._el(target);
|
|
3065
|
-
|
|
3066
|
-
if (!el || !el.
|
|
3067
|
-
|
|
3068
|
-
}
|
|
3069
|
-
// Then try CSS class (user tag)
|
|
3070
|
-
if (!el || !el._bwComponentHandle) {
|
|
3071
|
-
el = bw.$('.' + target)[0];
|
|
3072
|
-
}
|
|
3073
|
-
if (!el || !el._bwComponentHandle) return false;
|
|
3074
|
-
var comp = el._bwComponentHandle;
|
|
3075
|
-
if (!_is(comp[action], 'function')) {
|
|
3076
|
-
_cw('bw.message: unknown action "' + action + '" on component ' + target);
|
|
1825
|
+
if (!el) el = bw.$('.' + target)[0];
|
|
1826
|
+
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
1827
|
+
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
3077
1828
|
return false;
|
|
3078
1829
|
}
|
|
3079
|
-
|
|
1830
|
+
el.bw[action](data);
|
|
3080
1831
|
return true;
|
|
3081
1832
|
};
|
|
3082
1833
|
|
|
@@ -3302,132 +2053,29 @@ bw.apply = function(msg) {
|
|
|
3302
2053
|
// ===================================================================================
|
|
3303
2054
|
|
|
3304
2055
|
/**
|
|
3305
|
-
* Inspect a
|
|
3306
|
-
* Works with DOM elements
|
|
3307
|
-
* Returns the ComponentHandle for console chaining.
|
|
2056
|
+
* Inspect a DOM element's bitwrench state, handle methods, and metadata.
|
|
2057
|
+
* Works with DOM elements or CSS selectors.
|
|
3308
2058
|
*
|
|
3309
|
-
* @param {string|Element
|
|
3310
|
-
* @returns {
|
|
2059
|
+
* @param {string|Element} target - Selector or DOM element
|
|
2060
|
+
* @returns {Element|null} The element, or null if not found
|
|
3311
2061
|
* @category Component
|
|
3312
2062
|
* @example
|
|
3313
|
-
*
|
|
2063
|
+
* bw.inspect('#my-carousel');
|
|
3314
2064
|
* bw.inspect($0);
|
|
3315
|
-
* // Or by selector:
|
|
3316
|
-
* var h = bw.inspect('#my-dashboard');
|
|
3317
|
-
* h.set('count', 99); // chain from returned handle
|
|
3318
2065
|
*/
|
|
3319
2066
|
bw.inspect = function(target) {
|
|
3320
|
-
var el = target;
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
el = bw.$(target)[0];
|
|
3328
|
-
}
|
|
3329
|
-
if (!el) {
|
|
3330
|
-
_cw('bw.inspect: element not found');
|
|
3331
|
-
return null;
|
|
3332
|
-
}
|
|
3333
|
-
comp = el._bwComponentHandle;
|
|
3334
|
-
}
|
|
3335
|
-
if (!comp) {
|
|
3336
|
-
_cl('bw.inspect: no ComponentHandle on this element');
|
|
3337
|
-
_cl(' Tag:', el.tagName);
|
|
3338
|
-
_cl(' Classes:', el.className);
|
|
3339
|
-
_cl(' _bw_state:', el._bw_state || '(none)');
|
|
3340
|
-
return null;
|
|
3341
|
-
}
|
|
3342
|
-
var deps = comp._bindings.reduce(function(s, b) {
|
|
3343
|
-
return s.concat(b.deps || []);
|
|
3344
|
-
}, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
|
|
3345
|
-
console.group('Component: ' + comp._bwId);
|
|
3346
|
-
_cl('State:', comp._state);
|
|
3347
|
-
_cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
|
|
3348
|
-
_cl('Methods:', _keys(comp._methods));
|
|
3349
|
-
_cl('Actions:', _keys(comp._actions));
|
|
3350
|
-
_cl('User tag:', comp._userTag || '(none)');
|
|
3351
|
-
_cl('Mounted:', comp.mounted);
|
|
3352
|
-
_cl('Element:', comp.element);
|
|
2067
|
+
var el = _is(target, 'string') ? bw.$(target)[0] : target;
|
|
2068
|
+
if (!el) { _cw('bw.inspect: element not found'); return null; }
|
|
2069
|
+
console.group('Element: ' + (bw.getUUID(el) || el.id || el.tagName));
|
|
2070
|
+
_cl('State:', el._bw_state || '(none)');
|
|
2071
|
+
_cl('Handle:', el.bw ? _keys(el.bw) : '(none)');
|
|
2072
|
+
_cl('Classes:', el.className);
|
|
2073
|
+
_cl('Refs:', el._bw_refs || '(none)');
|
|
3353
2074
|
console.groupEnd();
|
|
3354
|
-
return
|
|
2075
|
+
return el;
|
|
3355
2076
|
};
|
|
3356
2077
|
|
|
3357
|
-
|
|
3358
|
-
// bw.compile() — Pre-compile TACO into optimized factory
|
|
3359
|
-
// ===================================================================================
|
|
3360
|
-
|
|
3361
|
-
/**
|
|
3362
|
-
* Pre-compile a TACO definition into a factory function.
|
|
3363
|
-
* The factory produces ComponentHandles with pre-compiled binding evaluators.
|
|
3364
|
-
*
|
|
3365
|
-
* Phase 1: validates API surface. Template cloning optimization deferred.
|
|
3366
|
-
*
|
|
3367
|
-
* @param {Object} taco - TACO definition
|
|
3368
|
-
* @returns {Function} Factory function(initialState?) → ComponentHandle
|
|
3369
|
-
* @category Component
|
|
3370
|
-
*/
|
|
3371
|
-
bw.compile = function(taco) {
|
|
3372
|
-
// Pre-extract all binding expressions
|
|
3373
|
-
var precompiled = [];
|
|
3374
|
-
function walkExpressions(node) {
|
|
3375
|
-
if (!_is(node, 'object')) return;
|
|
3376
|
-
if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
|
|
3377
|
-
var parsed = bw._parseBindings(node.c);
|
|
3378
|
-
for (var i = 0; i < parsed.length; i++) {
|
|
3379
|
-
try {
|
|
3380
|
-
precompiled.push({
|
|
3381
|
-
expr: parsed[i].expr,
|
|
3382
|
-
fn: new Function('state', 'with(state){return (' + parsed[i].expr + ');}')
|
|
3383
|
-
});
|
|
3384
|
-
} catch(e) {
|
|
3385
|
-
precompiled.push({ expr: parsed[i].expr, fn: function() { return ''; } });
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
}
|
|
3389
|
-
if (node.a) {
|
|
3390
|
-
for (var key in node.a) {
|
|
3391
|
-
if (_hop.call(node.a, key)) {
|
|
3392
|
-
var v = node.a[key];
|
|
3393
|
-
if (_is(v, 'string') && v.indexOf('${') >= 0) {
|
|
3394
|
-
var parsed2 = bw._parseBindings(v);
|
|
3395
|
-
for (var j = 0; j < parsed2.length; j++) {
|
|
3396
|
-
try {
|
|
3397
|
-
precompiled.push({
|
|
3398
|
-
expr: parsed2[j].expr,
|
|
3399
|
-
fn: new Function('state', 'with(state){return (' + parsed2[j].expr + ');}')
|
|
3400
|
-
});
|
|
3401
|
-
} catch(e2) {
|
|
3402
|
-
precompiled.push({ expr: parsed2[j].expr, fn: function() { return ''; } });
|
|
3403
|
-
}
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
}
|
|
3407
|
-
}
|
|
3408
|
-
}
|
|
3409
|
-
if (_isA(node.c)) {
|
|
3410
|
-
for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
|
|
3411
|
-
} else if (_is(node.c, 'object') && node.c.t) {
|
|
3412
|
-
walkExpressions(node.c);
|
|
3413
|
-
}
|
|
3414
|
-
}
|
|
3415
|
-
walkExpressions(taco);
|
|
3416
|
-
|
|
3417
|
-
return function(initialState) {
|
|
3418
|
-
var handle = new ComponentHandle(taco);
|
|
3419
|
-
handle._compile = true;
|
|
3420
|
-
handle._precompiledBindings = precompiled;
|
|
3421
|
-
if (initialState) {
|
|
3422
|
-
for (var k in initialState) {
|
|
3423
|
-
if (_hop.call(initialState, k)) {
|
|
3424
|
-
handle._state[k] = initialState[k];
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
}
|
|
3428
|
-
return handle;
|
|
3429
|
-
};
|
|
3430
|
-
};
|
|
2078
|
+
bw.compile = function() { throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
3431
2079
|
|
|
3432
2080
|
/**
|
|
3433
2081
|
* Generate CSS from JavaScript objects.
|
|
@@ -4653,8 +3301,8 @@ bw.render = function(element, position, taco) {
|
|
|
4653
3301
|
};
|
|
4654
3302
|
}
|
|
4655
3303
|
|
|
4656
|
-
// Generate unique
|
|
4657
|
-
const componentId = taco.o?.id || bw.uuid();
|
|
3304
|
+
// Generate unique UUID class if not provided
|
|
3305
|
+
const componentId = taco.o?.id || bw.uuid('uuid');
|
|
4658
3306
|
|
|
4659
3307
|
// Create DOM element
|
|
4660
3308
|
let domElement;
|
|
@@ -4669,9 +3317,10 @@ bw.render = function(element, position, taco) {
|
|
|
4669
3317
|
};
|
|
4670
3318
|
}
|
|
4671
3319
|
|
|
4672
|
-
// Add component ID
|
|
4673
|
-
domElement.
|
|
4674
|
-
|
|
3320
|
+
// Add component ID as class + lifecycle marker
|
|
3321
|
+
domElement.classList.add(componentId);
|
|
3322
|
+
domElement.classList.add(_BW_LC);
|
|
3323
|
+
|
|
4675
3324
|
// Insert into DOM based on position
|
|
4676
3325
|
try {
|
|
4677
3326
|
switch(position) {
|
|
@@ -4745,7 +3394,8 @@ bw.render = function(element, position, taco) {
|
|
|
4745
3394
|
|
|
4746
3395
|
// Re-render
|
|
4747
3396
|
const newElement = bw.createDOM(this._taco);
|
|
4748
|
-
newElement.
|
|
3397
|
+
newElement.classList.add(componentId);
|
|
3398
|
+
newElement.classList.add(_BW_LC);
|
|
4749
3399
|
|
|
4750
3400
|
// Replace in DOM
|
|
4751
3401
|
parent.replaceChild(newElement, this.element);
|
|
@@ -4937,13 +3587,12 @@ bw.BCCL = components.BCCL;
|
|
|
4937
3587
|
// Variant class helper: bw.variantClass('primary') → 'bw_primary'
|
|
4938
3588
|
bw.variantClass = components.variantClass;
|
|
4939
3589
|
|
|
4940
|
-
// Create functions that return
|
|
3590
|
+
// Create functions that return DOM elements (createCard, createTable, etc.)
|
|
4941
3591
|
Object.entries(components).forEach(([name, fn]) => {
|
|
4942
3592
|
if (name.startsWith('make')) {
|
|
4943
|
-
const createName = 'create' + name.substring(4);
|
|
3593
|
+
const createName = 'create' + name.substring(4);
|
|
4944
3594
|
bw[createName] = function(props) {
|
|
4945
|
-
|
|
4946
|
-
return bw.renderComponent(taco);
|
|
3595
|
+
return bw.createDOM(fn(props));
|
|
4947
3596
|
};
|
|
4948
3597
|
}
|
|
4949
3598
|
});
|