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