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
package/dist/bitwrench.esm.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/*! bitwrench v2.0.
|
|
1
|
+
/*! bitwrench v2.0.30 | 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.30',
|
|
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:51:29.111Z'
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -7778,7 +7778,6 @@ var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() =
|
|
|
7778
7778
|
// Console aliases use thin wrappers (not direct references) so that test
|
|
7779
7779
|
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
7780
7780
|
var _cw = function() { console.warn.apply(console, arguments); };
|
|
7781
|
-
var _cl = function() { console.log.apply(console, arguments); };
|
|
7782
7781
|
var _ce = function() { console.error.apply(console, arguments); };
|
|
7783
7782
|
|
|
7784
7783
|
/**
|
|
@@ -7915,61 +7914,105 @@ bw.uuid = function(prefix) {
|
|
|
7915
7914
|
};
|
|
7916
7915
|
|
|
7917
7916
|
/**
|
|
7918
|
-
* Look up a DOM element by ID
|
|
7917
|
+
* Look up a single DOM element by ID, CSS selector, UUID, or element ref.
|
|
7918
|
+
* Optionally apply content or a function to the resolved element.
|
|
7919
7919
|
*
|
|
7920
|
-
* Resolution order:
|
|
7921
|
-
* 1. Check `bw._nodeMap[id]`
|
|
7922
|
-
* 2.
|
|
7923
|
-
* 3.
|
|
7924
|
-
* 4.
|
|
7925
|
-
* 5. Cache the result for next time
|
|
7920
|
+
* Resolution order for string targets:
|
|
7921
|
+
* 1. Check `bw._nodeMap[id]` cache (O(1), stale entries auto-pruned)
|
|
7922
|
+
* 2. `document.getElementById(id)`
|
|
7923
|
+
* 3. `document.querySelector(id)` for selectors starting with # or .
|
|
7924
|
+
* 4. Class-based lookup for `bw_uuid_*` tokens
|
|
7926
7925
|
*
|
|
7927
|
-
*
|
|
7928
|
-
*
|
|
7929
|
-
*
|
|
7930
|
-
*
|
|
7926
|
+
* With one argument, returns the element (or null). With two arguments,
|
|
7927
|
+
* applies the second argument to the element and returns the element:
|
|
7928
|
+
* - string/number: sets `el.textContent`
|
|
7929
|
+
* - function: calls `apply(el)`, returns el
|
|
7930
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
7931
|
+
* - array: clears children, appends each item (string -> text node, TACO -> element)
|
|
7931
7932
|
*
|
|
7932
|
-
* @param {string|Element}
|
|
7933
|
+
* @param {string|Element} target - Element ref, ID, CSS selector, or bw_uuid_* class
|
|
7934
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
7933
7935
|
* @returns {Element|null} The DOM element, or null if not found
|
|
7934
|
-
* @category
|
|
7936
|
+
* @category DOM Selection
|
|
7937
|
+
* @see bw.$
|
|
7938
|
+
* @see bw.patch
|
|
7939
|
+
* @example
|
|
7940
|
+
* bw.el('#title') // lookup
|
|
7941
|
+
* bw.el('#title', 'Hello') // set text content
|
|
7942
|
+
* bw.el('#app', { t: 'h1', c: 'Hi' }) // mount TACO
|
|
7943
|
+
* bw.el('.card', function(el) { // apply function
|
|
7944
|
+
* el.style.opacity = '0.5';
|
|
7945
|
+
* })
|
|
7935
7946
|
*/
|
|
7936
|
-
bw.
|
|
7937
|
-
//
|
|
7938
|
-
|
|
7939
|
-
if (!
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
if (cached
|
|
7947
|
-
|
|
7947
|
+
bw.el = function(target, apply) {
|
|
7948
|
+
// Resolve target to element
|
|
7949
|
+
var el;
|
|
7950
|
+
if (!_is(target, 'string')) {
|
|
7951
|
+
el = target || null;
|
|
7952
|
+
} else if (!target || !bw._isBrowser) {
|
|
7953
|
+
el = null;
|
|
7954
|
+
} else {
|
|
7955
|
+
// 1. Check cache
|
|
7956
|
+
var cached = bw._nodeMap[target];
|
|
7957
|
+
if (cached) {
|
|
7958
|
+
if (cached.parentNode !== null) {
|
|
7959
|
+
el = cached;
|
|
7960
|
+
} else {
|
|
7961
|
+
delete bw._nodeMap[target];
|
|
7962
|
+
}
|
|
7963
|
+
}
|
|
7964
|
+
if (!el) {
|
|
7965
|
+
// 2. getElementById
|
|
7966
|
+
el = document.getElementById(target);
|
|
7967
|
+
// 3. querySelector for CSS selectors
|
|
7968
|
+
if (!el && (target.charAt(0) === '#' || target.charAt(0) === '.')) {
|
|
7969
|
+
el = document.querySelector(target);
|
|
7970
|
+
}
|
|
7971
|
+
// 4. bw_uuid_* class lookup
|
|
7972
|
+
if (!el && target.indexOf('bw_uuid_') === 0) {
|
|
7973
|
+
el = document.querySelector('.' + target);
|
|
7974
|
+
}
|
|
7975
|
+
// 5. Cache result
|
|
7976
|
+
if (el) bw._nodeMap[target] = el;
|
|
7948
7977
|
}
|
|
7949
|
-
// Stale — remove and fall through
|
|
7950
|
-
delete bw._nodeMap[id];
|
|
7951
7978
|
}
|
|
7952
7979
|
|
|
7953
|
-
//
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
// 3. Try querySelector for CSS selectors (starts with # or .)
|
|
7957
|
-
if (!el && (id.charAt(0) === '#' || id.charAt(0) === '.')) {
|
|
7958
|
-
el = document.querySelector(id);
|
|
7959
|
-
}
|
|
7980
|
+
// Apply (if provided and element found)
|
|
7981
|
+
if (el && apply !== undefined) _applyTo(el, apply);
|
|
7960
7982
|
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
el = document.querySelector('.' + id);
|
|
7964
|
-
}
|
|
7983
|
+
return el;
|
|
7984
|
+
};
|
|
7965
7985
|
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7986
|
+
/**
|
|
7987
|
+
* Internal: apply content or function to a DOM element.
|
|
7988
|
+
* Shared by bw.el() and bw.$().
|
|
7989
|
+
* @private
|
|
7990
|
+
*/
|
|
7991
|
+
function _applyTo(el, apply) {
|
|
7992
|
+
if (_is(apply, 'function')) {
|
|
7993
|
+
apply(el);
|
|
7994
|
+
} else if (_isA(apply)) {
|
|
7995
|
+
el.innerHTML = '';
|
|
7996
|
+
apply.forEach(function(item) {
|
|
7997
|
+
if (item != null) {
|
|
7998
|
+
if (_is(item, 'object') && item.t) {
|
|
7999
|
+
el.appendChild(bw.createDOM(item));
|
|
8000
|
+
} else {
|
|
8001
|
+
el.appendChild(document.createTextNode(String(item)));
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
8004
|
+
});
|
|
8005
|
+
} else if (_is(apply, 'object') && apply !== null && apply.t) {
|
|
8006
|
+
el.innerHTML = '';
|
|
8007
|
+
el.appendChild(bw.createDOM(apply));
|
|
8008
|
+
} else {
|
|
8009
|
+
el.textContent = String(apply);
|
|
7969
8010
|
}
|
|
8011
|
+
}
|
|
7970
8012
|
|
|
7971
|
-
|
|
7972
|
-
|
|
8013
|
+
// Internal alias — kept for one release cycle (v2.0.26).
|
|
8014
|
+
// Will be removed in v2.0.27. Use bw.el() instead.
|
|
8015
|
+
bw._el = bw.el;
|
|
7973
8016
|
|
|
7974
8017
|
/**
|
|
7975
8018
|
* Register a DOM element in the node cache under one or more keys.
|
|
@@ -8033,6 +8076,12 @@ var _BW_LC = 'bw_lc';
|
|
|
8033
8076
|
*/
|
|
8034
8077
|
var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
|
|
8035
8078
|
|
|
8079
|
+
/**
|
|
8080
|
+
* SVG namespace URI for createElementNS.
|
|
8081
|
+
* @private
|
|
8082
|
+
*/
|
|
8083
|
+
var _SVG_NS = 'http://www.w3.org/2000/svg';
|
|
8084
|
+
|
|
8036
8085
|
/**
|
|
8037
8086
|
* Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
|
|
8038
8087
|
*
|
|
@@ -8087,9 +8136,10 @@ bw.getUUID = function(tacoOrElement) {
|
|
|
8087
8136
|
if (!tacoOrElement) return null;
|
|
8088
8137
|
|
|
8089
8138
|
var classStr;
|
|
8090
|
-
// DOM element: check className
|
|
8139
|
+
// DOM element: check className (SVG elements use getAttribute for string value)
|
|
8091
8140
|
if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
|
|
8092
|
-
classStr = tacoOrElement.className
|
|
8141
|
+
classStr = typeof tacoOrElement.className === 'string'
|
|
8142
|
+
? tacoOrElement.className : (tacoOrElement.getAttribute('class') || '');
|
|
8093
8143
|
}
|
|
8094
8144
|
// TACO object: check a.class
|
|
8095
8145
|
else if (tacoOrElement.a && _is(tacoOrElement.a.class, 'string')) {
|
|
@@ -8358,7 +8408,7 @@ bw.htmlPage = function(opts) {
|
|
|
8358
8408
|
var fnCounterBefore = bw._fnIDCounter;
|
|
8359
8409
|
|
|
8360
8410
|
// Render body content
|
|
8361
|
-
var bodyHTML
|
|
8411
|
+
var bodyHTML;
|
|
8362
8412
|
if (_is(body, 'string')) {
|
|
8363
8413
|
bodyHTML = body;
|
|
8364
8414
|
} else {
|
|
@@ -8529,9 +8579,11 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8529
8579
|
}
|
|
8530
8580
|
|
|
8531
8581
|
const { t: tag, a: attrs = {}, c: content, o: opts = {} } = taco;
|
|
8532
|
-
|
|
8533
|
-
//
|
|
8534
|
-
|
|
8582
|
+
|
|
8583
|
+
// SVG namespace: detect SVG context and thread through children.
|
|
8584
|
+
// {t:'svg'} starts SVG context; foreignObject children revert to HTML.
|
|
8585
|
+
var svgCtx = options._svgCtx || (tag === 'svg');
|
|
8586
|
+
var el = svgCtx ? document.createElementNS(_SVG_NS, tag) : document.createElement(tag);
|
|
8535
8587
|
|
|
8536
8588
|
// Set attributes
|
|
8537
8589
|
for (const [key, value] of Object.entries(attrs)) {
|
|
@@ -8542,9 +8594,11 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8542
8594
|
Object.assign(el.style, value);
|
|
8543
8595
|
} else if (key === 'class') {
|
|
8544
8596
|
// Handle class as array or string
|
|
8597
|
+
// SVG elements use SVGAnimatedString for className, so use setAttribute
|
|
8545
8598
|
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
8546
8599
|
if (classStr) {
|
|
8547
|
-
el.
|
|
8600
|
+
if (svgCtx) el.setAttribute('class', classStr);
|
|
8601
|
+
else el.className = classStr;
|
|
8548
8602
|
}
|
|
8549
8603
|
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
8550
8604
|
// Event handlers
|
|
@@ -8565,11 +8619,17 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8565
8619
|
// Add children, building _bw_refs for fast parent→child access.
|
|
8566
8620
|
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
8567
8621
|
// so o.render functions can access them without any DOM lookup.
|
|
8622
|
+
// SVG: foreignObject children revert to HTML namespace; otherwise inherit.
|
|
8623
|
+
var childOpts = options;
|
|
8624
|
+
var childSvgCtx = svgCtx && tag !== 'foreignObject';
|
|
8625
|
+
if (childSvgCtx !== (options._svgCtx || false)) {
|
|
8626
|
+
childOpts = Object.assign({}, options, {_svgCtx: childSvgCtx || undefined});
|
|
8627
|
+
}
|
|
8568
8628
|
if (content != null) {
|
|
8569
8629
|
if (_isA(content)) {
|
|
8570
8630
|
content.forEach(child => {
|
|
8571
8631
|
if (child != null) {
|
|
8572
|
-
var childEl = bw.createDOM(child,
|
|
8632
|
+
var childEl = bw.createDOM(child, childOpts);
|
|
8573
8633
|
el.appendChild(childEl);
|
|
8574
8634
|
// Build local refs for addressable children
|
|
8575
8635
|
var childRefId = (child && child.a) ? (child.a.id || bw.getUUID(child)) : null;
|
|
@@ -8592,7 +8652,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8592
8652
|
// Raw HTML content — inject via innerHTML
|
|
8593
8653
|
el.innerHTML = content.v;
|
|
8594
8654
|
} else if (_is(content, 'object') && content.t) {
|
|
8595
|
-
var childEl = bw.createDOM(content,
|
|
8655
|
+
var childEl = bw.createDOM(content, childOpts);
|
|
8596
8656
|
el.appendChild(childEl);
|
|
8597
8657
|
var childRefId = content.a ? (content.a.id || bw.getUUID(content)) : null;
|
|
8598
8658
|
if (childRefId) {
|
|
@@ -8618,13 +8678,21 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8618
8678
|
}
|
|
8619
8679
|
|
|
8620
8680
|
// Register UUID class in node cache (bw_uuid_* tokens in class string)
|
|
8621
|
-
|
|
8622
|
-
|
|
8681
|
+
// SVG elements have SVGAnimatedString for className; use getAttribute instead
|
|
8682
|
+
var clsStr = svgCtx ? (el.getAttribute('class') || '') : el.className;
|
|
8683
|
+
if (clsStr) {
|
|
8684
|
+
var uuidMatch = clsStr.match(_UUID_RE);
|
|
8623
8685
|
if (uuidMatch) {
|
|
8624
8686
|
bw._nodeMap[uuidMatch[0]] = el;
|
|
8625
8687
|
}
|
|
8626
8688
|
}
|
|
8627
8689
|
|
|
8690
|
+
// Store component type metadata (e.g., 'card', 'tabs') for introspection.
|
|
8691
|
+
// BCCL factories set o.type; custom components can too.
|
|
8692
|
+
if (opts.type) {
|
|
8693
|
+
el._bw_type = opts.type;
|
|
8694
|
+
}
|
|
8695
|
+
|
|
8628
8696
|
// Handle lifecycle hooks and state
|
|
8629
8697
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
8630
8698
|
// Ensure element has a UUID class for identity
|
|
@@ -8654,11 +8722,13 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8654
8722
|
|
|
8655
8723
|
if (mountFn) {
|
|
8656
8724
|
if (document.body.contains(el)) {
|
|
8657
|
-
mountFn(el, el._bw_state || {});
|
|
8725
|
+
try { mountFn(el, el._bw_state || {}); }
|
|
8726
|
+
catch (e) { _cw('o.mounted error: ' + e.message); }
|
|
8658
8727
|
} else {
|
|
8659
8728
|
requestAnimationFrame(() => {
|
|
8660
8729
|
if (document.body.contains(el)) {
|
|
8661
|
-
mountFn(el, el._bw_state || {});
|
|
8730
|
+
try { mountFn(el, el._bw_state || {}); }
|
|
8731
|
+
catch (e) { _cw('o.mounted error: ' + e.message); }
|
|
8662
8732
|
}
|
|
8663
8733
|
});
|
|
8664
8734
|
}
|
|
@@ -8667,7 +8737,8 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8667
8737
|
// Store unmount callback keyed by UUID class
|
|
8668
8738
|
if (opts.unmount) {
|
|
8669
8739
|
bw._unmountCallbacks.set(uuid, () => {
|
|
8670
|
-
opts.unmount(el, el._bw_state || {});
|
|
8740
|
+
try { opts.unmount(el, el._bw_state || {}); }
|
|
8741
|
+
catch (e) { _cw('o.unmount error: ' + e.message); }
|
|
8671
8742
|
});
|
|
8672
8743
|
}
|
|
8673
8744
|
}
|
|
@@ -8686,24 +8757,25 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8686
8757
|
}
|
|
8687
8758
|
|
|
8688
8759
|
// Slot declarations: auto-generate setX/getX pairs
|
|
8760
|
+
// The target element is cached at creation time to avoid repeated
|
|
8761
|
+
// querySelector calls on every get/set invocation.
|
|
8689
8762
|
if (opts.slots) {
|
|
8690
8763
|
for (var sk in opts.slots) {
|
|
8691
8764
|
if (_hop.call(opts.slots, sk)) {
|
|
8692
8765
|
(function(name, selector) {
|
|
8766
|
+
var target = el.querySelector(selector);
|
|
8693
8767
|
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
8694
8768
|
el.bw['set' + cap] = function(value) {
|
|
8695
|
-
|
|
8696
|
-
if (!t) return;
|
|
8769
|
+
if (!target) return;
|
|
8697
8770
|
if (value != null && typeof value === 'object' && value.t) {
|
|
8698
|
-
|
|
8699
|
-
|
|
8771
|
+
target.innerHTML = '';
|
|
8772
|
+
target.appendChild(bw.createDOM(value));
|
|
8700
8773
|
} else {
|
|
8701
|
-
|
|
8774
|
+
target.textContent = (value != null) ? String(value) : '';
|
|
8702
8775
|
}
|
|
8703
8776
|
};
|
|
8704
8777
|
el.bw['get' + cap] = function() {
|
|
8705
|
-
|
|
8706
|
-
return t ? t.textContent : '';
|
|
8778
|
+
return target ? target.textContent : '';
|
|
8707
8779
|
};
|
|
8708
8780
|
})(sk, opts.slots[sk]);
|
|
8709
8781
|
}
|
|
@@ -8744,7 +8816,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
8744
8816
|
}
|
|
8745
8817
|
|
|
8746
8818
|
// Get target element (use cache-backed lookup)
|
|
8747
|
-
const targetEl = bw.
|
|
8819
|
+
const targetEl = bw.el(target);
|
|
8748
8820
|
|
|
8749
8821
|
if (!targetEl) {
|
|
8750
8822
|
_ce('bw.DOM: Target element not found:', target);
|
|
@@ -8847,7 +8919,8 @@ bw.cleanup = function(element) {
|
|
|
8847
8919
|
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
8848
8920
|
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
8849
8921
|
uuidEls.forEach(function(uel) {
|
|
8850
|
-
var
|
|
8922
|
+
var uc = typeof uel.className === 'string' ? uel.className : (uel.getAttribute('class') || '');
|
|
8923
|
+
var m = uc && uc.match(_UUID_RE);
|
|
8851
8924
|
if (m) delete bw._nodeMap[m[0]];
|
|
8852
8925
|
});
|
|
8853
8926
|
|
|
@@ -8933,9 +9006,10 @@ bw.cleanup = function(element) {
|
|
|
8933
9006
|
* bw.update(el); // re-renders, emits bw:statechange
|
|
8934
9007
|
*/
|
|
8935
9008
|
bw.update = function(target) {
|
|
8936
|
-
var el = bw.
|
|
9009
|
+
var el = bw.el(target);
|
|
8937
9010
|
if (el && el._bw_render) {
|
|
8938
|
-
el._bw_render(el, el._bw_state || {});
|
|
9011
|
+
try { el._bw_render(el, el._bw_state || {}); }
|
|
9012
|
+
catch (e) { _cw('o.render error: ' + e.message); }
|
|
8939
9013
|
bw.emit(el, 'statechange', el._bw_state);
|
|
8940
9014
|
}
|
|
8941
9015
|
return el || null;
|
|
@@ -8962,7 +9036,7 @@ bw.update = function(target) {
|
|
|
8962
9036
|
* bw.patch('info', { t: 'em', c: 'new' }); // replace children with TACO
|
|
8963
9037
|
*/
|
|
8964
9038
|
bw.patch = function(id, content, attr) {
|
|
8965
|
-
var el = bw.
|
|
9039
|
+
var el = bw.el(id);
|
|
8966
9040
|
if (!el) return null;
|
|
8967
9041
|
|
|
8968
9042
|
if (attr) {
|
|
@@ -9034,7 +9108,7 @@ bw.patchAll = function(patches) {
|
|
|
9034
9108
|
* // Dispatches CustomEvent 'bw:statechange' on the element
|
|
9035
9109
|
*/
|
|
9036
9110
|
bw.emit = function(target, eventName, detail) {
|
|
9037
|
-
var el = bw.
|
|
9111
|
+
var el = bw.el(target);
|
|
9038
9112
|
if (el) {
|
|
9039
9113
|
el.dispatchEvent(new CustomEvent('bw:' + eventName, {
|
|
9040
9114
|
bubbles: true,
|
|
@@ -9063,7 +9137,7 @@ bw.emit = function(target, eventName, detail) {
|
|
|
9063
9137
|
* });
|
|
9064
9138
|
*/
|
|
9065
9139
|
bw.on = function(target, eventName, handler) {
|
|
9066
|
-
var el = bw.
|
|
9140
|
+
var el = bw.el(target);
|
|
9067
9141
|
if (el) {
|
|
9068
9142
|
el.addEventListener('bw:' + eventName, function(e) {
|
|
9069
9143
|
handler(e.detail, e);
|
|
@@ -9090,23 +9164,38 @@ bw.on = function(target, eventName, handler) {
|
|
|
9090
9164
|
*
|
|
9091
9165
|
* @param {string} topic - Topic name (plain string, no prefix)
|
|
9092
9166
|
* @param {*} [detail] - Data to pass to subscribers
|
|
9093
|
-
* @returns {number} Count of successfully called subscribers
|
|
9167
|
+
* @returns {number} Count of successfully called subscribers (including wildcard matches)
|
|
9094
9168
|
* @category Pub/Sub
|
|
9095
9169
|
* @see bw.sub
|
|
9096
9170
|
* @example
|
|
9097
9171
|
* bw.pub('score:updated', { player: 'X', score: 10 });
|
|
9172
|
+
* // Wildcard subscribers matching 'score:*' will also fire
|
|
9098
9173
|
*/
|
|
9099
9174
|
bw.pub = function(topic, detail) {
|
|
9100
|
-
var subs = bw._topics[topic];
|
|
9101
|
-
if (!subs || subs.length === 0) return 0;
|
|
9102
|
-
var snapshot = subs.slice(); // safe against unsub during iteration
|
|
9103
9175
|
var called = 0;
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9176
|
+
// Exact-match subscribers
|
|
9177
|
+
var subs = bw._topics[topic];
|
|
9178
|
+
if (subs && subs.length > 0) {
|
|
9179
|
+
var snapshot = subs.slice();
|
|
9180
|
+
for (var i = 0; i < snapshot.length; i++) {
|
|
9181
|
+
try { snapshot[i].handler(detail, topic); called++; }
|
|
9182
|
+
catch (err) { _cw('bw.pub: subscriber error on topic "' + topic + '":', err); }
|
|
9183
|
+
}
|
|
9184
|
+
}
|
|
9185
|
+
// Wildcard subscribers -- patterns ending with '*'
|
|
9186
|
+
var keys = Object.keys(bw._topics);
|
|
9187
|
+
for (var k = 0; k < keys.length; k++) {
|
|
9188
|
+
var pat = keys[k];
|
|
9189
|
+
if (pat.charAt(pat.length - 1) !== '*') continue;
|
|
9190
|
+
var prefix = pat.slice(0, -1); // strip trailing '*'
|
|
9191
|
+
if (topic.length >= prefix.length && topic.substring(0, prefix.length) === prefix && topic !== pat) {
|
|
9192
|
+
var wsubs = bw._topics[pat];
|
|
9193
|
+
if (!wsubs) continue;
|
|
9194
|
+
var wsnap = wsubs.slice();
|
|
9195
|
+
for (var w = 0; w < wsnap.length; w++) {
|
|
9196
|
+
try { wsnap[w].handler(detail, topic); called++; }
|
|
9197
|
+
catch (err) { _cw('bw.pub: wildcard subscriber error on "' + pat + '" for topic "' + topic + '":', err); }
|
|
9198
|
+
}
|
|
9110
9199
|
}
|
|
9111
9200
|
}
|
|
9112
9201
|
return called;
|
|
@@ -9115,12 +9204,17 @@ bw.pub = function(topic, detail) {
|
|
|
9115
9204
|
/**
|
|
9116
9205
|
* Subscribe to a topic. Returns an unsub() function.
|
|
9117
9206
|
*
|
|
9118
|
-
*
|
|
9207
|
+
* Supports wildcard patterns: a topic ending in `*` matches any published
|
|
9208
|
+
* topic that starts with the prefix before the `*`. For example,
|
|
9209
|
+
* `'agui:*'` matches `'agui:ready'`, `'agui:error'`, etc. The handler
|
|
9210
|
+
* receives `(detail, topic)` so it can distinguish which topic fired.
|
|
9211
|
+
*
|
|
9212
|
+
* Optional third argument ties the subscription to a DOM element's lifecycle --
|
|
9119
9213
|
* when `bw.cleanup()` is called on that element, the subscription is automatically
|
|
9120
9214
|
* removed, preventing memory leaks.
|
|
9121
9215
|
*
|
|
9122
|
-
* @param {string} topic - Topic name
|
|
9123
|
-
* @param {Function} handler - Called with (detail) on each publish
|
|
9216
|
+
* @param {string} topic - Topic name, or wildcard pattern ending in '*'
|
|
9217
|
+
* @param {Function} handler - Called with (detail, topic) on each publish
|
|
9124
9218
|
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
9125
9219
|
* @returns {Function} Call to unsubscribe
|
|
9126
9220
|
* @category Pub/Sub
|
|
@@ -9131,6 +9225,11 @@ bw.pub = function(topic, detail) {
|
|
|
9131
9225
|
* console.log(detail.player, 'scored', detail.score);
|
|
9132
9226
|
* });
|
|
9133
9227
|
* // Later: unsub() to stop listening
|
|
9228
|
+
*
|
|
9229
|
+
* // Wildcard: listen to all 'agui:' topics
|
|
9230
|
+
* bw.sub('agui:*', function(detail, topic) {
|
|
9231
|
+
* console.log('Got', topic, detail);
|
|
9232
|
+
* });
|
|
9134
9233
|
*/
|
|
9135
9234
|
bw.sub = function(topic, handler, el) {
|
|
9136
9235
|
var id = ++bw._subIdCounter;
|
|
@@ -9182,6 +9281,37 @@ bw.unsub = function(topic, handler) {
|
|
|
9182
9281
|
return removed;
|
|
9183
9282
|
};
|
|
9184
9283
|
|
|
9284
|
+
/**
|
|
9285
|
+
* Subscribe to a topic for a single event only. The subscription is
|
|
9286
|
+
* automatically removed after the first publish. Equivalent to manually
|
|
9287
|
+
* calling unsub() inside a bw.sub() handler, but avoids the common bug
|
|
9288
|
+
* of forgetting to unsubscribe.
|
|
9289
|
+
*
|
|
9290
|
+
* @param {string} topic - Topic name
|
|
9291
|
+
* @param {Function} handler - Called once with (detail) on the next publish
|
|
9292
|
+
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
9293
|
+
* @returns {Function} Call to cancel the subscription before it fires
|
|
9294
|
+
* @category Pub/Sub
|
|
9295
|
+
* @see bw.sub
|
|
9296
|
+
* @see bw.pub
|
|
9297
|
+
* @example
|
|
9298
|
+
* bw.once('data:loaded', function(detail) {
|
|
9299
|
+
* console.log('Received:', detail);
|
|
9300
|
+
* // No need to unsubscribe -- already done automatically
|
|
9301
|
+
* });
|
|
9302
|
+
*
|
|
9303
|
+
* // Cancel before it fires:
|
|
9304
|
+
* var cancel = bw.once('timeout', handler);
|
|
9305
|
+
* cancel(); // handler will never be called
|
|
9306
|
+
*/
|
|
9307
|
+
bw.once = function(topic, handler, el) {
|
|
9308
|
+
var unsub = bw.sub(topic, function(detail) {
|
|
9309
|
+
unsub();
|
|
9310
|
+
handler(detail);
|
|
9311
|
+
}, el);
|
|
9312
|
+
return unsub;
|
|
9313
|
+
};
|
|
9314
|
+
|
|
9185
9315
|
// ===================================================================================
|
|
9186
9316
|
// Function Registry (revived from v1 for string dispatch contexts)
|
|
9187
9317
|
// ===================================================================================
|
|
@@ -9422,7 +9552,7 @@ bw.component = function() { throw new Error('bw.component() removed in v2.0.19.
|
|
|
9422
9552
|
* };
|
|
9423
9553
|
*/
|
|
9424
9554
|
bw.message = function(target, action, data) {
|
|
9425
|
-
var el = bw.
|
|
9555
|
+
var el = bw.el(target);
|
|
9426
9556
|
if (!el) el = bw.$('.' + target)[0];
|
|
9427
9557
|
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
9428
9558
|
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
@@ -9432,6 +9562,207 @@ bw.message = function(target, action, data) {
|
|
|
9432
9562
|
return true;
|
|
9433
9563
|
};
|
|
9434
9564
|
|
|
9565
|
+
/**
|
|
9566
|
+
* Collect form data from all input, select, and textarea elements within a
|
|
9567
|
+
* container. Each element's `name` attribute (or `id` if no name) becomes a
|
|
9568
|
+
* key in the returned object. This provides a lightweight alternative to the
|
|
9569
|
+
* browser FormData API that returns a plain object suitable for JSON
|
|
9570
|
+
* serialization or bw.pub().
|
|
9571
|
+
*
|
|
9572
|
+
* Handles all standard HTML form controls:
|
|
9573
|
+
* - text/number/email/etc inputs: string value
|
|
9574
|
+
* - checkboxes: boolean (true/false)
|
|
9575
|
+
* - radio buttons: string value of the checked radio (unchecked groups omitted)
|
|
9576
|
+
* - multi-select: array of selected option values
|
|
9577
|
+
* - textarea: string value
|
|
9578
|
+
*
|
|
9579
|
+
* Elements without both `name` and `id` attributes are silently skipped.
|
|
9580
|
+
*
|
|
9581
|
+
* @param {string|Element} target - CSS selector, UUID string, or DOM element
|
|
9582
|
+
* @returns {Object} Plain object mapping field names to values
|
|
9583
|
+
* @category Component
|
|
9584
|
+
* @see bw.makeForm
|
|
9585
|
+
* @see bw.makeInput
|
|
9586
|
+
* @example
|
|
9587
|
+
* // Given a form with name="email" input and name="agree" checkbox:
|
|
9588
|
+
* var data = bw.formData('#signup-form');
|
|
9589
|
+
* // => { email: 'user@example.com', agree: true }
|
|
9590
|
+
*
|
|
9591
|
+
* // Collect and publish in one step:
|
|
9592
|
+
* bw.pub('form:submit', bw.formData('#my-form'));
|
|
9593
|
+
*
|
|
9594
|
+
* // Works with any container, not just <form>:
|
|
9595
|
+
* bw.pub('settings:changed', bw.formData('.settings-panel'));
|
|
9596
|
+
*/
|
|
9597
|
+
bw.formData = function(target) {
|
|
9598
|
+
var el = bw.el(target);
|
|
9599
|
+
if (!el) return {};
|
|
9600
|
+
var result = {};
|
|
9601
|
+
var inputs = el.querySelectorAll('input, select, textarea');
|
|
9602
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
9603
|
+
var inp = inputs[i];
|
|
9604
|
+
var key = inp.name || inp.id;
|
|
9605
|
+
if (!key) continue;
|
|
9606
|
+
if (inp.type === 'checkbox') {
|
|
9607
|
+
result[key] = inp.checked;
|
|
9608
|
+
} else if (inp.type === 'radio') {
|
|
9609
|
+
if (inp.checked) result[key] = inp.value;
|
|
9610
|
+
} else if (inp.tagName === 'SELECT' && inp.multiple) {
|
|
9611
|
+
result[key] = [];
|
|
9612
|
+
for (var j = 0; j < inp.options.length; j++) {
|
|
9613
|
+
if (inp.options[j].selected) result[key].push(inp.options[j].value);
|
|
9614
|
+
}
|
|
9615
|
+
} else {
|
|
9616
|
+
result[key] = inp.value;
|
|
9617
|
+
}
|
|
9618
|
+
}
|
|
9619
|
+
return result;
|
|
9620
|
+
};
|
|
9621
|
+
|
|
9622
|
+
// ===================================================================================
|
|
9623
|
+
// bw.jsonPatch() — RFC 6902 JSON Patch on plain objects
|
|
9624
|
+
// ===================================================================================
|
|
9625
|
+
|
|
9626
|
+
/**
|
|
9627
|
+
* Apply RFC 6902 JSON Patch operations to a plain object.
|
|
9628
|
+
*
|
|
9629
|
+
* Supported operations: add, remove, replace, move, copy, test.
|
|
9630
|
+
* Paths use JSON Pointer (RFC 6901) notation: `/foo/bar/0`.
|
|
9631
|
+
* Mutates the target object in place and returns it.
|
|
9632
|
+
*
|
|
9633
|
+
* @param {Object} obj - Target object to patch
|
|
9634
|
+
* @param {Array<Object>} ops - Array of patch operations
|
|
9635
|
+
* @param {string} ops[].op - Operation: 'add', 'remove', 'replace', 'move', 'copy', 'test'
|
|
9636
|
+
* @param {string} ops[].path - JSON Pointer path (e.g. '/a/b/0')
|
|
9637
|
+
* @param {*} [ops[].value] - Value for add/replace/test
|
|
9638
|
+
* @param {string} [ops[].from] - Source path for move/copy
|
|
9639
|
+
* @returns {Object} The patched object (same reference)
|
|
9640
|
+
* @throws {Error} On invalid op, missing path, test failure, or path not found for remove
|
|
9641
|
+
* @category Data Utilities
|
|
9642
|
+
* @see bw.patch
|
|
9643
|
+
* @example
|
|
9644
|
+
* var obj = { a: 1, b: { c: 2 } };
|
|
9645
|
+
* bw.jsonPatch(obj, [
|
|
9646
|
+
* { op: 'replace', path: '/a', value: 10 },
|
|
9647
|
+
* { op: 'add', path: '/b/d', value: 3 },
|
|
9648
|
+
* { op: 'remove', path: '/b/c' }
|
|
9649
|
+
* ]);
|
|
9650
|
+
* // obj => { a: 10, b: { d: 3 } }
|
|
9651
|
+
*/
|
|
9652
|
+
bw.jsonPatch = function(obj, ops) {
|
|
9653
|
+
if (!_isA(ops)) return obj;
|
|
9654
|
+
|
|
9655
|
+
// Parse JSON Pointer path to array of keys
|
|
9656
|
+
function parsePath(path) {
|
|
9657
|
+
if (path === '') return [];
|
|
9658
|
+
if (path.charAt(0) !== '/') throw new Error('Invalid JSON Pointer: ' + path);
|
|
9659
|
+
return path.slice(1).split('/').map(function(s) {
|
|
9660
|
+
return s.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
9661
|
+
});
|
|
9662
|
+
}
|
|
9663
|
+
|
|
9664
|
+
// Walk to parent of final key; return { parent, key }
|
|
9665
|
+
function resolve(root, keys) {
|
|
9666
|
+
var parent = root;
|
|
9667
|
+
for (var i = 0; i < keys.length - 1; i++) {
|
|
9668
|
+
var k = _isA(parent) ? parseInt(keys[i], 10) : keys[i];
|
|
9669
|
+
if (parent[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
9670
|
+
parent = parent[k];
|
|
9671
|
+
}
|
|
9672
|
+
return { parent: parent, key: _isA(parent) ? parseInt(keys[keys.length - 1], 10) : keys[keys.length - 1] };
|
|
9673
|
+
}
|
|
9674
|
+
|
|
9675
|
+
// Get value at path
|
|
9676
|
+
function getVal(root, keys) {
|
|
9677
|
+
var cur = root;
|
|
9678
|
+
for (var i = 0; i < keys.length; i++) {
|
|
9679
|
+
var k = _isA(cur) ? parseInt(keys[i], 10) : keys[i];
|
|
9680
|
+
if (cur[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
9681
|
+
cur = cur[k];
|
|
9682
|
+
}
|
|
9683
|
+
return cur;
|
|
9684
|
+
}
|
|
9685
|
+
|
|
9686
|
+
for (var i = 0; i < ops.length; i++) {
|
|
9687
|
+
var op = ops[i];
|
|
9688
|
+
if (!op.op || !_is(op.path, 'string')) throw new Error('Invalid patch operation at index ' + i);
|
|
9689
|
+
var keys = parsePath(op.path);
|
|
9690
|
+
|
|
9691
|
+
var r, val, fromKeys, fr, tr, cr;
|
|
9692
|
+
switch (op.op) {
|
|
9693
|
+
case 'add': {
|
|
9694
|
+
if (keys.length === 0) throw new Error('Cannot add to root');
|
|
9695
|
+
r = resolve(obj, keys);
|
|
9696
|
+
if (_isA(r.parent) && r.key <= r.parent.length) {
|
|
9697
|
+
r.parent.splice(r.key, 0, op.value);
|
|
9698
|
+
} else {
|
|
9699
|
+
r.parent[r.key] = op.value;
|
|
9700
|
+
}
|
|
9701
|
+
break;
|
|
9702
|
+
}
|
|
9703
|
+
case 'remove': {
|
|
9704
|
+
if (keys.length === 0) throw new Error('Cannot remove root');
|
|
9705
|
+
r = resolve(obj, keys);
|
|
9706
|
+
if (_isA(r.parent)) {
|
|
9707
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
9708
|
+
r.parent.splice(r.key, 1);
|
|
9709
|
+
} else {
|
|
9710
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
9711
|
+
delete r.parent[r.key];
|
|
9712
|
+
}
|
|
9713
|
+
break;
|
|
9714
|
+
}
|
|
9715
|
+
case 'replace': {
|
|
9716
|
+
if (keys.length === 0) throw new Error('Cannot replace root');
|
|
9717
|
+
r = resolve(obj, keys);
|
|
9718
|
+
if (_isA(r.parent)) {
|
|
9719
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
9720
|
+
} else {
|
|
9721
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
9722
|
+
}
|
|
9723
|
+
r.parent[r.key] = op.value;
|
|
9724
|
+
break;
|
|
9725
|
+
}
|
|
9726
|
+
case 'move': {
|
|
9727
|
+
if (!_is(op.from, 'string')) throw new Error('move requires "from"');
|
|
9728
|
+
fromKeys = parsePath(op.from);
|
|
9729
|
+
val = getVal(obj, fromKeys);
|
|
9730
|
+
fr = resolve(obj, fromKeys);
|
|
9731
|
+
if (_isA(fr.parent)) { fr.parent.splice(fr.key, 1); }
|
|
9732
|
+
else { delete fr.parent[fr.key]; }
|
|
9733
|
+
tr = resolve(obj, keys);
|
|
9734
|
+
if (_isA(tr.parent) && tr.key <= tr.parent.length) {
|
|
9735
|
+
tr.parent.splice(tr.key, 0, val);
|
|
9736
|
+
} else {
|
|
9737
|
+
tr.parent[tr.key] = val;
|
|
9738
|
+
}
|
|
9739
|
+
break;
|
|
9740
|
+
}
|
|
9741
|
+
case 'copy': {
|
|
9742
|
+
if (!_is(op.from, 'string')) throw new Error('copy requires "from"');
|
|
9743
|
+
val = getVal(obj, parsePath(op.from));
|
|
9744
|
+
cr = resolve(obj, keys);
|
|
9745
|
+
if (_isA(cr.parent) && cr.key <= cr.parent.length) {
|
|
9746
|
+
cr.parent.splice(cr.key, 0, val);
|
|
9747
|
+
} else {
|
|
9748
|
+
cr.parent[cr.key] = val;
|
|
9749
|
+
}
|
|
9750
|
+
break;
|
|
9751
|
+
}
|
|
9752
|
+
case 'test': {
|
|
9753
|
+
var actual = getVal(obj, keys);
|
|
9754
|
+
if (JSON.stringify(actual) !== JSON.stringify(op.value)) {
|
|
9755
|
+
throw new Error('Test failed: ' + op.path + ' expected ' + JSON.stringify(op.value) + ' got ' + JSON.stringify(actual));
|
|
9756
|
+
}
|
|
9757
|
+
break;
|
|
9758
|
+
}
|
|
9759
|
+
default:
|
|
9760
|
+
throw new Error('Unknown op: ' + op.op);
|
|
9761
|
+
}
|
|
9762
|
+
}
|
|
9763
|
+
return obj;
|
|
9764
|
+
};
|
|
9765
|
+
|
|
9435
9766
|
// ===================================================================================
|
|
9436
9767
|
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
9437
9768
|
// ===================================================================================
|
|
@@ -9573,7 +9904,7 @@ bw.apply = function(msg) {
|
|
|
9573
9904
|
var target = msg.target;
|
|
9574
9905
|
|
|
9575
9906
|
if (type === 'replace') {
|
|
9576
|
-
var el = bw.
|
|
9907
|
+
var el = bw.el(target);
|
|
9577
9908
|
if (!el) return false;
|
|
9578
9909
|
bw.DOM(el, msg.node);
|
|
9579
9910
|
return true;
|
|
@@ -9583,14 +9914,14 @@ bw.apply = function(msg) {
|
|
|
9583
9914
|
return patched !== null;
|
|
9584
9915
|
|
|
9585
9916
|
} else if (type === 'append') {
|
|
9586
|
-
var parent = bw.
|
|
9917
|
+
var parent = bw.el(target);
|
|
9587
9918
|
if (!parent) return false;
|
|
9588
9919
|
var child = bw.createDOM(msg.node);
|
|
9589
9920
|
parent.appendChild(child);
|
|
9590
9921
|
return true;
|
|
9591
9922
|
|
|
9592
9923
|
} else if (type === 'remove') {
|
|
9593
|
-
var toRemove = bw.
|
|
9924
|
+
var toRemove = bw.el(target);
|
|
9594
9925
|
if (!toRemove) return false;
|
|
9595
9926
|
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
9596
9927
|
toRemove.remove();
|
|
@@ -9650,30 +9981,98 @@ bw.apply = function(msg) {
|
|
|
9650
9981
|
|
|
9651
9982
|
|
|
9652
9983
|
// ===================================================================================
|
|
9653
|
-
// bw.inspect() —
|
|
9984
|
+
// bw.inspect() — DOM introspection with bitwrench metadata
|
|
9654
9985
|
// ===================================================================================
|
|
9655
9986
|
|
|
9656
9987
|
/**
|
|
9657
|
-
* Inspect a DOM element
|
|
9658
|
-
*
|
|
9659
|
-
*
|
|
9660
|
-
*
|
|
9661
|
-
*
|
|
9988
|
+
* Inspect a DOM element and its subtree, returning a plain-object
|
|
9989
|
+
* representation with bitwrench metadata at each node. Useful for debugging,
|
|
9990
|
+
* devtools, MCP/AG-UI tool discovery, and automated testing.
|
|
9991
|
+
*
|
|
9992
|
+
* Each node in the returned tree includes:
|
|
9993
|
+
* - `tag` -- lowercase tag name (or '#text' for text nodes)
|
|
9994
|
+
* - `id` -- element id (if set)
|
|
9995
|
+
* - `uuid` -- bitwrench UUID class (if lifecycle-managed)
|
|
9996
|
+
* - `type` -- component type from o.type (if set, e.g. 'card', 'tabs')
|
|
9997
|
+
* - `classes` -- first 5 CSS classes (string, space-separated)
|
|
9998
|
+
* - `handles` -- array of el.bw method names (if any)
|
|
9999
|
+
* - `state` -- copy of _bw_state (if any)
|
|
10000
|
+
* - `hasRender` -- true if _bw_render is set
|
|
10001
|
+
* - `hasSubs` -- true if element has pub/sub subscriptions
|
|
10002
|
+
* - `refs` -- copy of _bw_refs keys (if any)
|
|
10003
|
+
* - `children` -- array of child node trees (up to depth limit, max 50 per level)
|
|
10004
|
+
*
|
|
10005
|
+
* @param {string|Element} target - CSS selector, UUID, or DOM element
|
|
10006
|
+
* @param {number} [depth=3] - Maximum recursion depth (0 = target only, no children)
|
|
10007
|
+
* @returns {Object|null} Plain object tree, or null if element not found
|
|
9662
10008
|
* @category Component
|
|
9663
10009
|
* @example
|
|
9664
|
-
*
|
|
9665
|
-
* bw.inspect(
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
10010
|
+
* // Get full tree from #app, 3 levels deep (default):
|
|
10011
|
+
* var info = bw.inspect('#app');
|
|
10012
|
+
*
|
|
10013
|
+
* // Shallow inspection (just the element, no children):
|
|
10014
|
+
* var info = bw.inspect('#my-carousel', 0);
|
|
10015
|
+
* console.log(info.handles); // ['next', 'prev', 'goToSlide']
|
|
10016
|
+
* console.log(info.type); // 'carousel'
|
|
10017
|
+
*
|
|
10018
|
+
* // Deep inspection for debugging:
|
|
10019
|
+
* console.log(JSON.stringify(bw.inspect('#app', 5), null, 2));
|
|
10020
|
+
*/
|
|
10021
|
+
bw.inspect = function(target, depth) {
|
|
10022
|
+
var el = bw.el(target);
|
|
10023
|
+
if (!el && _is(target, 'string')) el = bw.$(target)[0];
|
|
10024
|
+
if (!el) return null;
|
|
10025
|
+
if (depth === undefined || depth === null) depth = 3;
|
|
10026
|
+
|
|
10027
|
+
function walk(node, d) {
|
|
10028
|
+
if (!node) return null;
|
|
10029
|
+
// Skip non-element nodes (text, comment, etc.)
|
|
10030
|
+
if (node.nodeType !== 1) return null;
|
|
10031
|
+
|
|
10032
|
+
var info = { tag: node.tagName ? node.tagName.toLowerCase() : '#text' };
|
|
10033
|
+
|
|
10034
|
+
// Identity
|
|
10035
|
+
if (node.id) info.id = node.id;
|
|
10036
|
+
var uuid = bw.getUUID(node);
|
|
10037
|
+
if (uuid) info.uuid = uuid;
|
|
10038
|
+
if (node._bw_type) info.type = node._bw_type;
|
|
10039
|
+
|
|
10040
|
+
// CSS classes (first 5 for readability)
|
|
10041
|
+
if (node.className && typeof node.className === 'string') {
|
|
10042
|
+
info.classes = node.className.split(' ').slice(0, 5).join(' ');
|
|
10043
|
+
}
|
|
10044
|
+
|
|
10045
|
+
// Bitwrench handle methods
|
|
10046
|
+
if (node.bw) {
|
|
10047
|
+
var handles = _keys(node.bw);
|
|
10048
|
+
if (handles.length > 0) info.handles = handles;
|
|
10049
|
+
}
|
|
10050
|
+
|
|
10051
|
+
// State
|
|
10052
|
+
if (node._bw_state) info.state = node._bw_state;
|
|
10053
|
+
if (node._bw_render) info.hasRender = true;
|
|
10054
|
+
if (node._bw_subs && node._bw_subs.length > 0) info.hasSubs = true;
|
|
10055
|
+
|
|
10056
|
+
// Refs
|
|
10057
|
+
if (node._bw_refs) info.refs = _keys(node._bw_refs);
|
|
10058
|
+
|
|
10059
|
+
// Children (recurse up to depth limit, max 50 children per level)
|
|
10060
|
+
if (d < depth && node.children && node.children.length > 0) {
|
|
10061
|
+
info.children = [];
|
|
10062
|
+
var max = Math.min(node.children.length, 50);
|
|
10063
|
+
for (var i = 0; i < max; i++) {
|
|
10064
|
+
var child = walk(node.children[i], d + 1);
|
|
10065
|
+
if (child) info.children.push(child);
|
|
10066
|
+
}
|
|
10067
|
+
if (node.children.length > 50) {
|
|
10068
|
+
info.children.push({ tag: '...', count: node.children.length - 50 });
|
|
10069
|
+
}
|
|
10070
|
+
}
|
|
10071
|
+
|
|
10072
|
+
return info;
|
|
10073
|
+
}
|
|
10074
|
+
|
|
10075
|
+
return walk(el, 0);
|
|
9677
10076
|
};
|
|
9678
10077
|
|
|
9679
10078
|
bw.compile = function() { throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
@@ -9895,37 +10294,49 @@ bw.clip = clip;
|
|
|
9895
10294
|
* so you can use `.map()`, `.filter()`, etc. directly. Accepts CSS selectors,
|
|
9896
10295
|
* single elements, NodeLists, or arrays.
|
|
9897
10296
|
*
|
|
10297
|
+
* With an optional second argument, applies content or a function to
|
|
10298
|
+
* every matched element (same apply rules as `bw.el()`):
|
|
10299
|
+
* - string/number: sets `el.textContent`
|
|
10300
|
+
* - function: calls `apply(el)` for each element
|
|
10301
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
10302
|
+
* - array: clears children, appends each item
|
|
10303
|
+
*
|
|
9898
10304
|
* @param {string|Element|Array} selector - CSS selector, element, or array
|
|
10305
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
9899
10306
|
* @returns {Array} Array of DOM elements
|
|
9900
10307
|
* @category DOM Selection
|
|
10308
|
+
* @see bw.el
|
|
9901
10309
|
* @example
|
|
9902
|
-
* bw.$('.card')
|
|
9903
|
-
* bw.$(
|
|
9904
|
-
* bw.$('.card'
|
|
10310
|
+
* bw.$('.card') // => [div.card, div.card, ...]
|
|
10311
|
+
* bw.$('.status', 'Online') // set text on all .status elements
|
|
10312
|
+
* bw.$('.card', function(el) { // apply function to each
|
|
10313
|
+
* el.style.opacity = '0.5';
|
|
10314
|
+
* })
|
|
9905
10315
|
*/
|
|
9906
10316
|
if (bw._isBrowser) {
|
|
9907
|
-
bw.$ = function(selector) {
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
if (_isA(selector))
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
if (
|
|
9918
|
-
|
|
10317
|
+
bw.$ = function(selector, apply) {
|
|
10318
|
+
var els;
|
|
10319
|
+
if (!selector) {
|
|
10320
|
+
els = [];
|
|
10321
|
+
} else if (_isA(selector)) {
|
|
10322
|
+
els = selector;
|
|
10323
|
+
} else if (selector.nodeType) {
|
|
10324
|
+
els = [selector];
|
|
10325
|
+
} else if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
10326
|
+
els = Array.from(selector);
|
|
10327
|
+
} else if (_is(selector, 'string')) {
|
|
10328
|
+
els = Array.from(document.querySelectorAll(selector));
|
|
10329
|
+
} else {
|
|
10330
|
+
els = [];
|
|
9919
10331
|
}
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
return Array.from(document.querySelectorAll(selector));
|
|
10332
|
+
|
|
10333
|
+
if (apply !== undefined) {
|
|
10334
|
+
for (var i = 0; i < els.length; i++) _applyTo(els[i], apply);
|
|
9924
10335
|
}
|
|
9925
|
-
|
|
9926
|
-
return
|
|
10336
|
+
|
|
10337
|
+
return els;
|
|
9927
10338
|
};
|
|
9928
|
-
|
|
10339
|
+
|
|
9929
10340
|
// Convenience single element selector
|
|
9930
10341
|
bw.$.one = function(selector) {
|
|
9931
10342
|
return bw.$(selector)[0] || null;
|
|
@@ -10138,42 +10549,48 @@ bw.loadReset = function() {
|
|
|
10138
10549
|
};
|
|
10139
10550
|
|
|
10140
10551
|
/**
|
|
10141
|
-
* Toggle between primary and alternate palettes.
|
|
10552
|
+
* Toggle between primary and alternate theme palettes.
|
|
10142
10553
|
*
|
|
10143
|
-
* Adds/removes the `bw_theme_alt` class on the scoping element.
|
|
10554
|
+
* Adds/removes the `bw_theme_alt` class on the scoping element(s).
|
|
10144
10555
|
* Without a scope, toggles on `<html>` (global).
|
|
10145
|
-
* With a scope, toggles on
|
|
10556
|
+
* With a scope, toggles on ALL matching elements.
|
|
10146
10557
|
*
|
|
10147
|
-
* @param {string} [scope] -
|
|
10148
|
-
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
10558
|
+
* @param {string|Element} [scope] - Selector or element. Omit for global.
|
|
10559
|
+
* @returns {string} Active mode after toggle: 'primary' or 'alternate' (based on first element)
|
|
10149
10560
|
* @category CSS & Styling
|
|
10150
10561
|
* @see bw.applyStyles
|
|
10151
10562
|
* @see bw.clearStyles
|
|
10152
10563
|
* @example
|
|
10153
|
-
* bw.
|
|
10154
|
-
* bw.
|
|
10564
|
+
* bw.toggleThemeMode(); // global toggle on <html>
|
|
10565
|
+
* bw.toggleThemeMode('#my-dashboard'); // scoped toggle
|
|
10566
|
+
* bw.toggleThemeMode('.panel'); // toggle on ALL .panel elements
|
|
10155
10567
|
*/
|
|
10156
|
-
bw.
|
|
10568
|
+
bw.toggleThemeMode = function(scope) {
|
|
10157
10569
|
if (!bw._isBrowser) return 'primary';
|
|
10158
|
-
var
|
|
10570
|
+
var els;
|
|
10159
10571
|
if (scope) {
|
|
10160
|
-
|
|
10161
|
-
target = els[0];
|
|
10572
|
+
els = bw.$(scope);
|
|
10162
10573
|
} else {
|
|
10163
|
-
|
|
10574
|
+
els = [document.documentElement];
|
|
10164
10575
|
}
|
|
10165
|
-
if (!
|
|
10576
|
+
if (!els.length) return 'primary';
|
|
10166
10577
|
|
|
10167
|
-
var
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10578
|
+
var mode;
|
|
10579
|
+
for (var i = 0; i < els.length; i++) {
|
|
10580
|
+
var hasAlt = els[i].classList.contains('bw_theme_alt');
|
|
10581
|
+
if (hasAlt) {
|
|
10582
|
+
els[i].classList.remove('bw_theme_alt');
|
|
10583
|
+
} else {
|
|
10584
|
+
els[i].classList.add('bw_theme_alt');
|
|
10585
|
+
}
|
|
10586
|
+
if (i === 0) mode = hasAlt ? 'primary' : 'alternate';
|
|
10174
10587
|
}
|
|
10588
|
+
return mode;
|
|
10175
10589
|
};
|
|
10176
10590
|
|
|
10591
|
+
// Alias — kept for one release cycle. Use bw.toggleThemeMode() instead.
|
|
10592
|
+
bw.toggleStyles = bw.toggleThemeMode;
|
|
10593
|
+
|
|
10177
10594
|
/**
|
|
10178
10595
|
* Remove injected styles for a given scope.
|
|
10179
10596
|
*
|
|
@@ -11213,6 +11630,57 @@ Object.entries(components).forEach(([name, fn]) => {
|
|
|
11213
11630
|
}
|
|
11214
11631
|
});
|
|
11215
11632
|
|
|
11633
|
+
/**
|
|
11634
|
+
* Query the BCCL component registry. Returns metadata about registered
|
|
11635
|
+
* component types -- their names and factory function names. Useful for
|
|
11636
|
+
* tooling, introspection, documentation generators, and auto-complete
|
|
11637
|
+
* systems (including MCP/AG-UI tool discovery).
|
|
11638
|
+
*
|
|
11639
|
+
* With no arguments, returns an array of all registered component types.
|
|
11640
|
+
* With a type name, returns metadata for that single type (or null if
|
|
11641
|
+
* the type is not registered).
|
|
11642
|
+
*
|
|
11643
|
+
* @param {string} [type] - Optional component type name to look up
|
|
11644
|
+
* @returns {Array<Object>|Object|null} Array of {type, factory} objects,
|
|
11645
|
+
* a single {type, factory} object, or null if the type is not found
|
|
11646
|
+
* @category Component
|
|
11647
|
+
* @see bw.make
|
|
11648
|
+
* @see bw.BCCL
|
|
11649
|
+
* @example
|
|
11650
|
+
* // List all available component types:
|
|
11651
|
+
* bw.catalog();
|
|
11652
|
+
* // => [{ type: 'card', factory: 'makeCard' },
|
|
11653
|
+
* // { type: 'button', factory: 'makeButton' }, ...]
|
|
11654
|
+
*
|
|
11655
|
+
* // Look up a specific type:
|
|
11656
|
+
* bw.catalog('accordion');
|
|
11657
|
+
* // => { type: 'accordion', factory: 'makeAccordion' }
|
|
11658
|
+
*
|
|
11659
|
+
* // Check if a type exists:
|
|
11660
|
+
* if (bw.catalog('chart')) { ... }
|
|
11661
|
+
*
|
|
11662
|
+
* // Get just the type names:
|
|
11663
|
+
* bw.catalog().map(function(c) { return c.type; });
|
|
11664
|
+
* // => ['card', 'button', 'container', 'row', ...]
|
|
11665
|
+
*/
|
|
11666
|
+
bw.catalog = function(type) {
|
|
11667
|
+
if (type) {
|
|
11668
|
+
var def = bw.BCCL[type];
|
|
11669
|
+
if (!def) return null;
|
|
11670
|
+
return {
|
|
11671
|
+
type: type,
|
|
11672
|
+
factory: def.make.name || ('make' + type.charAt(0).toUpperCase() + type.slice(1))
|
|
11673
|
+
};
|
|
11674
|
+
}
|
|
11675
|
+
return Object.keys(bw.BCCL).map(function(k) {
|
|
11676
|
+
var def = bw.BCCL[k];
|
|
11677
|
+
return {
|
|
11678
|
+
type: k,
|
|
11679
|
+
factory: def.make.name || ('make' + k.charAt(0).toUpperCase() + k.slice(1))
|
|
11680
|
+
};
|
|
11681
|
+
});
|
|
11682
|
+
};
|
|
11683
|
+
|
|
11216
11684
|
// Also attach to global in browsers
|
|
11217
11685
|
if (bw._isBrowser && typeof window !== 'undefined') {
|
|
11218
11686
|
window.bw = bw;
|