bitwrench 2.0.25 → 2.0.30
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 +10 -4
- package/dist/bitwrench-bccl.cjs.js +1 -1
- package/dist/bitwrench-bccl.cjs.min.js +1 -1
- package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
- package/dist/bitwrench-bccl.esm.js +1 -1
- package/dist/bitwrench-bccl.esm.min.js +1 -1
- package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
- package/dist/bitwrench-bccl.umd.js +1 -1
- package/dist/bitwrench-bccl.umd.min.js +1 -1
- package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.cjs.min.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
- package/dist/bitwrench-debug.js +1 -1
- package/dist/bitwrench-debug.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +623 -155
- package/dist/bitwrench-lean.cjs.min.js +7 -7
- package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
- package/dist/bitwrench-lean.es5.js +650 -157
- package/dist/bitwrench-lean.es5.min.js +5 -5
- package/dist/bitwrench-lean.es5.min.js.gz +0 -0
- package/dist/bitwrench-lean.esm.js +623 -155
- package/dist/bitwrench-lean.esm.min.js +6 -6
- package/dist/bitwrench-lean.esm.min.js.gz +0 -0
- package/dist/bitwrench-lean.umd.js +623 -155
- package/dist/bitwrench-lean.umd.min.js +7 -7
- package/dist/bitwrench-lean.umd.min.js.gz +0 -0
- 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-util-css.umd.min.js.gz +0 -0
- package/dist/bitwrench.cjs.js +621 -153
- package/dist/bitwrench.cjs.min.js +6 -6
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +1 -1
- package/dist/bitwrench.d.ts +18 -11
- package/dist/bitwrench.es5.js +647 -154
- package/dist/bitwrench.es5.min.js +6 -6
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +621 -153
- package/dist/bitwrench.esm.min.js +5 -5
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.umd.js +621 -153
- package/dist/bitwrench.umd.min.js +6 -6
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +95 -95
- package/dist/bwserve.cjs.js +140 -7
- package/dist/bwserve.esm.js +141 -8
- package/dist/sri.json +45 -45
- package/docs/bitwrench-for-wasm.md +851 -0
- package/docs/bitwrench_api.md +133 -23
- package/docs/llm-bitwrench-guide.md +6 -5
- package/docs/state-management.md +27 -3
- package/docs/thinking-in-bitwrench.md +3 -2
- package/package.json +11 -9
- package/readme.html +17 -8
- package/src/bitwrench.d.ts +18 -11
- package/src/bitwrench.js +617 -148
- package/src/bwserve/bwclient.js +3 -3
- package/src/bwserve/client.js +26 -0
- package/src/bwserve/index.js +110 -3
- package/src/cli/attach.js +7 -5
- package/src/cli/serve.js +53 -10
- package/src/version.js +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bitwrench-lean v2.0.
|
|
1
|
+
/*! bitwrench-lean v2.0.30 | 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.
|
|
193
|
+
version: '2.0.30',
|
|
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-
|
|
200
|
+
buildDate: '2026-04-12T07:51:29.111Z'
|
|
201
201
|
};
|
|
202
202
|
|
|
203
203
|
/**
|
|
@@ -5830,9 +5830,6 @@
|
|
|
5830
5830
|
var _cw = function _cw() {
|
|
5831
5831
|
console.warn.apply(console, arguments);
|
|
5832
5832
|
};
|
|
5833
|
-
var _cl = function _cl() {
|
|
5834
|
-
console.log.apply(console, arguments);
|
|
5835
|
-
};
|
|
5836
5833
|
var _ce = function _ce() {
|
|
5837
5834
|
console.error.apply(console, arguments);
|
|
5838
5835
|
};
|
|
@@ -5973,60 +5970,104 @@
|
|
|
5973
5970
|
};
|
|
5974
5971
|
|
|
5975
5972
|
/**
|
|
5976
|
-
* Look up a DOM element by ID
|
|
5977
|
-
*
|
|
5978
|
-
*
|
|
5979
|
-
*
|
|
5980
|
-
*
|
|
5981
|
-
*
|
|
5982
|
-
*
|
|
5983
|
-
*
|
|
5984
|
-
*
|
|
5985
|
-
*
|
|
5986
|
-
*
|
|
5987
|
-
*
|
|
5988
|
-
*
|
|
5989
|
-
*
|
|
5990
|
-
*
|
|
5973
|
+
* Look up a single DOM element by ID, CSS selector, UUID, or element ref.
|
|
5974
|
+
* Optionally apply content or a function to the resolved element.
|
|
5975
|
+
*
|
|
5976
|
+
* Resolution order for string targets:
|
|
5977
|
+
* 1. Check `bw._nodeMap[id]` cache (O(1), stale entries auto-pruned)
|
|
5978
|
+
* 2. `document.getElementById(id)`
|
|
5979
|
+
* 3. `document.querySelector(id)` for selectors starting with # or .
|
|
5980
|
+
* 4. Class-based lookup for `bw_uuid_*` tokens
|
|
5981
|
+
*
|
|
5982
|
+
* With one argument, returns the element (or null). With two arguments,
|
|
5983
|
+
* applies the second argument to the element and returns the element:
|
|
5984
|
+
* - string/number: sets `el.textContent`
|
|
5985
|
+
* - function: calls `apply(el)`, returns el
|
|
5986
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
5987
|
+
* - array: clears children, appends each item (string -> text node, TACO -> element)
|
|
5988
|
+
*
|
|
5989
|
+
* @param {string|Element} target - Element ref, ID, CSS selector, or bw_uuid_* class
|
|
5990
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
5991
5991
|
* @returns {Element|null} The DOM element, or null if not found
|
|
5992
|
-
* @category
|
|
5992
|
+
* @category DOM Selection
|
|
5993
|
+
* @see bw.$
|
|
5994
|
+
* @see bw.patch
|
|
5995
|
+
* @example
|
|
5996
|
+
* bw.el('#title') // lookup
|
|
5997
|
+
* bw.el('#title', 'Hello') // set text content
|
|
5998
|
+
* bw.el('#app', { t: 'h1', c: 'Hi' }) // mount TACO
|
|
5999
|
+
* bw.el('.card', function(el) { // apply function
|
|
6000
|
+
* el.style.opacity = '0.5';
|
|
6001
|
+
* })
|
|
5993
6002
|
*/
|
|
5994
|
-
bw.
|
|
5995
|
-
//
|
|
5996
|
-
|
|
5997
|
-
if (!
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
if (cached
|
|
6005
|
-
|
|
6003
|
+
bw.el = function (target, apply) {
|
|
6004
|
+
// Resolve target to element
|
|
6005
|
+
var el;
|
|
6006
|
+
if (!_is(target, 'string')) {
|
|
6007
|
+
el = target || null;
|
|
6008
|
+
} else if (!target || !bw._isBrowser) {
|
|
6009
|
+
el = null;
|
|
6010
|
+
} else {
|
|
6011
|
+
// 1. Check cache
|
|
6012
|
+
var cached = bw._nodeMap[target];
|
|
6013
|
+
if (cached) {
|
|
6014
|
+
if (cached.parentNode !== null) {
|
|
6015
|
+
el = cached;
|
|
6016
|
+
} else {
|
|
6017
|
+
delete bw._nodeMap[target];
|
|
6018
|
+
}
|
|
6019
|
+
}
|
|
6020
|
+
if (!el) {
|
|
6021
|
+
// 2. getElementById
|
|
6022
|
+
el = document.getElementById(target);
|
|
6023
|
+
// 3. querySelector for CSS selectors
|
|
6024
|
+
if (!el && (target.charAt(0) === '#' || target.charAt(0) === '.')) {
|
|
6025
|
+
el = document.querySelector(target);
|
|
6026
|
+
}
|
|
6027
|
+
// 4. bw_uuid_* class lookup
|
|
6028
|
+
if (!el && target.indexOf('bw_uuid_') === 0) {
|
|
6029
|
+
el = document.querySelector('.' + target);
|
|
6030
|
+
}
|
|
6031
|
+
// 5. Cache result
|
|
6032
|
+
if (el) bw._nodeMap[target] = el;
|
|
6006
6033
|
}
|
|
6007
|
-
// Stale — remove and fall through
|
|
6008
|
-
delete bw._nodeMap[id];
|
|
6009
6034
|
}
|
|
6010
6035
|
|
|
6011
|
-
//
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
if (!el && (id.charAt(0) === '#' || id.charAt(0) === '.')) {
|
|
6016
|
-
el = document.querySelector(id);
|
|
6017
|
-
}
|
|
6036
|
+
// Apply (if provided and element found)
|
|
6037
|
+
if (el && apply !== undefined) _applyTo(el, apply);
|
|
6038
|
+
return el;
|
|
6039
|
+
};
|
|
6018
6040
|
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6041
|
+
/**
|
|
6042
|
+
* Internal: apply content or function to a DOM element.
|
|
6043
|
+
* Shared by bw.el() and bw.$().
|
|
6044
|
+
* @private
|
|
6045
|
+
*/
|
|
6046
|
+
function _applyTo(el, apply) {
|
|
6047
|
+
if (_is(apply, 'function')) {
|
|
6048
|
+
apply(el);
|
|
6049
|
+
} else if (_isA(apply)) {
|
|
6050
|
+
el.innerHTML = '';
|
|
6051
|
+
apply.forEach(function (item) {
|
|
6052
|
+
if (item != null) {
|
|
6053
|
+
if (_is(item, 'object') && item.t) {
|
|
6054
|
+
el.appendChild(bw.createDOM(item));
|
|
6055
|
+
} else {
|
|
6056
|
+
el.appendChild(document.createTextNode(String(item)));
|
|
6057
|
+
}
|
|
6058
|
+
}
|
|
6059
|
+
});
|
|
6060
|
+
} else if (_is(apply, 'object') && apply !== null && apply.t) {
|
|
6061
|
+
el.innerHTML = '';
|
|
6062
|
+
el.appendChild(bw.createDOM(apply));
|
|
6063
|
+
} else {
|
|
6064
|
+
el.textContent = String(apply);
|
|
6022
6065
|
}
|
|
6066
|
+
}
|
|
6023
6067
|
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
}
|
|
6028
|
-
return el;
|
|
6029
|
-
};
|
|
6068
|
+
// Internal alias — kept for one release cycle (v2.0.26).
|
|
6069
|
+
// Will be removed in v2.0.27. Use bw.el() instead.
|
|
6070
|
+
bw._el = bw.el;
|
|
6030
6071
|
|
|
6031
6072
|
/**
|
|
6032
6073
|
* Register a DOM element in the node cache under one or more keys.
|
|
@@ -6090,6 +6131,12 @@
|
|
|
6090
6131
|
*/
|
|
6091
6132
|
var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
|
|
6092
6133
|
|
|
6134
|
+
/**
|
|
6135
|
+
* SVG namespace URI for createElementNS.
|
|
6136
|
+
* @private
|
|
6137
|
+
*/
|
|
6138
|
+
var _SVG_NS = 'http://www.w3.org/2000/svg';
|
|
6139
|
+
|
|
6093
6140
|
/**
|
|
6094
6141
|
* Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
|
|
6095
6142
|
*
|
|
@@ -6140,9 +6187,9 @@
|
|
|
6140
6187
|
bw.getUUID = function (tacoOrElement) {
|
|
6141
6188
|
if (!tacoOrElement) return null;
|
|
6142
6189
|
var classStr;
|
|
6143
|
-
// DOM element: check className
|
|
6190
|
+
// DOM element: check className (SVG elements use getAttribute for string value)
|
|
6144
6191
|
if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
|
|
6145
|
-
classStr = tacoOrElement.className;
|
|
6192
|
+
classStr = typeof tacoOrElement.className === 'string' ? tacoOrElement.className : tacoOrElement.getAttribute('class') || '';
|
|
6146
6193
|
}
|
|
6147
6194
|
// TACO object: check a.class
|
|
6148
6195
|
else if (tacoOrElement.a && _is(tacoOrElement.a["class"], 'string')) {
|
|
@@ -6427,7 +6474,7 @@
|
|
|
6427
6474
|
var fnCounterBefore = bw._fnIDCounter;
|
|
6428
6475
|
|
|
6429
6476
|
// Render body content
|
|
6430
|
-
var bodyHTML
|
|
6477
|
+
var bodyHTML;
|
|
6431
6478
|
if (_is(body, 'string')) {
|
|
6432
6479
|
bodyHTML = body;
|
|
6433
6480
|
} else {
|
|
@@ -6603,8 +6650,10 @@
|
|
|
6603
6650
|
_taco$o2 = taco.o,
|
|
6604
6651
|
opts = _taco$o2 === void 0 ? {} : _taco$o2;
|
|
6605
6652
|
|
|
6606
|
-
//
|
|
6607
|
-
|
|
6653
|
+
// SVG namespace: detect SVG context and thread through children.
|
|
6654
|
+
// {t:'svg'} starts SVG context; foreignObject children revert to HTML.
|
|
6655
|
+
var svgCtx = options._svgCtx || tag === 'svg';
|
|
6656
|
+
var el = svgCtx ? document.createElementNS(_SVG_NS, tag) : document.createElement(tag);
|
|
6608
6657
|
|
|
6609
6658
|
// Set attributes
|
|
6610
6659
|
for (var _i2 = 0, _Object$entries2 = Object.entries(attrs); _i2 < _Object$entries2.length; _i2++) {
|
|
@@ -6617,9 +6666,10 @@
|
|
|
6617
6666
|
Object.assign(el.style, value);
|
|
6618
6667
|
} else if (key === 'class') {
|
|
6619
6668
|
// Handle class as array or string
|
|
6669
|
+
// SVG elements use SVGAnimatedString for className, so use setAttribute
|
|
6620
6670
|
var classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
6621
6671
|
if (classStr) {
|
|
6622
|
-
el.className = classStr;
|
|
6672
|
+
if (svgCtx) el.setAttribute('class', classStr);else el.className = classStr;
|
|
6623
6673
|
}
|
|
6624
6674
|
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
6625
6675
|
// Event handlers
|
|
@@ -6640,11 +6690,19 @@
|
|
|
6640
6690
|
// Add children, building _bw_refs for fast parent→child access.
|
|
6641
6691
|
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
6642
6692
|
// so o.render functions can access them without any DOM lookup.
|
|
6693
|
+
// SVG: foreignObject children revert to HTML namespace; otherwise inherit.
|
|
6694
|
+
var childOpts = options;
|
|
6695
|
+
var childSvgCtx = svgCtx && tag !== 'foreignObject';
|
|
6696
|
+
if (childSvgCtx !== (options._svgCtx || false)) {
|
|
6697
|
+
childOpts = Object.assign({}, options, {
|
|
6698
|
+
_svgCtx: childSvgCtx || undefined
|
|
6699
|
+
});
|
|
6700
|
+
}
|
|
6643
6701
|
if (content != null) {
|
|
6644
6702
|
if (_isA(content)) {
|
|
6645
6703
|
content.forEach(function (child) {
|
|
6646
6704
|
if (child != null) {
|
|
6647
|
-
var childEl = bw.createDOM(child,
|
|
6705
|
+
var childEl = bw.createDOM(child, childOpts);
|
|
6648
6706
|
el.appendChild(childEl);
|
|
6649
6707
|
// Build local refs for addressable children
|
|
6650
6708
|
var childRefId = child && child.a ? child.a.id || bw.getUUID(child) : null;
|
|
@@ -6667,7 +6725,7 @@
|
|
|
6667
6725
|
// Raw HTML content — inject via innerHTML
|
|
6668
6726
|
el.innerHTML = content.v;
|
|
6669
6727
|
} else if (_is(content, 'object') && content.t) {
|
|
6670
|
-
var childEl = bw.createDOM(content,
|
|
6728
|
+
var childEl = bw.createDOM(content, childOpts);
|
|
6671
6729
|
el.appendChild(childEl);
|
|
6672
6730
|
var childRefId = content.a ? content.a.id || bw.getUUID(content) : null;
|
|
6673
6731
|
if (childRefId) {
|
|
@@ -6693,13 +6751,21 @@
|
|
|
6693
6751
|
}
|
|
6694
6752
|
|
|
6695
6753
|
// Register UUID class in node cache (bw_uuid_* tokens in class string)
|
|
6696
|
-
|
|
6697
|
-
|
|
6754
|
+
// SVG elements have SVGAnimatedString for className; use getAttribute instead
|
|
6755
|
+
var clsStr = svgCtx ? el.getAttribute('class') || '' : el.className;
|
|
6756
|
+
if (clsStr) {
|
|
6757
|
+
var uuidMatch = clsStr.match(_UUID_RE);
|
|
6698
6758
|
if (uuidMatch) {
|
|
6699
6759
|
bw._nodeMap[uuidMatch[0]] = el;
|
|
6700
6760
|
}
|
|
6701
6761
|
}
|
|
6702
6762
|
|
|
6763
|
+
// Store component type metadata (e.g., 'card', 'tabs') for introspection.
|
|
6764
|
+
// BCCL factories set o.type; custom components can too.
|
|
6765
|
+
if (opts.type) {
|
|
6766
|
+
el._bw_type = opts.type;
|
|
6767
|
+
}
|
|
6768
|
+
|
|
6703
6769
|
// Handle lifecycle hooks and state
|
|
6704
6770
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
6705
6771
|
// Ensure element has a UUID class for identity
|
|
@@ -6728,11 +6794,19 @@
|
|
|
6728
6794
|
} : null);
|
|
6729
6795
|
if (mountFn) {
|
|
6730
6796
|
if (document.body.contains(el)) {
|
|
6731
|
-
|
|
6797
|
+
try {
|
|
6798
|
+
mountFn(el, el._bw_state || {});
|
|
6799
|
+
} catch (e) {
|
|
6800
|
+
_cw('o.mounted error: ' + e.message);
|
|
6801
|
+
}
|
|
6732
6802
|
} else {
|
|
6733
6803
|
requestAnimationFrame(function () {
|
|
6734
6804
|
if (document.body.contains(el)) {
|
|
6735
|
-
|
|
6805
|
+
try {
|
|
6806
|
+
mountFn(el, el._bw_state || {});
|
|
6807
|
+
} catch (e) {
|
|
6808
|
+
_cw('o.mounted error: ' + e.message);
|
|
6809
|
+
}
|
|
6736
6810
|
}
|
|
6737
6811
|
});
|
|
6738
6812
|
}
|
|
@@ -6741,7 +6815,11 @@
|
|
|
6741
6815
|
// Store unmount callback keyed by UUID class
|
|
6742
6816
|
if (opts.unmount) {
|
|
6743
6817
|
bw._unmountCallbacks.set(uuid, function () {
|
|
6744
|
-
|
|
6818
|
+
try {
|
|
6819
|
+
opts.unmount(el, el._bw_state || {});
|
|
6820
|
+
} catch (e) {
|
|
6821
|
+
_cw('o.unmount error: ' + e.message);
|
|
6822
|
+
}
|
|
6745
6823
|
});
|
|
6746
6824
|
}
|
|
6747
6825
|
}
|
|
@@ -6760,24 +6838,25 @@
|
|
|
6760
6838
|
}
|
|
6761
6839
|
|
|
6762
6840
|
// Slot declarations: auto-generate setX/getX pairs
|
|
6841
|
+
// The target element is cached at creation time to avoid repeated
|
|
6842
|
+
// querySelector calls on every get/set invocation.
|
|
6763
6843
|
if (opts.slots) {
|
|
6764
6844
|
for (var sk in opts.slots) {
|
|
6765
6845
|
if (_hop.call(opts.slots, sk)) {
|
|
6766
6846
|
(function (name, selector) {
|
|
6847
|
+
var target = el.querySelector(selector);
|
|
6767
6848
|
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
6768
6849
|
el.bw['set' + cap] = function (value) {
|
|
6769
|
-
|
|
6770
|
-
if (!t) return;
|
|
6850
|
+
if (!target) return;
|
|
6771
6851
|
if (value != null && _typeof(value) === 'object' && value.t) {
|
|
6772
|
-
|
|
6773
|
-
|
|
6852
|
+
target.innerHTML = '';
|
|
6853
|
+
target.appendChild(bw.createDOM(value));
|
|
6774
6854
|
} else {
|
|
6775
|
-
|
|
6855
|
+
target.textContent = value != null ? String(value) : '';
|
|
6776
6856
|
}
|
|
6777
6857
|
};
|
|
6778
6858
|
el.bw['get' + cap] = function () {
|
|
6779
|
-
|
|
6780
|
-
return t ? t.textContent : '';
|
|
6859
|
+
return target ? target.textContent : '';
|
|
6781
6860
|
};
|
|
6782
6861
|
})(sk, opts.slots[sk]);
|
|
6783
6862
|
}
|
|
@@ -6818,7 +6897,7 @@
|
|
|
6818
6897
|
}
|
|
6819
6898
|
|
|
6820
6899
|
// Get target element (use cache-backed lookup)
|
|
6821
|
-
var targetEl = bw.
|
|
6900
|
+
var targetEl = bw.el(target);
|
|
6822
6901
|
if (!targetEl) {
|
|
6823
6902
|
_ce('bw.DOM: Target element not found:', target);
|
|
6824
6903
|
return null;
|
|
@@ -6921,7 +7000,8 @@
|
|
|
6921
7000
|
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
6922
7001
|
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
6923
7002
|
uuidEls.forEach(function (uel) {
|
|
6924
|
-
var
|
|
7003
|
+
var uc = typeof uel.className === 'string' ? uel.className : uel.getAttribute('class') || '';
|
|
7004
|
+
var m = uc && uc.match(_UUID_RE);
|
|
6925
7005
|
if (m) delete bw._nodeMap[m[0]];
|
|
6926
7006
|
});
|
|
6927
7007
|
|
|
@@ -7009,9 +7089,13 @@
|
|
|
7009
7089
|
* bw.update(el); // re-renders, emits bw:statechange
|
|
7010
7090
|
*/
|
|
7011
7091
|
bw.update = function (target) {
|
|
7012
|
-
var el = bw.
|
|
7092
|
+
var el = bw.el(target);
|
|
7013
7093
|
if (el && el._bw_render) {
|
|
7014
|
-
|
|
7094
|
+
try {
|
|
7095
|
+
el._bw_render(el, el._bw_state || {});
|
|
7096
|
+
} catch (e) {
|
|
7097
|
+
_cw('o.render error: ' + e.message);
|
|
7098
|
+
}
|
|
7015
7099
|
bw.emit(el, 'statechange', el._bw_state);
|
|
7016
7100
|
}
|
|
7017
7101
|
return el || null;
|
|
@@ -7038,7 +7122,7 @@
|
|
|
7038
7122
|
* bw.patch('info', { t: 'em', c: 'new' }); // replace children with TACO
|
|
7039
7123
|
*/
|
|
7040
7124
|
bw.patch = function (id, content, attr) {
|
|
7041
|
-
var el = bw.
|
|
7125
|
+
var el = bw.el(id);
|
|
7042
7126
|
if (!el) return null;
|
|
7043
7127
|
if (attr) {
|
|
7044
7128
|
// Patch an attribute
|
|
@@ -7109,7 +7193,7 @@
|
|
|
7109
7193
|
* // Dispatches CustomEvent 'bw:statechange' on the element
|
|
7110
7194
|
*/
|
|
7111
7195
|
bw.emit = function (target, eventName, detail) {
|
|
7112
|
-
var el = bw.
|
|
7196
|
+
var el = bw.el(target);
|
|
7113
7197
|
if (el) {
|
|
7114
7198
|
el.dispatchEvent(new CustomEvent('bw:' + eventName, {
|
|
7115
7199
|
bubbles: true,
|
|
@@ -7138,7 +7222,7 @@
|
|
|
7138
7222
|
* });
|
|
7139
7223
|
*/
|
|
7140
7224
|
bw.on = function (target, eventName, handler) {
|
|
7141
|
-
var el = bw.
|
|
7225
|
+
var el = bw.el(target);
|
|
7142
7226
|
if (el) {
|
|
7143
7227
|
el.addEventListener('bw:' + eventName, function (e) {
|
|
7144
7228
|
handler(e.detail, e);
|
|
@@ -7165,23 +7249,46 @@
|
|
|
7165
7249
|
*
|
|
7166
7250
|
* @param {string} topic - Topic name (plain string, no prefix)
|
|
7167
7251
|
* @param {*} [detail] - Data to pass to subscribers
|
|
7168
|
-
* @returns {number} Count of successfully called subscribers
|
|
7252
|
+
* @returns {number} Count of successfully called subscribers (including wildcard matches)
|
|
7169
7253
|
* @category Pub/Sub
|
|
7170
7254
|
* @see bw.sub
|
|
7171
7255
|
* @example
|
|
7172
7256
|
* bw.pub('score:updated', { player: 'X', score: 10 });
|
|
7257
|
+
* // Wildcard subscribers matching 'score:*' will also fire
|
|
7173
7258
|
*/
|
|
7174
7259
|
bw.pub = function (topic, detail) {
|
|
7175
|
-
var subs = bw._topics[topic];
|
|
7176
|
-
if (!subs || subs.length === 0) return 0;
|
|
7177
|
-
var snapshot = subs.slice(); // safe against unsub during iteration
|
|
7178
7260
|
var called = 0;
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7261
|
+
// Exact-match subscribers
|
|
7262
|
+
var subs = bw._topics[topic];
|
|
7263
|
+
if (subs && subs.length > 0) {
|
|
7264
|
+
var snapshot = subs.slice();
|
|
7265
|
+
for (var i = 0; i < snapshot.length; i++) {
|
|
7266
|
+
try {
|
|
7267
|
+
snapshot[i].handler(detail, topic);
|
|
7268
|
+
called++;
|
|
7269
|
+
} catch (err) {
|
|
7270
|
+
_cw('bw.pub: subscriber error on topic "' + topic + '":', err);
|
|
7271
|
+
}
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
// Wildcard subscribers -- patterns ending with '*'
|
|
7275
|
+
var keys = Object.keys(bw._topics);
|
|
7276
|
+
for (var k = 0; k < keys.length; k++) {
|
|
7277
|
+
var pat = keys[k];
|
|
7278
|
+
if (pat.charAt(pat.length - 1) !== '*') continue;
|
|
7279
|
+
var prefix = pat.slice(0, -1); // strip trailing '*'
|
|
7280
|
+
if (topic.length >= prefix.length && topic.substring(0, prefix.length) === prefix && topic !== pat) {
|
|
7281
|
+
var wsubs = bw._topics[pat];
|
|
7282
|
+
if (!wsubs) continue;
|
|
7283
|
+
var wsnap = wsubs.slice();
|
|
7284
|
+
for (var w = 0; w < wsnap.length; w++) {
|
|
7285
|
+
try {
|
|
7286
|
+
wsnap[w].handler(detail, topic);
|
|
7287
|
+
called++;
|
|
7288
|
+
} catch (err) {
|
|
7289
|
+
_cw('bw.pub: wildcard subscriber error on "' + pat + '" for topic "' + topic + '":', err);
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7185
7292
|
}
|
|
7186
7293
|
}
|
|
7187
7294
|
return called;
|
|
@@ -7190,12 +7297,17 @@
|
|
|
7190
7297
|
/**
|
|
7191
7298
|
* Subscribe to a topic. Returns an unsub() function.
|
|
7192
7299
|
*
|
|
7193
|
-
*
|
|
7300
|
+
* Supports wildcard patterns: a topic ending in `*` matches any published
|
|
7301
|
+
* topic that starts with the prefix before the `*`. For example,
|
|
7302
|
+
* `'agui:*'` matches `'agui:ready'`, `'agui:error'`, etc. The handler
|
|
7303
|
+
* receives `(detail, topic)` so it can distinguish which topic fired.
|
|
7304
|
+
*
|
|
7305
|
+
* Optional third argument ties the subscription to a DOM element's lifecycle --
|
|
7194
7306
|
* when `bw.cleanup()` is called on that element, the subscription is automatically
|
|
7195
7307
|
* removed, preventing memory leaks.
|
|
7196
7308
|
*
|
|
7197
|
-
* @param {string} topic - Topic name
|
|
7198
|
-
* @param {Function} handler - Called with (detail) on each publish
|
|
7309
|
+
* @param {string} topic - Topic name, or wildcard pattern ending in '*'
|
|
7310
|
+
* @param {Function} handler - Called with (detail, topic) on each publish
|
|
7199
7311
|
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
7200
7312
|
* @returns {Function} Call to unsubscribe
|
|
7201
7313
|
* @category Pub/Sub
|
|
@@ -7206,6 +7318,11 @@
|
|
|
7206
7318
|
* console.log(detail.player, 'scored', detail.score);
|
|
7207
7319
|
* });
|
|
7208
7320
|
* // Later: unsub() to stop listening
|
|
7321
|
+
*
|
|
7322
|
+
* // Wildcard: listen to all 'agui:' topics
|
|
7323
|
+
* bw.sub('agui:*', function(detail, topic) {
|
|
7324
|
+
* console.log('Got', topic, detail);
|
|
7325
|
+
* });
|
|
7209
7326
|
*/
|
|
7210
7327
|
bw.sub = function (topic, handler, el) {
|
|
7211
7328
|
var id = ++bw._subIdCounter;
|
|
@@ -7262,6 +7379,37 @@
|
|
|
7262
7379
|
return removed;
|
|
7263
7380
|
};
|
|
7264
7381
|
|
|
7382
|
+
/**
|
|
7383
|
+
* Subscribe to a topic for a single event only. The subscription is
|
|
7384
|
+
* automatically removed after the first publish. Equivalent to manually
|
|
7385
|
+
* calling unsub() inside a bw.sub() handler, but avoids the common bug
|
|
7386
|
+
* of forgetting to unsubscribe.
|
|
7387
|
+
*
|
|
7388
|
+
* @param {string} topic - Topic name
|
|
7389
|
+
* @param {Function} handler - Called once with (detail) on the next publish
|
|
7390
|
+
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
7391
|
+
* @returns {Function} Call to cancel the subscription before it fires
|
|
7392
|
+
* @category Pub/Sub
|
|
7393
|
+
* @see bw.sub
|
|
7394
|
+
* @see bw.pub
|
|
7395
|
+
* @example
|
|
7396
|
+
* bw.once('data:loaded', function(detail) {
|
|
7397
|
+
* console.log('Received:', detail);
|
|
7398
|
+
* // No need to unsubscribe -- already done automatically
|
|
7399
|
+
* });
|
|
7400
|
+
*
|
|
7401
|
+
* // Cancel before it fires:
|
|
7402
|
+
* var cancel = bw.once('timeout', handler);
|
|
7403
|
+
* cancel(); // handler will never be called
|
|
7404
|
+
*/
|
|
7405
|
+
bw.once = function (topic, handler, el) {
|
|
7406
|
+
var unsub = bw.sub(topic, function (detail) {
|
|
7407
|
+
unsub();
|
|
7408
|
+
handler(detail);
|
|
7409
|
+
}, el);
|
|
7410
|
+
return unsub;
|
|
7411
|
+
};
|
|
7412
|
+
|
|
7265
7413
|
// ===================================================================================
|
|
7266
7414
|
// Function Registry (revived from v1 for string dispatch contexts)
|
|
7267
7415
|
// ===================================================================================
|
|
@@ -7505,7 +7653,7 @@
|
|
|
7505
7653
|
* };
|
|
7506
7654
|
*/
|
|
7507
7655
|
bw.message = function (target, action, data) {
|
|
7508
|
-
var el = bw.
|
|
7656
|
+
var el = bw.el(target);
|
|
7509
7657
|
if (!el) el = bw.$('.' + target)[0];
|
|
7510
7658
|
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
7511
7659
|
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
@@ -7515,6 +7663,217 @@
|
|
|
7515
7663
|
return true;
|
|
7516
7664
|
};
|
|
7517
7665
|
|
|
7666
|
+
/**
|
|
7667
|
+
* Collect form data from all input, select, and textarea elements within a
|
|
7668
|
+
* container. Each element's `name` attribute (or `id` if no name) becomes a
|
|
7669
|
+
* key in the returned object. This provides a lightweight alternative to the
|
|
7670
|
+
* browser FormData API that returns a plain object suitable for JSON
|
|
7671
|
+
* serialization or bw.pub().
|
|
7672
|
+
*
|
|
7673
|
+
* Handles all standard HTML form controls:
|
|
7674
|
+
* - text/number/email/etc inputs: string value
|
|
7675
|
+
* - checkboxes: boolean (true/false)
|
|
7676
|
+
* - radio buttons: string value of the checked radio (unchecked groups omitted)
|
|
7677
|
+
* - multi-select: array of selected option values
|
|
7678
|
+
* - textarea: string value
|
|
7679
|
+
*
|
|
7680
|
+
* Elements without both `name` and `id` attributes are silently skipped.
|
|
7681
|
+
*
|
|
7682
|
+
* @param {string|Element} target - CSS selector, UUID string, or DOM element
|
|
7683
|
+
* @returns {Object} Plain object mapping field names to values
|
|
7684
|
+
* @category Component
|
|
7685
|
+
* @see bw.makeForm
|
|
7686
|
+
* @see bw.makeInput
|
|
7687
|
+
* @example
|
|
7688
|
+
* // Given a form with name="email" input and name="agree" checkbox:
|
|
7689
|
+
* var data = bw.formData('#signup-form');
|
|
7690
|
+
* // => { email: 'user@example.com', agree: true }
|
|
7691
|
+
*
|
|
7692
|
+
* // Collect and publish in one step:
|
|
7693
|
+
* bw.pub('form:submit', bw.formData('#my-form'));
|
|
7694
|
+
*
|
|
7695
|
+
* // Works with any container, not just <form>:
|
|
7696
|
+
* bw.pub('settings:changed', bw.formData('.settings-panel'));
|
|
7697
|
+
*/
|
|
7698
|
+
bw.formData = function (target) {
|
|
7699
|
+
var el = bw.el(target);
|
|
7700
|
+
if (!el) return {};
|
|
7701
|
+
var result = {};
|
|
7702
|
+
var inputs = el.querySelectorAll('input, select, textarea');
|
|
7703
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
7704
|
+
var inp = inputs[i];
|
|
7705
|
+
var key = inp.name || inp.id;
|
|
7706
|
+
if (!key) continue;
|
|
7707
|
+
if (inp.type === 'checkbox') {
|
|
7708
|
+
result[key] = inp.checked;
|
|
7709
|
+
} else if (inp.type === 'radio') {
|
|
7710
|
+
if (inp.checked) result[key] = inp.value;
|
|
7711
|
+
} else if (inp.tagName === 'SELECT' && inp.multiple) {
|
|
7712
|
+
result[key] = [];
|
|
7713
|
+
for (var j = 0; j < inp.options.length; j++) {
|
|
7714
|
+
if (inp.options[j].selected) result[key].push(inp.options[j].value);
|
|
7715
|
+
}
|
|
7716
|
+
} else {
|
|
7717
|
+
result[key] = inp.value;
|
|
7718
|
+
}
|
|
7719
|
+
}
|
|
7720
|
+
return result;
|
|
7721
|
+
};
|
|
7722
|
+
|
|
7723
|
+
// ===================================================================================
|
|
7724
|
+
// bw.jsonPatch() — RFC 6902 JSON Patch on plain objects
|
|
7725
|
+
// ===================================================================================
|
|
7726
|
+
|
|
7727
|
+
/**
|
|
7728
|
+
* Apply RFC 6902 JSON Patch operations to a plain object.
|
|
7729
|
+
*
|
|
7730
|
+
* Supported operations: add, remove, replace, move, copy, test.
|
|
7731
|
+
* Paths use JSON Pointer (RFC 6901) notation: `/foo/bar/0`.
|
|
7732
|
+
* Mutates the target object in place and returns it.
|
|
7733
|
+
*
|
|
7734
|
+
* @param {Object} obj - Target object to patch
|
|
7735
|
+
* @param {Array<Object>} ops - Array of patch operations
|
|
7736
|
+
* @param {string} ops[].op - Operation: 'add', 'remove', 'replace', 'move', 'copy', 'test'
|
|
7737
|
+
* @param {string} ops[].path - JSON Pointer path (e.g. '/a/b/0')
|
|
7738
|
+
* @param {*} [ops[].value] - Value for add/replace/test
|
|
7739
|
+
* @param {string} [ops[].from] - Source path for move/copy
|
|
7740
|
+
* @returns {Object} The patched object (same reference)
|
|
7741
|
+
* @throws {Error} On invalid op, missing path, test failure, or path not found for remove
|
|
7742
|
+
* @category Data Utilities
|
|
7743
|
+
* @see bw.patch
|
|
7744
|
+
* @example
|
|
7745
|
+
* var obj = { a: 1, b: { c: 2 } };
|
|
7746
|
+
* bw.jsonPatch(obj, [
|
|
7747
|
+
* { op: 'replace', path: '/a', value: 10 },
|
|
7748
|
+
* { op: 'add', path: '/b/d', value: 3 },
|
|
7749
|
+
* { op: 'remove', path: '/b/c' }
|
|
7750
|
+
* ]);
|
|
7751
|
+
* // obj => { a: 10, b: { d: 3 } }
|
|
7752
|
+
*/
|
|
7753
|
+
bw.jsonPatch = function (obj, ops) {
|
|
7754
|
+
if (!_isA(ops)) return obj;
|
|
7755
|
+
|
|
7756
|
+
// Parse JSON Pointer path to array of keys
|
|
7757
|
+
function parsePath(path) {
|
|
7758
|
+
if (path === '') return [];
|
|
7759
|
+
if (path.charAt(0) !== '/') throw new Error('Invalid JSON Pointer: ' + path);
|
|
7760
|
+
return path.slice(1).split('/').map(function (s) {
|
|
7761
|
+
return s.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
7762
|
+
});
|
|
7763
|
+
}
|
|
7764
|
+
|
|
7765
|
+
// Walk to parent of final key; return { parent, key }
|
|
7766
|
+
function resolve(root, keys) {
|
|
7767
|
+
var parent = root;
|
|
7768
|
+
for (var i = 0; i < keys.length - 1; i++) {
|
|
7769
|
+
var k = _isA(parent) ? parseInt(keys[i], 10) : keys[i];
|
|
7770
|
+
if (parent[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
7771
|
+
parent = parent[k];
|
|
7772
|
+
}
|
|
7773
|
+
return {
|
|
7774
|
+
parent: parent,
|
|
7775
|
+
key: _isA(parent) ? parseInt(keys[keys.length - 1], 10) : keys[keys.length - 1]
|
|
7776
|
+
};
|
|
7777
|
+
}
|
|
7778
|
+
|
|
7779
|
+
// Get value at path
|
|
7780
|
+
function getVal(root, keys) {
|
|
7781
|
+
var cur = root;
|
|
7782
|
+
for (var i = 0; i < keys.length; i++) {
|
|
7783
|
+
var k = _isA(cur) ? parseInt(keys[i], 10) : keys[i];
|
|
7784
|
+
if (cur[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
7785
|
+
cur = cur[k];
|
|
7786
|
+
}
|
|
7787
|
+
return cur;
|
|
7788
|
+
}
|
|
7789
|
+
for (var i = 0; i < ops.length; i++) {
|
|
7790
|
+
var op = ops[i];
|
|
7791
|
+
if (!op.op || !_is(op.path, 'string')) throw new Error('Invalid patch operation at index ' + i);
|
|
7792
|
+
var keys = parsePath(op.path);
|
|
7793
|
+
var r, val, fromKeys, fr, tr, cr;
|
|
7794
|
+
switch (op.op) {
|
|
7795
|
+
case 'add':
|
|
7796
|
+
{
|
|
7797
|
+
if (keys.length === 0) throw new Error('Cannot add to root');
|
|
7798
|
+
r = resolve(obj, keys);
|
|
7799
|
+
if (_isA(r.parent) && r.key <= r.parent.length) {
|
|
7800
|
+
r.parent.splice(r.key, 0, op.value);
|
|
7801
|
+
} else {
|
|
7802
|
+
r.parent[r.key] = op.value;
|
|
7803
|
+
}
|
|
7804
|
+
break;
|
|
7805
|
+
}
|
|
7806
|
+
case 'remove':
|
|
7807
|
+
{
|
|
7808
|
+
if (keys.length === 0) throw new Error('Cannot remove root');
|
|
7809
|
+
r = resolve(obj, keys);
|
|
7810
|
+
if (_isA(r.parent)) {
|
|
7811
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
7812
|
+
r.parent.splice(r.key, 1);
|
|
7813
|
+
} else {
|
|
7814
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
7815
|
+
delete r.parent[r.key];
|
|
7816
|
+
}
|
|
7817
|
+
break;
|
|
7818
|
+
}
|
|
7819
|
+
case 'replace':
|
|
7820
|
+
{
|
|
7821
|
+
if (keys.length === 0) throw new Error('Cannot replace root');
|
|
7822
|
+
r = resolve(obj, keys);
|
|
7823
|
+
if (_isA(r.parent)) {
|
|
7824
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
7825
|
+
} else {
|
|
7826
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
7827
|
+
}
|
|
7828
|
+
r.parent[r.key] = op.value;
|
|
7829
|
+
break;
|
|
7830
|
+
}
|
|
7831
|
+
case 'move':
|
|
7832
|
+
{
|
|
7833
|
+
if (!_is(op.from, 'string')) throw new Error('move requires "from"');
|
|
7834
|
+
fromKeys = parsePath(op.from);
|
|
7835
|
+
val = getVal(obj, fromKeys);
|
|
7836
|
+
fr = resolve(obj, fromKeys);
|
|
7837
|
+
if (_isA(fr.parent)) {
|
|
7838
|
+
fr.parent.splice(fr.key, 1);
|
|
7839
|
+
} else {
|
|
7840
|
+
delete fr.parent[fr.key];
|
|
7841
|
+
}
|
|
7842
|
+
tr = resolve(obj, keys);
|
|
7843
|
+
if (_isA(tr.parent) && tr.key <= tr.parent.length) {
|
|
7844
|
+
tr.parent.splice(tr.key, 0, val);
|
|
7845
|
+
} else {
|
|
7846
|
+
tr.parent[tr.key] = val;
|
|
7847
|
+
}
|
|
7848
|
+
break;
|
|
7849
|
+
}
|
|
7850
|
+
case 'copy':
|
|
7851
|
+
{
|
|
7852
|
+
if (!_is(op.from, 'string')) throw new Error('copy requires "from"');
|
|
7853
|
+
val = getVal(obj, parsePath(op.from));
|
|
7854
|
+
cr = resolve(obj, keys);
|
|
7855
|
+
if (_isA(cr.parent) && cr.key <= cr.parent.length) {
|
|
7856
|
+
cr.parent.splice(cr.key, 0, val);
|
|
7857
|
+
} else {
|
|
7858
|
+
cr.parent[cr.key] = val;
|
|
7859
|
+
}
|
|
7860
|
+
break;
|
|
7861
|
+
}
|
|
7862
|
+
case 'test':
|
|
7863
|
+
{
|
|
7864
|
+
var actual = getVal(obj, keys);
|
|
7865
|
+
if (JSON.stringify(actual) !== JSON.stringify(op.value)) {
|
|
7866
|
+
throw new Error('Test failed: ' + op.path + ' expected ' + JSON.stringify(op.value) + ' got ' + JSON.stringify(actual));
|
|
7867
|
+
}
|
|
7868
|
+
break;
|
|
7869
|
+
}
|
|
7870
|
+
default:
|
|
7871
|
+
throw new Error('Unknown op: ' + op.op);
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
return obj;
|
|
7875
|
+
};
|
|
7876
|
+
|
|
7518
7877
|
// ===================================================================================
|
|
7519
7878
|
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
7520
7879
|
// ===================================================================================
|
|
@@ -7647,7 +8006,7 @@
|
|
|
7647
8006
|
var type = msg.type;
|
|
7648
8007
|
var target = msg.target;
|
|
7649
8008
|
if (type === 'replace') {
|
|
7650
|
-
var el = bw.
|
|
8009
|
+
var el = bw.el(target);
|
|
7651
8010
|
if (!el) return false;
|
|
7652
8011
|
bw.DOM(el, msg.node);
|
|
7653
8012
|
return true;
|
|
@@ -7655,13 +8014,13 @@
|
|
|
7655
8014
|
var patched = bw.patch(target, msg.content, msg.attr);
|
|
7656
8015
|
return patched !== null;
|
|
7657
8016
|
} else if (type === 'append') {
|
|
7658
|
-
var parent = bw.
|
|
8017
|
+
var parent = bw.el(target);
|
|
7659
8018
|
if (!parent) return false;
|
|
7660
8019
|
var child = bw.createDOM(msg.node);
|
|
7661
8020
|
parent.appendChild(child);
|
|
7662
8021
|
return true;
|
|
7663
8022
|
} else if (type === 'remove') {
|
|
7664
|
-
var toRemove = bw.
|
|
8023
|
+
var toRemove = bw.el(target);
|
|
7665
8024
|
if (!toRemove) return false;
|
|
7666
8025
|
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
7667
8026
|
toRemove.remove();
|
|
@@ -7714,33 +8073,99 @@
|
|
|
7714
8073
|
};
|
|
7715
8074
|
|
|
7716
8075
|
// ===================================================================================
|
|
7717
|
-
// bw.inspect() —
|
|
8076
|
+
// bw.inspect() — DOM introspection with bitwrench metadata
|
|
7718
8077
|
// ===================================================================================
|
|
7719
8078
|
|
|
7720
8079
|
/**
|
|
7721
|
-
* Inspect a DOM element
|
|
7722
|
-
*
|
|
7723
|
-
*
|
|
7724
|
-
*
|
|
7725
|
-
*
|
|
8080
|
+
* Inspect a DOM element and its subtree, returning a plain-object
|
|
8081
|
+
* representation with bitwrench metadata at each node. Useful for debugging,
|
|
8082
|
+
* devtools, MCP/AG-UI tool discovery, and automated testing.
|
|
8083
|
+
*
|
|
8084
|
+
* Each node in the returned tree includes:
|
|
8085
|
+
* - `tag` -- lowercase tag name (or '#text' for text nodes)
|
|
8086
|
+
* - `id` -- element id (if set)
|
|
8087
|
+
* - `uuid` -- bitwrench UUID class (if lifecycle-managed)
|
|
8088
|
+
* - `type` -- component type from o.type (if set, e.g. 'card', 'tabs')
|
|
8089
|
+
* - `classes` -- first 5 CSS classes (string, space-separated)
|
|
8090
|
+
* - `handles` -- array of el.bw method names (if any)
|
|
8091
|
+
* - `state` -- copy of _bw_state (if any)
|
|
8092
|
+
* - `hasRender` -- true if _bw_render is set
|
|
8093
|
+
* - `hasSubs` -- true if element has pub/sub subscriptions
|
|
8094
|
+
* - `refs` -- copy of _bw_refs keys (if any)
|
|
8095
|
+
* - `children` -- array of child node trees (up to depth limit, max 50 per level)
|
|
8096
|
+
*
|
|
8097
|
+
* @param {string|Element} target - CSS selector, UUID, or DOM element
|
|
8098
|
+
* @param {number} [depth=3] - Maximum recursion depth (0 = target only, no children)
|
|
8099
|
+
* @returns {Object|null} Plain object tree, or null if element not found
|
|
7726
8100
|
* @category Component
|
|
7727
8101
|
* @example
|
|
7728
|
-
*
|
|
7729
|
-
* bw.inspect(
|
|
8102
|
+
* // Get full tree from #app, 3 levels deep (default):
|
|
8103
|
+
* var info = bw.inspect('#app');
|
|
8104
|
+
*
|
|
8105
|
+
* // Shallow inspection (just the element, no children):
|
|
8106
|
+
* var info = bw.inspect('#my-carousel', 0);
|
|
8107
|
+
* console.log(info.handles); // ['next', 'prev', 'goToSlide']
|
|
8108
|
+
* console.log(info.type); // 'carousel'
|
|
8109
|
+
*
|
|
8110
|
+
* // Deep inspection for debugging:
|
|
8111
|
+
* console.log(JSON.stringify(bw.inspect('#app', 5), null, 2));
|
|
7730
8112
|
*/
|
|
7731
|
-
bw.inspect = function (target) {
|
|
7732
|
-
var el =
|
|
7733
|
-
if (!el)
|
|
7734
|
-
|
|
7735
|
-
|
|
8113
|
+
bw.inspect = function (target, depth) {
|
|
8114
|
+
var el = bw.el(target);
|
|
8115
|
+
if (!el && _is(target, 'string')) el = bw.$(target)[0];
|
|
8116
|
+
if (!el) return null;
|
|
8117
|
+
if (depth === undefined || depth === null) depth = 3;
|
|
8118
|
+
function walk(node, d) {
|
|
8119
|
+
if (!node) return null;
|
|
8120
|
+
// Skip non-element nodes (text, comment, etc.)
|
|
8121
|
+
if (node.nodeType !== 1) return null;
|
|
8122
|
+
var info = {
|
|
8123
|
+
tag: node.tagName ? node.tagName.toLowerCase() : '#text'
|
|
8124
|
+
};
|
|
8125
|
+
|
|
8126
|
+
// Identity
|
|
8127
|
+
if (node.id) info.id = node.id;
|
|
8128
|
+
var uuid = bw.getUUID(node);
|
|
8129
|
+
if (uuid) info.uuid = uuid;
|
|
8130
|
+
if (node._bw_type) info.type = node._bw_type;
|
|
8131
|
+
|
|
8132
|
+
// CSS classes (first 5 for readability)
|
|
8133
|
+
if (node.className && typeof node.className === 'string') {
|
|
8134
|
+
info.classes = node.className.split(' ').slice(0, 5).join(' ');
|
|
8135
|
+
}
|
|
8136
|
+
|
|
8137
|
+
// Bitwrench handle methods
|
|
8138
|
+
if (node.bw) {
|
|
8139
|
+
var handles = _keys(node.bw);
|
|
8140
|
+
if (handles.length > 0) info.handles = handles;
|
|
8141
|
+
}
|
|
8142
|
+
|
|
8143
|
+
// State
|
|
8144
|
+
if (node._bw_state) info.state = node._bw_state;
|
|
8145
|
+
if (node._bw_render) info.hasRender = true;
|
|
8146
|
+
if (node._bw_subs && node._bw_subs.length > 0) info.hasSubs = true;
|
|
8147
|
+
|
|
8148
|
+
// Refs
|
|
8149
|
+
if (node._bw_refs) info.refs = _keys(node._bw_refs);
|
|
8150
|
+
|
|
8151
|
+
// Children (recurse up to depth limit, max 50 children per level)
|
|
8152
|
+
if (d < depth && node.children && node.children.length > 0) {
|
|
8153
|
+
info.children = [];
|
|
8154
|
+
var max = Math.min(node.children.length, 50);
|
|
8155
|
+
for (var i = 0; i < max; i++) {
|
|
8156
|
+
var child = walk(node.children[i], d + 1);
|
|
8157
|
+
if (child) info.children.push(child);
|
|
8158
|
+
}
|
|
8159
|
+
if (node.children.length > 50) {
|
|
8160
|
+
info.children.push({
|
|
8161
|
+
tag: '...',
|
|
8162
|
+
count: node.children.length - 50
|
|
8163
|
+
});
|
|
8164
|
+
}
|
|
8165
|
+
}
|
|
8166
|
+
return info;
|
|
7736
8167
|
}
|
|
7737
|
-
|
|
7738
|
-
_cl('State:', el._bw_state || '(none)');
|
|
7739
|
-
_cl('Handle:', el.bw ? _keys(el.bw) : '(none)');
|
|
7740
|
-
_cl('Classes:', el.className);
|
|
7741
|
-
_cl('Refs:', el._bw_refs || '(none)');
|
|
7742
|
-
console.groupEnd();
|
|
7743
|
-
return el;
|
|
8168
|
+
return walk(el, 0);
|
|
7744
8169
|
};
|
|
7745
8170
|
bw.compile = function () {
|
|
7746
8171
|
throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.');
|
|
@@ -7979,34 +8404,45 @@
|
|
|
7979
8404
|
* so you can use `.map()`, `.filter()`, etc. directly. Accepts CSS selectors,
|
|
7980
8405
|
* single elements, NodeLists, or arrays.
|
|
7981
8406
|
*
|
|
8407
|
+
* With an optional second argument, applies content or a function to
|
|
8408
|
+
* every matched element (same apply rules as `bw.el()`):
|
|
8409
|
+
* - string/number: sets `el.textContent`
|
|
8410
|
+
* - function: calls `apply(el)` for each element
|
|
8411
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
8412
|
+
* - array: clears children, appends each item
|
|
8413
|
+
*
|
|
7982
8414
|
* @param {string|Element|Array} selector - CSS selector, element, or array
|
|
8415
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
7983
8416
|
* @returns {Array} Array of DOM elements
|
|
7984
8417
|
* @category DOM Selection
|
|
8418
|
+
* @see bw.el
|
|
7985
8419
|
* @example
|
|
7986
|
-
* bw.$('.card')
|
|
7987
|
-
* bw.$(
|
|
7988
|
-
* bw.$('.card'
|
|
8420
|
+
* bw.$('.card') // => [div.card, div.card, ...]
|
|
8421
|
+
* bw.$('.status', 'Online') // set text on all .status elements
|
|
8422
|
+
* bw.$('.card', function(el) { // apply function to each
|
|
8423
|
+
* el.style.opacity = '0.5';
|
|
8424
|
+
* })
|
|
7989
8425
|
*/
|
|
7990
8426
|
if (bw._isBrowser) {
|
|
7991
|
-
bw.$ = function (selector) {
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
if (_isA(selector))
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
if (
|
|
8002
|
-
|
|
8427
|
+
bw.$ = function (selector, apply) {
|
|
8428
|
+
var els;
|
|
8429
|
+
if (!selector) {
|
|
8430
|
+
els = [];
|
|
8431
|
+
} else if (_isA(selector)) {
|
|
8432
|
+
els = selector;
|
|
8433
|
+
} else if (selector.nodeType) {
|
|
8434
|
+
els = [selector];
|
|
8435
|
+
} else if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
8436
|
+
els = Array.from(selector);
|
|
8437
|
+
} else if (_is(selector, 'string')) {
|
|
8438
|
+
els = Array.from(document.querySelectorAll(selector));
|
|
8439
|
+
} else {
|
|
8440
|
+
els = [];
|
|
8003
8441
|
}
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
if (_is(selector, 'string')) {
|
|
8007
|
-
return Array.from(document.querySelectorAll(selector));
|
|
8442
|
+
if (apply !== undefined) {
|
|
8443
|
+
for (var i = 0; i < els.length; i++) _applyTo(els[i], apply);
|
|
8008
8444
|
}
|
|
8009
|
-
return
|
|
8445
|
+
return els;
|
|
8010
8446
|
};
|
|
8011
8447
|
|
|
8012
8448
|
// Convenience single element selector
|
|
@@ -8225,41 +8661,47 @@
|
|
|
8225
8661
|
};
|
|
8226
8662
|
|
|
8227
8663
|
/**
|
|
8228
|
-
* Toggle between primary and alternate palettes.
|
|
8664
|
+
* Toggle between primary and alternate theme palettes.
|
|
8229
8665
|
*
|
|
8230
|
-
* Adds/removes the `bw_theme_alt` class on the scoping element.
|
|
8666
|
+
* Adds/removes the `bw_theme_alt` class on the scoping element(s).
|
|
8231
8667
|
* Without a scope, toggles on `<html>` (global).
|
|
8232
|
-
* With a scope, toggles on
|
|
8668
|
+
* With a scope, toggles on ALL matching elements.
|
|
8233
8669
|
*
|
|
8234
|
-
* @param {string} [scope] -
|
|
8235
|
-
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
8670
|
+
* @param {string|Element} [scope] - Selector or element. Omit for global.
|
|
8671
|
+
* @returns {string} Active mode after toggle: 'primary' or 'alternate' (based on first element)
|
|
8236
8672
|
* @category CSS & Styling
|
|
8237
8673
|
* @see bw.applyStyles
|
|
8238
8674
|
* @see bw.clearStyles
|
|
8239
8675
|
* @example
|
|
8240
|
-
* bw.
|
|
8241
|
-
* bw.
|
|
8676
|
+
* bw.toggleThemeMode(); // global toggle on <html>
|
|
8677
|
+
* bw.toggleThemeMode('#my-dashboard'); // scoped toggle
|
|
8678
|
+
* bw.toggleThemeMode('.panel'); // toggle on ALL .panel elements
|
|
8242
8679
|
*/
|
|
8243
|
-
bw.
|
|
8680
|
+
bw.toggleThemeMode = function (scope) {
|
|
8244
8681
|
if (!bw._isBrowser) return 'primary';
|
|
8245
|
-
var
|
|
8682
|
+
var els;
|
|
8246
8683
|
if (scope) {
|
|
8247
|
-
|
|
8248
|
-
target = els[0];
|
|
8249
|
-
} else {
|
|
8250
|
-
target = document.documentElement;
|
|
8251
|
-
}
|
|
8252
|
-
if (!target) return 'primary';
|
|
8253
|
-
var hasAlt = target.classList.contains('bw_theme_alt');
|
|
8254
|
-
if (hasAlt) {
|
|
8255
|
-
target.classList.remove('bw_theme_alt');
|
|
8256
|
-
return 'primary';
|
|
8684
|
+
els = bw.$(scope);
|
|
8257
8685
|
} else {
|
|
8258
|
-
|
|
8259
|
-
|
|
8686
|
+
els = [document.documentElement];
|
|
8687
|
+
}
|
|
8688
|
+
if (!els.length) return 'primary';
|
|
8689
|
+
var mode;
|
|
8690
|
+
for (var i = 0; i < els.length; i++) {
|
|
8691
|
+
var hasAlt = els[i].classList.contains('bw_theme_alt');
|
|
8692
|
+
if (hasAlt) {
|
|
8693
|
+
els[i].classList.remove('bw_theme_alt');
|
|
8694
|
+
} else {
|
|
8695
|
+
els[i].classList.add('bw_theme_alt');
|
|
8696
|
+
}
|
|
8697
|
+
if (i === 0) mode = hasAlt ? 'primary' : 'alternate';
|
|
8260
8698
|
}
|
|
8699
|
+
return mode;
|
|
8261
8700
|
};
|
|
8262
8701
|
|
|
8702
|
+
// Alias — kept for one release cycle. Use bw.toggleThemeMode() instead.
|
|
8703
|
+
bw.toggleStyles = bw.toggleThemeMode;
|
|
8704
|
+
|
|
8263
8705
|
/**
|
|
8264
8706
|
* Remove injected styles for a given scope.
|
|
8265
8707
|
*
|
|
@@ -9359,6 +9801,57 @@
|
|
|
9359
9801
|
}
|
|
9360
9802
|
});
|
|
9361
9803
|
|
|
9804
|
+
/**
|
|
9805
|
+
* Query the BCCL component registry. Returns metadata about registered
|
|
9806
|
+
* component types -- their names and factory function names. Useful for
|
|
9807
|
+
* tooling, introspection, documentation generators, and auto-complete
|
|
9808
|
+
* systems (including MCP/AG-UI tool discovery).
|
|
9809
|
+
*
|
|
9810
|
+
* With no arguments, returns an array of all registered component types.
|
|
9811
|
+
* With a type name, returns metadata for that single type (or null if
|
|
9812
|
+
* the type is not registered).
|
|
9813
|
+
*
|
|
9814
|
+
* @param {string} [type] - Optional component type name to look up
|
|
9815
|
+
* @returns {Array<Object>|Object|null} Array of {type, factory} objects,
|
|
9816
|
+
* a single {type, factory} object, or null if the type is not found
|
|
9817
|
+
* @category Component
|
|
9818
|
+
* @see bw.make
|
|
9819
|
+
* @see bw.BCCL
|
|
9820
|
+
* @example
|
|
9821
|
+
* // List all available component types:
|
|
9822
|
+
* bw.catalog();
|
|
9823
|
+
* // => [{ type: 'card', factory: 'makeCard' },
|
|
9824
|
+
* // { type: 'button', factory: 'makeButton' }, ...]
|
|
9825
|
+
*
|
|
9826
|
+
* // Look up a specific type:
|
|
9827
|
+
* bw.catalog('accordion');
|
|
9828
|
+
* // => { type: 'accordion', factory: 'makeAccordion' }
|
|
9829
|
+
*
|
|
9830
|
+
* // Check if a type exists:
|
|
9831
|
+
* if (bw.catalog('chart')) { ... }
|
|
9832
|
+
*
|
|
9833
|
+
* // Get just the type names:
|
|
9834
|
+
* bw.catalog().map(function(c) { return c.type; });
|
|
9835
|
+
* // => ['card', 'button', 'container', 'row', ...]
|
|
9836
|
+
*/
|
|
9837
|
+
bw.catalog = function (type) {
|
|
9838
|
+
if (type) {
|
|
9839
|
+
var def = bw.BCCL[type];
|
|
9840
|
+
if (!def) return null;
|
|
9841
|
+
return {
|
|
9842
|
+
type: type,
|
|
9843
|
+
factory: def.make.name || 'make' + type.charAt(0).toUpperCase() + type.slice(1)
|
|
9844
|
+
};
|
|
9845
|
+
}
|
|
9846
|
+
return Object.keys(bw.BCCL).map(function (k) {
|
|
9847
|
+
var def = bw.BCCL[k];
|
|
9848
|
+
return {
|
|
9849
|
+
type: k,
|
|
9850
|
+
factory: def.make.name || 'make' + k.charAt(0).toUpperCase() + k.slice(1)
|
|
9851
|
+
};
|
|
9852
|
+
});
|
|
9853
|
+
};
|
|
9854
|
+
|
|
9362
9855
|
// Also attach to global in browsers
|
|
9363
9856
|
if (bw._isBrowser && typeof window !== 'undefined') {
|
|
9364
9857
|
window.bw = bw;
|