bitwrench 2.0.25 → 2.0.31
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 +92 -92
- 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,18 +1,18 @@
|
|
|
1
|
-
/*! bitwrench-lean v2.0.
|
|
1
|
+
/*! bitwrench-lean v2.0.31 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
/**
|
|
3
3
|
* Auto-generated version file from package.json
|
|
4
4
|
* DO NOT EDIT DIRECTLY - Use npm run generate-version
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const VERSION_INFO = {
|
|
8
|
-
version: '2.0.
|
|
8
|
+
version: '2.0.31',
|
|
9
9
|
name: 'bitwrench',
|
|
10
10
|
description: 'A library for javascript UI functions.',
|
|
11
11
|
license: 'BSD-2-Clause',
|
|
12
12
|
homepage: 'https://deftio.github.com/bitwrench/pages',
|
|
13
13
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
14
14
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
15
|
-
buildDate: '2026-
|
|
15
|
+
buildDate: '2026-04-12T07:56:29.791Z'
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -3948,7 +3948,6 @@ var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() =
|
|
|
3948
3948
|
// Console aliases use thin wrappers (not direct references) so that test
|
|
3949
3949
|
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
3950
3950
|
var _cw = function() { console.warn.apply(console, arguments); };
|
|
3951
|
-
var _cl = function() { console.log.apply(console, arguments); };
|
|
3952
3951
|
var _ce = function() { console.error.apply(console, arguments); };
|
|
3953
3952
|
|
|
3954
3953
|
/**
|
|
@@ -4085,61 +4084,105 @@ bw.uuid = function(prefix) {
|
|
|
4085
4084
|
};
|
|
4086
4085
|
|
|
4087
4086
|
/**
|
|
4088
|
-
* Look up a DOM element by ID
|
|
4089
|
-
*
|
|
4090
|
-
*
|
|
4091
|
-
*
|
|
4092
|
-
*
|
|
4093
|
-
*
|
|
4094
|
-
*
|
|
4095
|
-
*
|
|
4096
|
-
*
|
|
4097
|
-
*
|
|
4098
|
-
*
|
|
4099
|
-
*
|
|
4100
|
-
*
|
|
4101
|
-
*
|
|
4102
|
-
*
|
|
4087
|
+
* Look up a single DOM element by ID, CSS selector, UUID, or element ref.
|
|
4088
|
+
* Optionally apply content or a function to the resolved element.
|
|
4089
|
+
*
|
|
4090
|
+
* Resolution order for string targets:
|
|
4091
|
+
* 1. Check `bw._nodeMap[id]` cache (O(1), stale entries auto-pruned)
|
|
4092
|
+
* 2. `document.getElementById(id)`
|
|
4093
|
+
* 3. `document.querySelector(id)` for selectors starting with # or .
|
|
4094
|
+
* 4. Class-based lookup for `bw_uuid_*` tokens
|
|
4095
|
+
*
|
|
4096
|
+
* With one argument, returns the element (or null). With two arguments,
|
|
4097
|
+
* applies the second argument to the element and returns the element:
|
|
4098
|
+
* - string/number: sets `el.textContent`
|
|
4099
|
+
* - function: calls `apply(el)`, returns el
|
|
4100
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
4101
|
+
* - array: clears children, appends each item (string -> text node, TACO -> element)
|
|
4102
|
+
*
|
|
4103
|
+
* @param {string|Element} target - Element ref, ID, CSS selector, or bw_uuid_* class
|
|
4104
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
4103
4105
|
* @returns {Element|null} The DOM element, or null if not found
|
|
4104
|
-
* @category
|
|
4106
|
+
* @category DOM Selection
|
|
4107
|
+
* @see bw.$
|
|
4108
|
+
* @see bw.patch
|
|
4109
|
+
* @example
|
|
4110
|
+
* bw.el('#title') // lookup
|
|
4111
|
+
* bw.el('#title', 'Hello') // set text content
|
|
4112
|
+
* bw.el('#app', { t: 'h1', c: 'Hi' }) // mount TACO
|
|
4113
|
+
* bw.el('.card', function(el) { // apply function
|
|
4114
|
+
* el.style.opacity = '0.5';
|
|
4115
|
+
* })
|
|
4105
4116
|
*/
|
|
4106
|
-
bw.
|
|
4107
|
-
//
|
|
4108
|
-
|
|
4109
|
-
if (!
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
if (cached
|
|
4117
|
-
|
|
4117
|
+
bw.el = function(target, apply) {
|
|
4118
|
+
// Resolve target to element
|
|
4119
|
+
var el;
|
|
4120
|
+
if (!_is(target, 'string')) {
|
|
4121
|
+
el = target || null;
|
|
4122
|
+
} else if (!target || !bw._isBrowser) {
|
|
4123
|
+
el = null;
|
|
4124
|
+
} else {
|
|
4125
|
+
// 1. Check cache
|
|
4126
|
+
var cached = bw._nodeMap[target];
|
|
4127
|
+
if (cached) {
|
|
4128
|
+
if (cached.parentNode !== null) {
|
|
4129
|
+
el = cached;
|
|
4130
|
+
} else {
|
|
4131
|
+
delete bw._nodeMap[target];
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
if (!el) {
|
|
4135
|
+
// 2. getElementById
|
|
4136
|
+
el = document.getElementById(target);
|
|
4137
|
+
// 3. querySelector for CSS selectors
|
|
4138
|
+
if (!el && (target.charAt(0) === '#' || target.charAt(0) === '.')) {
|
|
4139
|
+
el = document.querySelector(target);
|
|
4140
|
+
}
|
|
4141
|
+
// 4. bw_uuid_* class lookup
|
|
4142
|
+
if (!el && target.indexOf('bw_uuid_') === 0) {
|
|
4143
|
+
el = document.querySelector('.' + target);
|
|
4144
|
+
}
|
|
4145
|
+
// 5. Cache result
|
|
4146
|
+
if (el) bw._nodeMap[target] = el;
|
|
4118
4147
|
}
|
|
4119
|
-
// Stale — remove and fall through
|
|
4120
|
-
delete bw._nodeMap[id];
|
|
4121
4148
|
}
|
|
4122
4149
|
|
|
4123
|
-
//
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
// 3. Try querySelector for CSS selectors (starts with # or .)
|
|
4127
|
-
if (!el && (id.charAt(0) === '#' || id.charAt(0) === '.')) {
|
|
4128
|
-
el = document.querySelector(id);
|
|
4129
|
-
}
|
|
4150
|
+
// Apply (if provided and element found)
|
|
4151
|
+
if (el && apply !== undefined) _applyTo(el, apply);
|
|
4130
4152
|
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
el = document.querySelector('.' + id);
|
|
4134
|
-
}
|
|
4153
|
+
return el;
|
|
4154
|
+
};
|
|
4135
4155
|
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4156
|
+
/**
|
|
4157
|
+
* Internal: apply content or function to a DOM element.
|
|
4158
|
+
* Shared by bw.el() and bw.$().
|
|
4159
|
+
* @private
|
|
4160
|
+
*/
|
|
4161
|
+
function _applyTo(el, apply) {
|
|
4162
|
+
if (_is(apply, 'function')) {
|
|
4163
|
+
apply(el);
|
|
4164
|
+
} else if (_isA(apply)) {
|
|
4165
|
+
el.innerHTML = '';
|
|
4166
|
+
apply.forEach(function(item) {
|
|
4167
|
+
if (item != null) {
|
|
4168
|
+
if (_is(item, 'object') && item.t) {
|
|
4169
|
+
el.appendChild(bw.createDOM(item));
|
|
4170
|
+
} else {
|
|
4171
|
+
el.appendChild(document.createTextNode(String(item)));
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
});
|
|
4175
|
+
} else if (_is(apply, 'object') && apply !== null && apply.t) {
|
|
4176
|
+
el.innerHTML = '';
|
|
4177
|
+
el.appendChild(bw.createDOM(apply));
|
|
4178
|
+
} else {
|
|
4179
|
+
el.textContent = String(apply);
|
|
4139
4180
|
}
|
|
4181
|
+
}
|
|
4140
4182
|
|
|
4141
|
-
|
|
4142
|
-
|
|
4183
|
+
// Internal alias — kept for one release cycle (v2.0.26).
|
|
4184
|
+
// Will be removed in v2.0.27. Use bw.el() instead.
|
|
4185
|
+
bw._el = bw.el;
|
|
4143
4186
|
|
|
4144
4187
|
/**
|
|
4145
4188
|
* Register a DOM element in the node cache under one or more keys.
|
|
@@ -4203,6 +4246,12 @@ var _BW_LC = 'bw_lc';
|
|
|
4203
4246
|
*/
|
|
4204
4247
|
var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
|
|
4205
4248
|
|
|
4249
|
+
/**
|
|
4250
|
+
* SVG namespace URI for createElementNS.
|
|
4251
|
+
* @private
|
|
4252
|
+
*/
|
|
4253
|
+
var _SVG_NS = 'http://www.w3.org/2000/svg';
|
|
4254
|
+
|
|
4206
4255
|
/**
|
|
4207
4256
|
* Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
|
|
4208
4257
|
*
|
|
@@ -4257,9 +4306,10 @@ bw.getUUID = function(tacoOrElement) {
|
|
|
4257
4306
|
if (!tacoOrElement) return null;
|
|
4258
4307
|
|
|
4259
4308
|
var classStr;
|
|
4260
|
-
// DOM element: check className
|
|
4309
|
+
// DOM element: check className (SVG elements use getAttribute for string value)
|
|
4261
4310
|
if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
|
|
4262
|
-
classStr = tacoOrElement.className
|
|
4311
|
+
classStr = typeof tacoOrElement.className === 'string'
|
|
4312
|
+
? tacoOrElement.className : (tacoOrElement.getAttribute('class') || '');
|
|
4263
4313
|
}
|
|
4264
4314
|
// TACO object: check a.class
|
|
4265
4315
|
else if (tacoOrElement.a && _is(tacoOrElement.a.class, 'string')) {
|
|
@@ -4528,7 +4578,7 @@ bw.htmlPage = function(opts) {
|
|
|
4528
4578
|
var fnCounterBefore = bw._fnIDCounter;
|
|
4529
4579
|
|
|
4530
4580
|
// Render body content
|
|
4531
|
-
var bodyHTML
|
|
4581
|
+
var bodyHTML;
|
|
4532
4582
|
if (_is(body, 'string')) {
|
|
4533
4583
|
bodyHTML = body;
|
|
4534
4584
|
} else {
|
|
@@ -4699,9 +4749,11 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4699
4749
|
}
|
|
4700
4750
|
|
|
4701
4751
|
const { t: tag, a: attrs = {}, c: content, o: opts = {} } = taco;
|
|
4702
|
-
|
|
4703
|
-
//
|
|
4704
|
-
|
|
4752
|
+
|
|
4753
|
+
// SVG namespace: detect SVG context and thread through children.
|
|
4754
|
+
// {t:'svg'} starts SVG context; foreignObject children revert to HTML.
|
|
4755
|
+
var svgCtx = options._svgCtx || (tag === 'svg');
|
|
4756
|
+
var el = svgCtx ? document.createElementNS(_SVG_NS, tag) : document.createElement(tag);
|
|
4705
4757
|
|
|
4706
4758
|
// Set attributes
|
|
4707
4759
|
for (const [key, value] of Object.entries(attrs)) {
|
|
@@ -4712,9 +4764,11 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4712
4764
|
Object.assign(el.style, value);
|
|
4713
4765
|
} else if (key === 'class') {
|
|
4714
4766
|
// Handle class as array or string
|
|
4767
|
+
// SVG elements use SVGAnimatedString for className, so use setAttribute
|
|
4715
4768
|
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
4716
4769
|
if (classStr) {
|
|
4717
|
-
el.
|
|
4770
|
+
if (svgCtx) el.setAttribute('class', classStr);
|
|
4771
|
+
else el.className = classStr;
|
|
4718
4772
|
}
|
|
4719
4773
|
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
4720
4774
|
// Event handlers
|
|
@@ -4735,11 +4789,17 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4735
4789
|
// Add children, building _bw_refs for fast parent→child access.
|
|
4736
4790
|
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
4737
4791
|
// so o.render functions can access them without any DOM lookup.
|
|
4792
|
+
// SVG: foreignObject children revert to HTML namespace; otherwise inherit.
|
|
4793
|
+
var childOpts = options;
|
|
4794
|
+
var childSvgCtx = svgCtx && tag !== 'foreignObject';
|
|
4795
|
+
if (childSvgCtx !== (options._svgCtx || false)) {
|
|
4796
|
+
childOpts = Object.assign({}, options, {_svgCtx: childSvgCtx || undefined});
|
|
4797
|
+
}
|
|
4738
4798
|
if (content != null) {
|
|
4739
4799
|
if (_isA(content)) {
|
|
4740
4800
|
content.forEach(child => {
|
|
4741
4801
|
if (child != null) {
|
|
4742
|
-
var childEl = bw.createDOM(child,
|
|
4802
|
+
var childEl = bw.createDOM(child, childOpts);
|
|
4743
4803
|
el.appendChild(childEl);
|
|
4744
4804
|
// Build local refs for addressable children
|
|
4745
4805
|
var childRefId = (child && child.a) ? (child.a.id || bw.getUUID(child)) : null;
|
|
@@ -4762,7 +4822,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4762
4822
|
// Raw HTML content — inject via innerHTML
|
|
4763
4823
|
el.innerHTML = content.v;
|
|
4764
4824
|
} else if (_is(content, 'object') && content.t) {
|
|
4765
|
-
var childEl = bw.createDOM(content,
|
|
4825
|
+
var childEl = bw.createDOM(content, childOpts);
|
|
4766
4826
|
el.appendChild(childEl);
|
|
4767
4827
|
var childRefId = content.a ? (content.a.id || bw.getUUID(content)) : null;
|
|
4768
4828
|
if (childRefId) {
|
|
@@ -4788,13 +4848,21 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4788
4848
|
}
|
|
4789
4849
|
|
|
4790
4850
|
// Register UUID class in node cache (bw_uuid_* tokens in class string)
|
|
4791
|
-
|
|
4792
|
-
|
|
4851
|
+
// SVG elements have SVGAnimatedString for className; use getAttribute instead
|
|
4852
|
+
var clsStr = svgCtx ? (el.getAttribute('class') || '') : el.className;
|
|
4853
|
+
if (clsStr) {
|
|
4854
|
+
var uuidMatch = clsStr.match(_UUID_RE);
|
|
4793
4855
|
if (uuidMatch) {
|
|
4794
4856
|
bw._nodeMap[uuidMatch[0]] = el;
|
|
4795
4857
|
}
|
|
4796
4858
|
}
|
|
4797
4859
|
|
|
4860
|
+
// Store component type metadata (e.g., 'card', 'tabs') for introspection.
|
|
4861
|
+
// BCCL factories set o.type; custom components can too.
|
|
4862
|
+
if (opts.type) {
|
|
4863
|
+
el._bw_type = opts.type;
|
|
4864
|
+
}
|
|
4865
|
+
|
|
4798
4866
|
// Handle lifecycle hooks and state
|
|
4799
4867
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
4800
4868
|
// Ensure element has a UUID class for identity
|
|
@@ -4824,11 +4892,13 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4824
4892
|
|
|
4825
4893
|
if (mountFn) {
|
|
4826
4894
|
if (document.body.contains(el)) {
|
|
4827
|
-
mountFn(el, el._bw_state || {});
|
|
4895
|
+
try { mountFn(el, el._bw_state || {}); }
|
|
4896
|
+
catch (e) { _cw('o.mounted error: ' + e.message); }
|
|
4828
4897
|
} else {
|
|
4829
4898
|
requestAnimationFrame(() => {
|
|
4830
4899
|
if (document.body.contains(el)) {
|
|
4831
|
-
mountFn(el, el._bw_state || {});
|
|
4900
|
+
try { mountFn(el, el._bw_state || {}); }
|
|
4901
|
+
catch (e) { _cw('o.mounted error: ' + e.message); }
|
|
4832
4902
|
}
|
|
4833
4903
|
});
|
|
4834
4904
|
}
|
|
@@ -4837,7 +4907,8 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4837
4907
|
// Store unmount callback keyed by UUID class
|
|
4838
4908
|
if (opts.unmount) {
|
|
4839
4909
|
bw._unmountCallbacks.set(uuid, () => {
|
|
4840
|
-
opts.unmount(el, el._bw_state || {});
|
|
4910
|
+
try { opts.unmount(el, el._bw_state || {}); }
|
|
4911
|
+
catch (e) { _cw('o.unmount error: ' + e.message); }
|
|
4841
4912
|
});
|
|
4842
4913
|
}
|
|
4843
4914
|
}
|
|
@@ -4856,24 +4927,25 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4856
4927
|
}
|
|
4857
4928
|
|
|
4858
4929
|
// Slot declarations: auto-generate setX/getX pairs
|
|
4930
|
+
// The target element is cached at creation time to avoid repeated
|
|
4931
|
+
// querySelector calls on every get/set invocation.
|
|
4859
4932
|
if (opts.slots) {
|
|
4860
4933
|
for (var sk in opts.slots) {
|
|
4861
4934
|
if (_hop.call(opts.slots, sk)) {
|
|
4862
4935
|
(function(name, selector) {
|
|
4936
|
+
var target = el.querySelector(selector);
|
|
4863
4937
|
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
4864
4938
|
el.bw['set' + cap] = function(value) {
|
|
4865
|
-
|
|
4866
|
-
if (!t) return;
|
|
4939
|
+
if (!target) return;
|
|
4867
4940
|
if (value != null && typeof value === 'object' && value.t) {
|
|
4868
|
-
|
|
4869
|
-
|
|
4941
|
+
target.innerHTML = '';
|
|
4942
|
+
target.appendChild(bw.createDOM(value));
|
|
4870
4943
|
} else {
|
|
4871
|
-
|
|
4944
|
+
target.textContent = (value != null) ? String(value) : '';
|
|
4872
4945
|
}
|
|
4873
4946
|
};
|
|
4874
4947
|
el.bw['get' + cap] = function() {
|
|
4875
|
-
|
|
4876
|
-
return t ? t.textContent : '';
|
|
4948
|
+
return target ? target.textContent : '';
|
|
4877
4949
|
};
|
|
4878
4950
|
})(sk, opts.slots[sk]);
|
|
4879
4951
|
}
|
|
@@ -4914,7 +4986,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
4914
4986
|
}
|
|
4915
4987
|
|
|
4916
4988
|
// Get target element (use cache-backed lookup)
|
|
4917
|
-
const targetEl = bw.
|
|
4989
|
+
const targetEl = bw.el(target);
|
|
4918
4990
|
|
|
4919
4991
|
if (!targetEl) {
|
|
4920
4992
|
_ce('bw.DOM: Target element not found:', target);
|
|
@@ -5017,7 +5089,8 @@ bw.cleanup = function(element) {
|
|
|
5017
5089
|
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
5018
5090
|
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
5019
5091
|
uuidEls.forEach(function(uel) {
|
|
5020
|
-
var
|
|
5092
|
+
var uc = typeof uel.className === 'string' ? uel.className : (uel.getAttribute('class') || '');
|
|
5093
|
+
var m = uc && uc.match(_UUID_RE);
|
|
5021
5094
|
if (m) delete bw._nodeMap[m[0]];
|
|
5022
5095
|
});
|
|
5023
5096
|
|
|
@@ -5103,9 +5176,10 @@ bw.cleanup = function(element) {
|
|
|
5103
5176
|
* bw.update(el); // re-renders, emits bw:statechange
|
|
5104
5177
|
*/
|
|
5105
5178
|
bw.update = function(target) {
|
|
5106
|
-
var el = bw.
|
|
5179
|
+
var el = bw.el(target);
|
|
5107
5180
|
if (el && el._bw_render) {
|
|
5108
|
-
el._bw_render(el, el._bw_state || {});
|
|
5181
|
+
try { el._bw_render(el, el._bw_state || {}); }
|
|
5182
|
+
catch (e) { _cw('o.render error: ' + e.message); }
|
|
5109
5183
|
bw.emit(el, 'statechange', el._bw_state);
|
|
5110
5184
|
}
|
|
5111
5185
|
return el || null;
|
|
@@ -5132,7 +5206,7 @@ bw.update = function(target) {
|
|
|
5132
5206
|
* bw.patch('info', { t: 'em', c: 'new' }); // replace children with TACO
|
|
5133
5207
|
*/
|
|
5134
5208
|
bw.patch = function(id, content, attr) {
|
|
5135
|
-
var el = bw.
|
|
5209
|
+
var el = bw.el(id);
|
|
5136
5210
|
if (!el) return null;
|
|
5137
5211
|
|
|
5138
5212
|
if (attr) {
|
|
@@ -5204,7 +5278,7 @@ bw.patchAll = function(patches) {
|
|
|
5204
5278
|
* // Dispatches CustomEvent 'bw:statechange' on the element
|
|
5205
5279
|
*/
|
|
5206
5280
|
bw.emit = function(target, eventName, detail) {
|
|
5207
|
-
var el = bw.
|
|
5281
|
+
var el = bw.el(target);
|
|
5208
5282
|
if (el) {
|
|
5209
5283
|
el.dispatchEvent(new CustomEvent('bw:' + eventName, {
|
|
5210
5284
|
bubbles: true,
|
|
@@ -5233,7 +5307,7 @@ bw.emit = function(target, eventName, detail) {
|
|
|
5233
5307
|
* });
|
|
5234
5308
|
*/
|
|
5235
5309
|
bw.on = function(target, eventName, handler) {
|
|
5236
|
-
var el = bw.
|
|
5310
|
+
var el = bw.el(target);
|
|
5237
5311
|
if (el) {
|
|
5238
5312
|
el.addEventListener('bw:' + eventName, function(e) {
|
|
5239
5313
|
handler(e.detail, e);
|
|
@@ -5260,23 +5334,38 @@ bw.on = function(target, eventName, handler) {
|
|
|
5260
5334
|
*
|
|
5261
5335
|
* @param {string} topic - Topic name (plain string, no prefix)
|
|
5262
5336
|
* @param {*} [detail] - Data to pass to subscribers
|
|
5263
|
-
* @returns {number} Count of successfully called subscribers
|
|
5337
|
+
* @returns {number} Count of successfully called subscribers (including wildcard matches)
|
|
5264
5338
|
* @category Pub/Sub
|
|
5265
5339
|
* @see bw.sub
|
|
5266
5340
|
* @example
|
|
5267
5341
|
* bw.pub('score:updated', { player: 'X', score: 10 });
|
|
5342
|
+
* // Wildcard subscribers matching 'score:*' will also fire
|
|
5268
5343
|
*/
|
|
5269
5344
|
bw.pub = function(topic, detail) {
|
|
5270
|
-
var subs = bw._topics[topic];
|
|
5271
|
-
if (!subs || subs.length === 0) return 0;
|
|
5272
|
-
var snapshot = subs.slice(); // safe against unsub during iteration
|
|
5273
5345
|
var called = 0;
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5346
|
+
// Exact-match subscribers
|
|
5347
|
+
var subs = bw._topics[topic];
|
|
5348
|
+
if (subs && subs.length > 0) {
|
|
5349
|
+
var snapshot = subs.slice();
|
|
5350
|
+
for (var i = 0; i < snapshot.length; i++) {
|
|
5351
|
+
try { snapshot[i].handler(detail, topic); called++; }
|
|
5352
|
+
catch (err) { _cw('bw.pub: subscriber error on topic "' + topic + '":', err); }
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
// Wildcard subscribers -- patterns ending with '*'
|
|
5356
|
+
var keys = Object.keys(bw._topics);
|
|
5357
|
+
for (var k = 0; k < keys.length; k++) {
|
|
5358
|
+
var pat = keys[k];
|
|
5359
|
+
if (pat.charAt(pat.length - 1) !== '*') continue;
|
|
5360
|
+
var prefix = pat.slice(0, -1); // strip trailing '*'
|
|
5361
|
+
if (topic.length >= prefix.length && topic.substring(0, prefix.length) === prefix && topic !== pat) {
|
|
5362
|
+
var wsubs = bw._topics[pat];
|
|
5363
|
+
if (!wsubs) continue;
|
|
5364
|
+
var wsnap = wsubs.slice();
|
|
5365
|
+
for (var w = 0; w < wsnap.length; w++) {
|
|
5366
|
+
try { wsnap[w].handler(detail, topic); called++; }
|
|
5367
|
+
catch (err) { _cw('bw.pub: wildcard subscriber error on "' + pat + '" for topic "' + topic + '":', err); }
|
|
5368
|
+
}
|
|
5280
5369
|
}
|
|
5281
5370
|
}
|
|
5282
5371
|
return called;
|
|
@@ -5285,12 +5374,17 @@ bw.pub = function(topic, detail) {
|
|
|
5285
5374
|
/**
|
|
5286
5375
|
* Subscribe to a topic. Returns an unsub() function.
|
|
5287
5376
|
*
|
|
5288
|
-
*
|
|
5377
|
+
* Supports wildcard patterns: a topic ending in `*` matches any published
|
|
5378
|
+
* topic that starts with the prefix before the `*`. For example,
|
|
5379
|
+
* `'agui:*'` matches `'agui:ready'`, `'agui:error'`, etc. The handler
|
|
5380
|
+
* receives `(detail, topic)` so it can distinguish which topic fired.
|
|
5381
|
+
*
|
|
5382
|
+
* Optional third argument ties the subscription to a DOM element's lifecycle --
|
|
5289
5383
|
* when `bw.cleanup()` is called on that element, the subscription is automatically
|
|
5290
5384
|
* removed, preventing memory leaks.
|
|
5291
5385
|
*
|
|
5292
|
-
* @param {string} topic - Topic name
|
|
5293
|
-
* @param {Function} handler - Called with (detail) on each publish
|
|
5386
|
+
* @param {string} topic - Topic name, or wildcard pattern ending in '*'
|
|
5387
|
+
* @param {Function} handler - Called with (detail, topic) on each publish
|
|
5294
5388
|
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
5295
5389
|
* @returns {Function} Call to unsubscribe
|
|
5296
5390
|
* @category Pub/Sub
|
|
@@ -5301,6 +5395,11 @@ bw.pub = function(topic, detail) {
|
|
|
5301
5395
|
* console.log(detail.player, 'scored', detail.score);
|
|
5302
5396
|
* });
|
|
5303
5397
|
* // Later: unsub() to stop listening
|
|
5398
|
+
*
|
|
5399
|
+
* // Wildcard: listen to all 'agui:' topics
|
|
5400
|
+
* bw.sub('agui:*', function(detail, topic) {
|
|
5401
|
+
* console.log('Got', topic, detail);
|
|
5402
|
+
* });
|
|
5304
5403
|
*/
|
|
5305
5404
|
bw.sub = function(topic, handler, el) {
|
|
5306
5405
|
var id = ++bw._subIdCounter;
|
|
@@ -5352,6 +5451,37 @@ bw.unsub = function(topic, handler) {
|
|
|
5352
5451
|
return removed;
|
|
5353
5452
|
};
|
|
5354
5453
|
|
|
5454
|
+
/**
|
|
5455
|
+
* Subscribe to a topic for a single event only. The subscription is
|
|
5456
|
+
* automatically removed after the first publish. Equivalent to manually
|
|
5457
|
+
* calling unsub() inside a bw.sub() handler, but avoids the common bug
|
|
5458
|
+
* of forgetting to unsubscribe.
|
|
5459
|
+
*
|
|
5460
|
+
* @param {string} topic - Topic name
|
|
5461
|
+
* @param {Function} handler - Called once with (detail) on the next publish
|
|
5462
|
+
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
5463
|
+
* @returns {Function} Call to cancel the subscription before it fires
|
|
5464
|
+
* @category Pub/Sub
|
|
5465
|
+
* @see bw.sub
|
|
5466
|
+
* @see bw.pub
|
|
5467
|
+
* @example
|
|
5468
|
+
* bw.once('data:loaded', function(detail) {
|
|
5469
|
+
* console.log('Received:', detail);
|
|
5470
|
+
* // No need to unsubscribe -- already done automatically
|
|
5471
|
+
* });
|
|
5472
|
+
*
|
|
5473
|
+
* // Cancel before it fires:
|
|
5474
|
+
* var cancel = bw.once('timeout', handler);
|
|
5475
|
+
* cancel(); // handler will never be called
|
|
5476
|
+
*/
|
|
5477
|
+
bw.once = function(topic, handler, el) {
|
|
5478
|
+
var unsub = bw.sub(topic, function(detail) {
|
|
5479
|
+
unsub();
|
|
5480
|
+
handler(detail);
|
|
5481
|
+
}, el);
|
|
5482
|
+
return unsub;
|
|
5483
|
+
};
|
|
5484
|
+
|
|
5355
5485
|
// ===================================================================================
|
|
5356
5486
|
// Function Registry (revived from v1 for string dispatch contexts)
|
|
5357
5487
|
// ===================================================================================
|
|
@@ -5592,7 +5722,7 @@ bw.component = function() { throw new Error('bw.component() removed in v2.0.19.
|
|
|
5592
5722
|
* };
|
|
5593
5723
|
*/
|
|
5594
5724
|
bw.message = function(target, action, data) {
|
|
5595
|
-
var el = bw.
|
|
5725
|
+
var el = bw.el(target);
|
|
5596
5726
|
if (!el) el = bw.$('.' + target)[0];
|
|
5597
5727
|
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
5598
5728
|
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
@@ -5602,6 +5732,207 @@ bw.message = function(target, action, data) {
|
|
|
5602
5732
|
return true;
|
|
5603
5733
|
};
|
|
5604
5734
|
|
|
5735
|
+
/**
|
|
5736
|
+
* Collect form data from all input, select, and textarea elements within a
|
|
5737
|
+
* container. Each element's `name` attribute (or `id` if no name) becomes a
|
|
5738
|
+
* key in the returned object. This provides a lightweight alternative to the
|
|
5739
|
+
* browser FormData API that returns a plain object suitable for JSON
|
|
5740
|
+
* serialization or bw.pub().
|
|
5741
|
+
*
|
|
5742
|
+
* Handles all standard HTML form controls:
|
|
5743
|
+
* - text/number/email/etc inputs: string value
|
|
5744
|
+
* - checkboxes: boolean (true/false)
|
|
5745
|
+
* - radio buttons: string value of the checked radio (unchecked groups omitted)
|
|
5746
|
+
* - multi-select: array of selected option values
|
|
5747
|
+
* - textarea: string value
|
|
5748
|
+
*
|
|
5749
|
+
* Elements without both `name` and `id` attributes are silently skipped.
|
|
5750
|
+
*
|
|
5751
|
+
* @param {string|Element} target - CSS selector, UUID string, or DOM element
|
|
5752
|
+
* @returns {Object} Plain object mapping field names to values
|
|
5753
|
+
* @category Component
|
|
5754
|
+
* @see bw.makeForm
|
|
5755
|
+
* @see bw.makeInput
|
|
5756
|
+
* @example
|
|
5757
|
+
* // Given a form with name="email" input and name="agree" checkbox:
|
|
5758
|
+
* var data = bw.formData('#signup-form');
|
|
5759
|
+
* // => { email: 'user@example.com', agree: true }
|
|
5760
|
+
*
|
|
5761
|
+
* // Collect and publish in one step:
|
|
5762
|
+
* bw.pub('form:submit', bw.formData('#my-form'));
|
|
5763
|
+
*
|
|
5764
|
+
* // Works with any container, not just <form>:
|
|
5765
|
+
* bw.pub('settings:changed', bw.formData('.settings-panel'));
|
|
5766
|
+
*/
|
|
5767
|
+
bw.formData = function(target) {
|
|
5768
|
+
var el = bw.el(target);
|
|
5769
|
+
if (!el) return {};
|
|
5770
|
+
var result = {};
|
|
5771
|
+
var inputs = el.querySelectorAll('input, select, textarea');
|
|
5772
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
5773
|
+
var inp = inputs[i];
|
|
5774
|
+
var key = inp.name || inp.id;
|
|
5775
|
+
if (!key) continue;
|
|
5776
|
+
if (inp.type === 'checkbox') {
|
|
5777
|
+
result[key] = inp.checked;
|
|
5778
|
+
} else if (inp.type === 'radio') {
|
|
5779
|
+
if (inp.checked) result[key] = inp.value;
|
|
5780
|
+
} else if (inp.tagName === 'SELECT' && inp.multiple) {
|
|
5781
|
+
result[key] = [];
|
|
5782
|
+
for (var j = 0; j < inp.options.length; j++) {
|
|
5783
|
+
if (inp.options[j].selected) result[key].push(inp.options[j].value);
|
|
5784
|
+
}
|
|
5785
|
+
} else {
|
|
5786
|
+
result[key] = inp.value;
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
return result;
|
|
5790
|
+
};
|
|
5791
|
+
|
|
5792
|
+
// ===================================================================================
|
|
5793
|
+
// bw.jsonPatch() — RFC 6902 JSON Patch on plain objects
|
|
5794
|
+
// ===================================================================================
|
|
5795
|
+
|
|
5796
|
+
/**
|
|
5797
|
+
* Apply RFC 6902 JSON Patch operations to a plain object.
|
|
5798
|
+
*
|
|
5799
|
+
* Supported operations: add, remove, replace, move, copy, test.
|
|
5800
|
+
* Paths use JSON Pointer (RFC 6901) notation: `/foo/bar/0`.
|
|
5801
|
+
* Mutates the target object in place and returns it.
|
|
5802
|
+
*
|
|
5803
|
+
* @param {Object} obj - Target object to patch
|
|
5804
|
+
* @param {Array<Object>} ops - Array of patch operations
|
|
5805
|
+
* @param {string} ops[].op - Operation: 'add', 'remove', 'replace', 'move', 'copy', 'test'
|
|
5806
|
+
* @param {string} ops[].path - JSON Pointer path (e.g. '/a/b/0')
|
|
5807
|
+
* @param {*} [ops[].value] - Value for add/replace/test
|
|
5808
|
+
* @param {string} [ops[].from] - Source path for move/copy
|
|
5809
|
+
* @returns {Object} The patched object (same reference)
|
|
5810
|
+
* @throws {Error} On invalid op, missing path, test failure, or path not found for remove
|
|
5811
|
+
* @category Data Utilities
|
|
5812
|
+
* @see bw.patch
|
|
5813
|
+
* @example
|
|
5814
|
+
* var obj = { a: 1, b: { c: 2 } };
|
|
5815
|
+
* bw.jsonPatch(obj, [
|
|
5816
|
+
* { op: 'replace', path: '/a', value: 10 },
|
|
5817
|
+
* { op: 'add', path: '/b/d', value: 3 },
|
|
5818
|
+
* { op: 'remove', path: '/b/c' }
|
|
5819
|
+
* ]);
|
|
5820
|
+
* // obj => { a: 10, b: { d: 3 } }
|
|
5821
|
+
*/
|
|
5822
|
+
bw.jsonPatch = function(obj, ops) {
|
|
5823
|
+
if (!_isA(ops)) return obj;
|
|
5824
|
+
|
|
5825
|
+
// Parse JSON Pointer path to array of keys
|
|
5826
|
+
function parsePath(path) {
|
|
5827
|
+
if (path === '') return [];
|
|
5828
|
+
if (path.charAt(0) !== '/') throw new Error('Invalid JSON Pointer: ' + path);
|
|
5829
|
+
return path.slice(1).split('/').map(function(s) {
|
|
5830
|
+
return s.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
5831
|
+
});
|
|
5832
|
+
}
|
|
5833
|
+
|
|
5834
|
+
// Walk to parent of final key; return { parent, key }
|
|
5835
|
+
function resolve(root, keys) {
|
|
5836
|
+
var parent = root;
|
|
5837
|
+
for (var i = 0; i < keys.length - 1; i++) {
|
|
5838
|
+
var k = _isA(parent) ? parseInt(keys[i], 10) : keys[i];
|
|
5839
|
+
if (parent[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
5840
|
+
parent = parent[k];
|
|
5841
|
+
}
|
|
5842
|
+
return { parent: parent, key: _isA(parent) ? parseInt(keys[keys.length - 1], 10) : keys[keys.length - 1] };
|
|
5843
|
+
}
|
|
5844
|
+
|
|
5845
|
+
// Get value at path
|
|
5846
|
+
function getVal(root, keys) {
|
|
5847
|
+
var cur = root;
|
|
5848
|
+
for (var i = 0; i < keys.length; i++) {
|
|
5849
|
+
var k = _isA(cur) ? parseInt(keys[i], 10) : keys[i];
|
|
5850
|
+
if (cur[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
5851
|
+
cur = cur[k];
|
|
5852
|
+
}
|
|
5853
|
+
return cur;
|
|
5854
|
+
}
|
|
5855
|
+
|
|
5856
|
+
for (var i = 0; i < ops.length; i++) {
|
|
5857
|
+
var op = ops[i];
|
|
5858
|
+
if (!op.op || !_is(op.path, 'string')) throw new Error('Invalid patch operation at index ' + i);
|
|
5859
|
+
var keys = parsePath(op.path);
|
|
5860
|
+
|
|
5861
|
+
var r, val, fromKeys, fr, tr, cr;
|
|
5862
|
+
switch (op.op) {
|
|
5863
|
+
case 'add': {
|
|
5864
|
+
if (keys.length === 0) throw new Error('Cannot add to root');
|
|
5865
|
+
r = resolve(obj, keys);
|
|
5866
|
+
if (_isA(r.parent) && r.key <= r.parent.length) {
|
|
5867
|
+
r.parent.splice(r.key, 0, op.value);
|
|
5868
|
+
} else {
|
|
5869
|
+
r.parent[r.key] = op.value;
|
|
5870
|
+
}
|
|
5871
|
+
break;
|
|
5872
|
+
}
|
|
5873
|
+
case 'remove': {
|
|
5874
|
+
if (keys.length === 0) throw new Error('Cannot remove root');
|
|
5875
|
+
r = resolve(obj, keys);
|
|
5876
|
+
if (_isA(r.parent)) {
|
|
5877
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
5878
|
+
r.parent.splice(r.key, 1);
|
|
5879
|
+
} else {
|
|
5880
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
5881
|
+
delete r.parent[r.key];
|
|
5882
|
+
}
|
|
5883
|
+
break;
|
|
5884
|
+
}
|
|
5885
|
+
case 'replace': {
|
|
5886
|
+
if (keys.length === 0) throw new Error('Cannot replace root');
|
|
5887
|
+
r = resolve(obj, keys);
|
|
5888
|
+
if (_isA(r.parent)) {
|
|
5889
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
5890
|
+
} else {
|
|
5891
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
5892
|
+
}
|
|
5893
|
+
r.parent[r.key] = op.value;
|
|
5894
|
+
break;
|
|
5895
|
+
}
|
|
5896
|
+
case 'move': {
|
|
5897
|
+
if (!_is(op.from, 'string')) throw new Error('move requires "from"');
|
|
5898
|
+
fromKeys = parsePath(op.from);
|
|
5899
|
+
val = getVal(obj, fromKeys);
|
|
5900
|
+
fr = resolve(obj, fromKeys);
|
|
5901
|
+
if (_isA(fr.parent)) { fr.parent.splice(fr.key, 1); }
|
|
5902
|
+
else { delete fr.parent[fr.key]; }
|
|
5903
|
+
tr = resolve(obj, keys);
|
|
5904
|
+
if (_isA(tr.parent) && tr.key <= tr.parent.length) {
|
|
5905
|
+
tr.parent.splice(tr.key, 0, val);
|
|
5906
|
+
} else {
|
|
5907
|
+
tr.parent[tr.key] = val;
|
|
5908
|
+
}
|
|
5909
|
+
break;
|
|
5910
|
+
}
|
|
5911
|
+
case 'copy': {
|
|
5912
|
+
if (!_is(op.from, 'string')) throw new Error('copy requires "from"');
|
|
5913
|
+
val = getVal(obj, parsePath(op.from));
|
|
5914
|
+
cr = resolve(obj, keys);
|
|
5915
|
+
if (_isA(cr.parent) && cr.key <= cr.parent.length) {
|
|
5916
|
+
cr.parent.splice(cr.key, 0, val);
|
|
5917
|
+
} else {
|
|
5918
|
+
cr.parent[cr.key] = val;
|
|
5919
|
+
}
|
|
5920
|
+
break;
|
|
5921
|
+
}
|
|
5922
|
+
case 'test': {
|
|
5923
|
+
var actual = getVal(obj, keys);
|
|
5924
|
+
if (JSON.stringify(actual) !== JSON.stringify(op.value)) {
|
|
5925
|
+
throw new Error('Test failed: ' + op.path + ' expected ' + JSON.stringify(op.value) + ' got ' + JSON.stringify(actual));
|
|
5926
|
+
}
|
|
5927
|
+
break;
|
|
5928
|
+
}
|
|
5929
|
+
default:
|
|
5930
|
+
throw new Error('Unknown op: ' + op.op);
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
return obj;
|
|
5934
|
+
};
|
|
5935
|
+
|
|
5605
5936
|
// ===================================================================================
|
|
5606
5937
|
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
5607
5938
|
// ===================================================================================
|
|
@@ -5743,7 +6074,7 @@ bw.apply = function(msg) {
|
|
|
5743
6074
|
var target = msg.target;
|
|
5744
6075
|
|
|
5745
6076
|
if (type === 'replace') {
|
|
5746
|
-
var el = bw.
|
|
6077
|
+
var el = bw.el(target);
|
|
5747
6078
|
if (!el) return false;
|
|
5748
6079
|
bw.DOM(el, msg.node);
|
|
5749
6080
|
return true;
|
|
@@ -5753,14 +6084,14 @@ bw.apply = function(msg) {
|
|
|
5753
6084
|
return patched !== null;
|
|
5754
6085
|
|
|
5755
6086
|
} else if (type === 'append') {
|
|
5756
|
-
var parent = bw.
|
|
6087
|
+
var parent = bw.el(target);
|
|
5757
6088
|
if (!parent) return false;
|
|
5758
6089
|
var child = bw.createDOM(msg.node);
|
|
5759
6090
|
parent.appendChild(child);
|
|
5760
6091
|
return true;
|
|
5761
6092
|
|
|
5762
6093
|
} else if (type === 'remove') {
|
|
5763
|
-
var toRemove = bw.
|
|
6094
|
+
var toRemove = bw.el(target);
|
|
5764
6095
|
if (!toRemove) return false;
|
|
5765
6096
|
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
5766
6097
|
toRemove.remove();
|
|
@@ -5820,30 +6151,98 @@ bw.apply = function(msg) {
|
|
|
5820
6151
|
|
|
5821
6152
|
|
|
5822
6153
|
// ===================================================================================
|
|
5823
|
-
// bw.inspect() —
|
|
6154
|
+
// bw.inspect() — DOM introspection with bitwrench metadata
|
|
5824
6155
|
// ===================================================================================
|
|
5825
6156
|
|
|
5826
6157
|
/**
|
|
5827
|
-
* Inspect a DOM element
|
|
5828
|
-
*
|
|
5829
|
-
*
|
|
5830
|
-
*
|
|
5831
|
-
*
|
|
6158
|
+
* Inspect a DOM element and its subtree, returning a plain-object
|
|
6159
|
+
* representation with bitwrench metadata at each node. Useful for debugging,
|
|
6160
|
+
* devtools, MCP/AG-UI tool discovery, and automated testing.
|
|
6161
|
+
*
|
|
6162
|
+
* Each node in the returned tree includes:
|
|
6163
|
+
* - `tag` -- lowercase tag name (or '#text' for text nodes)
|
|
6164
|
+
* - `id` -- element id (if set)
|
|
6165
|
+
* - `uuid` -- bitwrench UUID class (if lifecycle-managed)
|
|
6166
|
+
* - `type` -- component type from o.type (if set, e.g. 'card', 'tabs')
|
|
6167
|
+
* - `classes` -- first 5 CSS classes (string, space-separated)
|
|
6168
|
+
* - `handles` -- array of el.bw method names (if any)
|
|
6169
|
+
* - `state` -- copy of _bw_state (if any)
|
|
6170
|
+
* - `hasRender` -- true if _bw_render is set
|
|
6171
|
+
* - `hasSubs` -- true if element has pub/sub subscriptions
|
|
6172
|
+
* - `refs` -- copy of _bw_refs keys (if any)
|
|
6173
|
+
* - `children` -- array of child node trees (up to depth limit, max 50 per level)
|
|
6174
|
+
*
|
|
6175
|
+
* @param {string|Element} target - CSS selector, UUID, or DOM element
|
|
6176
|
+
* @param {number} [depth=3] - Maximum recursion depth (0 = target only, no children)
|
|
6177
|
+
* @returns {Object|null} Plain object tree, or null if element not found
|
|
5832
6178
|
* @category Component
|
|
5833
6179
|
* @example
|
|
5834
|
-
*
|
|
5835
|
-
* bw.inspect(
|
|
6180
|
+
* // Get full tree from #app, 3 levels deep (default):
|
|
6181
|
+
* var info = bw.inspect('#app');
|
|
6182
|
+
*
|
|
6183
|
+
* // Shallow inspection (just the element, no children):
|
|
6184
|
+
* var info = bw.inspect('#my-carousel', 0);
|
|
6185
|
+
* console.log(info.handles); // ['next', 'prev', 'goToSlide']
|
|
6186
|
+
* console.log(info.type); // 'carousel'
|
|
6187
|
+
*
|
|
6188
|
+
* // Deep inspection for debugging:
|
|
6189
|
+
* console.log(JSON.stringify(bw.inspect('#app', 5), null, 2));
|
|
5836
6190
|
*/
|
|
5837
|
-
bw.inspect = function(target) {
|
|
5838
|
-
var el =
|
|
5839
|
-
if (!el
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
6191
|
+
bw.inspect = function(target, depth) {
|
|
6192
|
+
var el = bw.el(target);
|
|
6193
|
+
if (!el && _is(target, 'string')) el = bw.$(target)[0];
|
|
6194
|
+
if (!el) return null;
|
|
6195
|
+
if (depth === undefined || depth === null) depth = 3;
|
|
6196
|
+
|
|
6197
|
+
function walk(node, d) {
|
|
6198
|
+
if (!node) return null;
|
|
6199
|
+
// Skip non-element nodes (text, comment, etc.)
|
|
6200
|
+
if (node.nodeType !== 1) return null;
|
|
6201
|
+
|
|
6202
|
+
var info = { tag: node.tagName ? node.tagName.toLowerCase() : '#text' };
|
|
6203
|
+
|
|
6204
|
+
// Identity
|
|
6205
|
+
if (node.id) info.id = node.id;
|
|
6206
|
+
var uuid = bw.getUUID(node);
|
|
6207
|
+
if (uuid) info.uuid = uuid;
|
|
6208
|
+
if (node._bw_type) info.type = node._bw_type;
|
|
6209
|
+
|
|
6210
|
+
// CSS classes (first 5 for readability)
|
|
6211
|
+
if (node.className && typeof node.className === 'string') {
|
|
6212
|
+
info.classes = node.className.split(' ').slice(0, 5).join(' ');
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6215
|
+
// Bitwrench handle methods
|
|
6216
|
+
if (node.bw) {
|
|
6217
|
+
var handles = _keys(node.bw);
|
|
6218
|
+
if (handles.length > 0) info.handles = handles;
|
|
6219
|
+
}
|
|
6220
|
+
|
|
6221
|
+
// State
|
|
6222
|
+
if (node._bw_state) info.state = node._bw_state;
|
|
6223
|
+
if (node._bw_render) info.hasRender = true;
|
|
6224
|
+
if (node._bw_subs && node._bw_subs.length > 0) info.hasSubs = true;
|
|
6225
|
+
|
|
6226
|
+
// Refs
|
|
6227
|
+
if (node._bw_refs) info.refs = _keys(node._bw_refs);
|
|
6228
|
+
|
|
6229
|
+
// Children (recurse up to depth limit, max 50 children per level)
|
|
6230
|
+
if (d < depth && node.children && node.children.length > 0) {
|
|
6231
|
+
info.children = [];
|
|
6232
|
+
var max = Math.min(node.children.length, 50);
|
|
6233
|
+
for (var i = 0; i < max; i++) {
|
|
6234
|
+
var child = walk(node.children[i], d + 1);
|
|
6235
|
+
if (child) info.children.push(child);
|
|
6236
|
+
}
|
|
6237
|
+
if (node.children.length > 50) {
|
|
6238
|
+
info.children.push({ tag: '...', count: node.children.length - 50 });
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
|
|
6242
|
+
return info;
|
|
6243
|
+
}
|
|
6244
|
+
|
|
6245
|
+
return walk(el, 0);
|
|
5847
6246
|
};
|
|
5848
6247
|
|
|
5849
6248
|
bw.compile = function() { throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
@@ -6065,37 +6464,49 @@ bw.clip = clip;
|
|
|
6065
6464
|
* so you can use `.map()`, `.filter()`, etc. directly. Accepts CSS selectors,
|
|
6066
6465
|
* single elements, NodeLists, or arrays.
|
|
6067
6466
|
*
|
|
6467
|
+
* With an optional second argument, applies content or a function to
|
|
6468
|
+
* every matched element (same apply rules as `bw.el()`):
|
|
6469
|
+
* - string/number: sets `el.textContent`
|
|
6470
|
+
* - function: calls `apply(el)` for each element
|
|
6471
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
6472
|
+
* - array: clears children, appends each item
|
|
6473
|
+
*
|
|
6068
6474
|
* @param {string|Element|Array} selector - CSS selector, element, or array
|
|
6475
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
6069
6476
|
* @returns {Array} Array of DOM elements
|
|
6070
6477
|
* @category DOM Selection
|
|
6478
|
+
* @see bw.el
|
|
6071
6479
|
* @example
|
|
6072
|
-
* bw.$('.card')
|
|
6073
|
-
* bw.$(
|
|
6074
|
-
* bw.$('.card'
|
|
6480
|
+
* bw.$('.card') // => [div.card, div.card, ...]
|
|
6481
|
+
* bw.$('.status', 'Online') // set text on all .status elements
|
|
6482
|
+
* bw.$('.card', function(el) { // apply function to each
|
|
6483
|
+
* el.style.opacity = '0.5';
|
|
6484
|
+
* })
|
|
6075
6485
|
*/
|
|
6076
6486
|
if (bw._isBrowser) {
|
|
6077
|
-
bw.$ = function(selector) {
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
if (_isA(selector))
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
if (
|
|
6088
|
-
|
|
6487
|
+
bw.$ = function(selector, apply) {
|
|
6488
|
+
var els;
|
|
6489
|
+
if (!selector) {
|
|
6490
|
+
els = [];
|
|
6491
|
+
} else if (_isA(selector)) {
|
|
6492
|
+
els = selector;
|
|
6493
|
+
} else if (selector.nodeType) {
|
|
6494
|
+
els = [selector];
|
|
6495
|
+
} else if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
6496
|
+
els = Array.from(selector);
|
|
6497
|
+
} else if (_is(selector, 'string')) {
|
|
6498
|
+
els = Array.from(document.querySelectorAll(selector));
|
|
6499
|
+
} else {
|
|
6500
|
+
els = [];
|
|
6089
6501
|
}
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
return Array.from(document.querySelectorAll(selector));
|
|
6502
|
+
|
|
6503
|
+
if (apply !== undefined) {
|
|
6504
|
+
for (var i = 0; i < els.length; i++) _applyTo(els[i], apply);
|
|
6094
6505
|
}
|
|
6095
|
-
|
|
6096
|
-
return
|
|
6506
|
+
|
|
6507
|
+
return els;
|
|
6097
6508
|
};
|
|
6098
|
-
|
|
6509
|
+
|
|
6099
6510
|
// Convenience single element selector
|
|
6100
6511
|
bw.$.one = function(selector) {
|
|
6101
6512
|
return bw.$(selector)[0] || null;
|
|
@@ -6308,42 +6719,48 @@ bw.loadReset = function() {
|
|
|
6308
6719
|
};
|
|
6309
6720
|
|
|
6310
6721
|
/**
|
|
6311
|
-
* Toggle between primary and alternate palettes.
|
|
6722
|
+
* Toggle between primary and alternate theme palettes.
|
|
6312
6723
|
*
|
|
6313
|
-
* Adds/removes the `bw_theme_alt` class on the scoping element.
|
|
6724
|
+
* Adds/removes the `bw_theme_alt` class on the scoping element(s).
|
|
6314
6725
|
* Without a scope, toggles on `<html>` (global).
|
|
6315
|
-
* With a scope, toggles on
|
|
6726
|
+
* With a scope, toggles on ALL matching elements.
|
|
6316
6727
|
*
|
|
6317
|
-
* @param {string} [scope] -
|
|
6318
|
-
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
6728
|
+
* @param {string|Element} [scope] - Selector or element. Omit for global.
|
|
6729
|
+
* @returns {string} Active mode after toggle: 'primary' or 'alternate' (based on first element)
|
|
6319
6730
|
* @category CSS & Styling
|
|
6320
6731
|
* @see bw.applyStyles
|
|
6321
6732
|
* @see bw.clearStyles
|
|
6322
6733
|
* @example
|
|
6323
|
-
* bw.
|
|
6324
|
-
* bw.
|
|
6734
|
+
* bw.toggleThemeMode(); // global toggle on <html>
|
|
6735
|
+
* bw.toggleThemeMode('#my-dashboard'); // scoped toggle
|
|
6736
|
+
* bw.toggleThemeMode('.panel'); // toggle on ALL .panel elements
|
|
6325
6737
|
*/
|
|
6326
|
-
bw.
|
|
6738
|
+
bw.toggleThemeMode = function(scope) {
|
|
6327
6739
|
if (!bw._isBrowser) return 'primary';
|
|
6328
|
-
var
|
|
6740
|
+
var els;
|
|
6329
6741
|
if (scope) {
|
|
6330
|
-
|
|
6331
|
-
target = els[0];
|
|
6742
|
+
els = bw.$(scope);
|
|
6332
6743
|
} else {
|
|
6333
|
-
|
|
6744
|
+
els = [document.documentElement];
|
|
6334
6745
|
}
|
|
6335
|
-
if (!
|
|
6746
|
+
if (!els.length) return 'primary';
|
|
6336
6747
|
|
|
6337
|
-
var
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6748
|
+
var mode;
|
|
6749
|
+
for (var i = 0; i < els.length; i++) {
|
|
6750
|
+
var hasAlt = els[i].classList.contains('bw_theme_alt');
|
|
6751
|
+
if (hasAlt) {
|
|
6752
|
+
els[i].classList.remove('bw_theme_alt');
|
|
6753
|
+
} else {
|
|
6754
|
+
els[i].classList.add('bw_theme_alt');
|
|
6755
|
+
}
|
|
6756
|
+
if (i === 0) mode = hasAlt ? 'primary' : 'alternate';
|
|
6344
6757
|
}
|
|
6758
|
+
return mode;
|
|
6345
6759
|
};
|
|
6346
6760
|
|
|
6761
|
+
// Alias — kept for one release cycle. Use bw.toggleThemeMode() instead.
|
|
6762
|
+
bw.toggleStyles = bw.toggleThemeMode;
|
|
6763
|
+
|
|
6347
6764
|
/**
|
|
6348
6765
|
* Remove injected styles for a given scope.
|
|
6349
6766
|
*
|
|
@@ -7383,6 +7800,57 @@ Object.entries(components).forEach(([name, fn]) => {
|
|
|
7383
7800
|
}
|
|
7384
7801
|
});
|
|
7385
7802
|
|
|
7803
|
+
/**
|
|
7804
|
+
* Query the BCCL component registry. Returns metadata about registered
|
|
7805
|
+
* component types -- their names and factory function names. Useful for
|
|
7806
|
+
* tooling, introspection, documentation generators, and auto-complete
|
|
7807
|
+
* systems (including MCP/AG-UI tool discovery).
|
|
7808
|
+
*
|
|
7809
|
+
* With no arguments, returns an array of all registered component types.
|
|
7810
|
+
* With a type name, returns metadata for that single type (or null if
|
|
7811
|
+
* the type is not registered).
|
|
7812
|
+
*
|
|
7813
|
+
* @param {string} [type] - Optional component type name to look up
|
|
7814
|
+
* @returns {Array<Object>|Object|null} Array of {type, factory} objects,
|
|
7815
|
+
* a single {type, factory} object, or null if the type is not found
|
|
7816
|
+
* @category Component
|
|
7817
|
+
* @see bw.make
|
|
7818
|
+
* @see bw.BCCL
|
|
7819
|
+
* @example
|
|
7820
|
+
* // List all available component types:
|
|
7821
|
+
* bw.catalog();
|
|
7822
|
+
* // => [{ type: 'card', factory: 'makeCard' },
|
|
7823
|
+
* // { type: 'button', factory: 'makeButton' }, ...]
|
|
7824
|
+
*
|
|
7825
|
+
* // Look up a specific type:
|
|
7826
|
+
* bw.catalog('accordion');
|
|
7827
|
+
* // => { type: 'accordion', factory: 'makeAccordion' }
|
|
7828
|
+
*
|
|
7829
|
+
* // Check if a type exists:
|
|
7830
|
+
* if (bw.catalog('chart')) { ... }
|
|
7831
|
+
*
|
|
7832
|
+
* // Get just the type names:
|
|
7833
|
+
* bw.catalog().map(function(c) { return c.type; });
|
|
7834
|
+
* // => ['card', 'button', 'container', 'row', ...]
|
|
7835
|
+
*/
|
|
7836
|
+
bw.catalog = function(type) {
|
|
7837
|
+
if (type) {
|
|
7838
|
+
var def = bw.BCCL[type];
|
|
7839
|
+
if (!def) return null;
|
|
7840
|
+
return {
|
|
7841
|
+
type: type,
|
|
7842
|
+
factory: def.make.name || ('make' + type.charAt(0).toUpperCase() + type.slice(1))
|
|
7843
|
+
};
|
|
7844
|
+
}
|
|
7845
|
+
return Object.keys(bw.BCCL).map(function(k) {
|
|
7846
|
+
var def = bw.BCCL[k];
|
|
7847
|
+
return {
|
|
7848
|
+
type: k,
|
|
7849
|
+
factory: def.make.name || ('make' + k.charAt(0).toUpperCase() + k.slice(1))
|
|
7850
|
+
};
|
|
7851
|
+
});
|
|
7852
|
+
};
|
|
7853
|
+
|
|
7386
7854
|
// Also attach to global in browsers
|
|
7387
7855
|
if (bw._isBrowser && typeof window !== 'undefined') {
|
|
7388
7856
|
window.bw = bw;
|