bitwrench 2.0.25 → 2.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/bitwrench-bccl.cjs.js +1 -1
- package/dist/bitwrench-bccl.cjs.min.js +1 -1
- package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
- package/dist/bitwrench-bccl.esm.js +1 -1
- package/dist/bitwrench-bccl.esm.min.js +1 -1
- package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
- package/dist/bitwrench-bccl.umd.js +1 -1
- package/dist/bitwrench-bccl.umd.min.js +1 -1
- package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.cjs.min.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
- package/dist/bitwrench-debug.js +1 -1
- package/dist/bitwrench-debug.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +623 -155
- package/dist/bitwrench-lean.cjs.min.js +7 -7
- package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
- package/dist/bitwrench-lean.es5.js +650 -157
- package/dist/bitwrench-lean.es5.min.js +5 -5
- package/dist/bitwrench-lean.es5.min.js.gz +0 -0
- package/dist/bitwrench-lean.esm.js +623 -155
- package/dist/bitwrench-lean.esm.min.js +6 -6
- package/dist/bitwrench-lean.esm.min.js.gz +0 -0
- package/dist/bitwrench-lean.umd.js +623 -155
- package/dist/bitwrench-lean.umd.min.js +7 -7
- package/dist/bitwrench-lean.umd.min.js.gz +0 -0
- package/dist/bitwrench-util-css.cjs.js +1 -1
- package/dist/bitwrench-util-css.cjs.min.js +1 -1
- package/dist/bitwrench-util-css.es5.js +1 -1
- package/dist/bitwrench-util-css.es5.min.js +1 -1
- package/dist/bitwrench-util-css.esm.js +1 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -1
- package/dist/bitwrench-util-css.umd.js +1 -1
- package/dist/bitwrench-util-css.umd.min.js +1 -1
- package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
- package/dist/bitwrench.cjs.js +621 -153
- package/dist/bitwrench.cjs.min.js +6 -6
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +1 -1
- package/dist/bitwrench.d.ts +18 -11
- package/dist/bitwrench.es5.js +647 -154
- package/dist/bitwrench.es5.min.js +6 -6
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +621 -153
- package/dist/bitwrench.esm.min.js +5 -5
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.umd.js +621 -153
- package/dist/bitwrench.umd.min.js +6 -6
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +92 -92
- package/dist/bwserve.cjs.js +140 -7
- package/dist/bwserve.esm.js +141 -8
- package/dist/sri.json +45 -45
- package/docs/bitwrench-for-wasm.md +851 -0
- package/docs/bitwrench_api.md +133 -23
- package/docs/llm-bitwrench-guide.md +6 -5
- package/docs/state-management.md +27 -3
- package/docs/thinking-in-bitwrench.md +3 -2
- package/package.json +11 -9
- package/readme.html +17 -8
- package/src/bitwrench.d.ts +18 -11
- package/src/bitwrench.js +617 -148
- package/src/bwserve/bwclient.js +3 -3
- package/src/bwserve/client.js +26 -0
- package/src/bwserve/index.js +110 -3
- package/src/cli/attach.js +7 -5
- package/src/cli/serve.js +53 -10
- package/src/version.js +3 -3
package/dist/bitwrench.es5.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bitwrench v2.0.
|
|
1
|
+
/*! bitwrench v2.0.31 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
@@ -190,14 +190,14 @@
|
|
|
190
190
|
*/
|
|
191
191
|
|
|
192
192
|
var VERSION_INFO = {
|
|
193
|
-
version: '2.0.
|
|
193
|
+
version: '2.0.31',
|
|
194
194
|
name: 'bitwrench',
|
|
195
195
|
description: 'A library for javascript UI functions.',
|
|
196
196
|
license: 'BSD-2-Clause',
|
|
197
197
|
homepage: 'https://deftio.github.com/bitwrench/pages',
|
|
198
198
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
199
199
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
200
|
-
buildDate: '2026-
|
|
200
|
+
buildDate: '2026-04-12T07:56:29.791Z'
|
|
201
201
|
};
|
|
202
202
|
|
|
203
203
|
/**
|
|
@@ -10100,9 +10100,6 @@
|
|
|
10100
10100
|
var _cw = function _cw() {
|
|
10101
10101
|
console.warn.apply(console, arguments);
|
|
10102
10102
|
};
|
|
10103
|
-
var _cl = function _cl() {
|
|
10104
|
-
console.log.apply(console, arguments);
|
|
10105
|
-
};
|
|
10106
10103
|
var _ce = function _ce() {
|
|
10107
10104
|
console.error.apply(console, arguments);
|
|
10108
10105
|
};
|
|
@@ -10243,60 +10240,104 @@
|
|
|
10243
10240
|
};
|
|
10244
10241
|
|
|
10245
10242
|
/**
|
|
10246
|
-
* Look up a DOM element by ID
|
|
10243
|
+
* Look up a single DOM element by ID, CSS selector, UUID, or element ref.
|
|
10244
|
+
* Optionally apply content or a function to the resolved element.
|
|
10247
10245
|
*
|
|
10248
|
-
* Resolution order:
|
|
10249
|
-
* 1. Check `bw._nodeMap[id]`
|
|
10250
|
-
* 2.
|
|
10251
|
-
* 3.
|
|
10252
|
-
* 4.
|
|
10253
|
-
* 5. Cache the result for next time
|
|
10246
|
+
* Resolution order for string targets:
|
|
10247
|
+
* 1. Check `bw._nodeMap[id]` cache (O(1), stale entries auto-pruned)
|
|
10248
|
+
* 2. `document.getElementById(id)`
|
|
10249
|
+
* 3. `document.querySelector(id)` for selectors starting with # or .
|
|
10250
|
+
* 4. Class-based lookup for `bw_uuid_*` tokens
|
|
10254
10251
|
*
|
|
10255
|
-
*
|
|
10256
|
-
*
|
|
10257
|
-
*
|
|
10258
|
-
*
|
|
10252
|
+
* With one argument, returns the element (or null). With two arguments,
|
|
10253
|
+
* applies the second argument to the element and returns the element:
|
|
10254
|
+
* - string/number: sets `el.textContent`
|
|
10255
|
+
* - function: calls `apply(el)`, returns el
|
|
10256
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
10257
|
+
* - array: clears children, appends each item (string -> text node, TACO -> element)
|
|
10259
10258
|
*
|
|
10260
|
-
* @param {string|Element}
|
|
10259
|
+
* @param {string|Element} target - Element ref, ID, CSS selector, or bw_uuid_* class
|
|
10260
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
10261
10261
|
* @returns {Element|null} The DOM element, or null if not found
|
|
10262
|
-
* @category
|
|
10262
|
+
* @category DOM Selection
|
|
10263
|
+
* @see bw.$
|
|
10264
|
+
* @see bw.patch
|
|
10265
|
+
* @example
|
|
10266
|
+
* bw.el('#title') // lookup
|
|
10267
|
+
* bw.el('#title', 'Hello') // set text content
|
|
10268
|
+
* bw.el('#app', { t: 'h1', c: 'Hi' }) // mount TACO
|
|
10269
|
+
* bw.el('.card', function(el) { // apply function
|
|
10270
|
+
* el.style.opacity = '0.5';
|
|
10271
|
+
* })
|
|
10263
10272
|
*/
|
|
10264
|
-
bw.
|
|
10265
|
-
//
|
|
10266
|
-
|
|
10267
|
-
if (!
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
if (cached
|
|
10275
|
-
|
|
10273
|
+
bw.el = function (target, apply) {
|
|
10274
|
+
// Resolve target to element
|
|
10275
|
+
var el;
|
|
10276
|
+
if (!_is(target, 'string')) {
|
|
10277
|
+
el = target || null;
|
|
10278
|
+
} else if (!target || !bw._isBrowser) {
|
|
10279
|
+
el = null;
|
|
10280
|
+
} else {
|
|
10281
|
+
// 1. Check cache
|
|
10282
|
+
var cached = bw._nodeMap[target];
|
|
10283
|
+
if (cached) {
|
|
10284
|
+
if (cached.parentNode !== null) {
|
|
10285
|
+
el = cached;
|
|
10286
|
+
} else {
|
|
10287
|
+
delete bw._nodeMap[target];
|
|
10288
|
+
}
|
|
10289
|
+
}
|
|
10290
|
+
if (!el) {
|
|
10291
|
+
// 2. getElementById
|
|
10292
|
+
el = document.getElementById(target);
|
|
10293
|
+
// 3. querySelector for CSS selectors
|
|
10294
|
+
if (!el && (target.charAt(0) === '#' || target.charAt(0) === '.')) {
|
|
10295
|
+
el = document.querySelector(target);
|
|
10296
|
+
}
|
|
10297
|
+
// 4. bw_uuid_* class lookup
|
|
10298
|
+
if (!el && target.indexOf('bw_uuid_') === 0) {
|
|
10299
|
+
el = document.querySelector('.' + target);
|
|
10300
|
+
}
|
|
10301
|
+
// 5. Cache result
|
|
10302
|
+
if (el) bw._nodeMap[target] = el;
|
|
10276
10303
|
}
|
|
10277
|
-
// Stale — remove and fall through
|
|
10278
|
-
delete bw._nodeMap[id];
|
|
10279
10304
|
}
|
|
10280
10305
|
|
|
10281
|
-
//
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
if (!el && (id.charAt(0) === '#' || id.charAt(0) === '.')) {
|
|
10286
|
-
el = document.querySelector(id);
|
|
10287
|
-
}
|
|
10306
|
+
// Apply (if provided and element found)
|
|
10307
|
+
if (el && apply !== undefined) _applyTo(el, apply);
|
|
10308
|
+
return el;
|
|
10309
|
+
};
|
|
10288
10310
|
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10311
|
+
/**
|
|
10312
|
+
* Internal: apply content or function to a DOM element.
|
|
10313
|
+
* Shared by bw.el() and bw.$().
|
|
10314
|
+
* @private
|
|
10315
|
+
*/
|
|
10316
|
+
function _applyTo(el, apply) {
|
|
10317
|
+
if (_is(apply, 'function')) {
|
|
10318
|
+
apply(el);
|
|
10319
|
+
} else if (_isA(apply)) {
|
|
10320
|
+
el.innerHTML = '';
|
|
10321
|
+
apply.forEach(function (item) {
|
|
10322
|
+
if (item != null) {
|
|
10323
|
+
if (_is(item, 'object') && item.t) {
|
|
10324
|
+
el.appendChild(bw.createDOM(item));
|
|
10325
|
+
} else {
|
|
10326
|
+
el.appendChild(document.createTextNode(String(item)));
|
|
10327
|
+
}
|
|
10328
|
+
}
|
|
10329
|
+
});
|
|
10330
|
+
} else if (_is(apply, 'object') && apply !== null && apply.t) {
|
|
10331
|
+
el.innerHTML = '';
|
|
10332
|
+
el.appendChild(bw.createDOM(apply));
|
|
10333
|
+
} else {
|
|
10334
|
+
el.textContent = String(apply);
|
|
10292
10335
|
}
|
|
10336
|
+
}
|
|
10293
10337
|
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
}
|
|
10298
|
-
return el;
|
|
10299
|
-
};
|
|
10338
|
+
// Internal alias — kept for one release cycle (v2.0.26).
|
|
10339
|
+
// Will be removed in v2.0.27. Use bw.el() instead.
|
|
10340
|
+
bw._el = bw.el;
|
|
10300
10341
|
|
|
10301
10342
|
/**
|
|
10302
10343
|
* Register a DOM element in the node cache under one or more keys.
|
|
@@ -10360,6 +10401,12 @@
|
|
|
10360
10401
|
*/
|
|
10361
10402
|
var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
|
|
10362
10403
|
|
|
10404
|
+
/**
|
|
10405
|
+
* SVG namespace URI for createElementNS.
|
|
10406
|
+
* @private
|
|
10407
|
+
*/
|
|
10408
|
+
var _SVG_NS = 'http://www.w3.org/2000/svg';
|
|
10409
|
+
|
|
10363
10410
|
/**
|
|
10364
10411
|
* Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
|
|
10365
10412
|
*
|
|
@@ -10410,9 +10457,9 @@
|
|
|
10410
10457
|
bw.getUUID = function (tacoOrElement) {
|
|
10411
10458
|
if (!tacoOrElement) return null;
|
|
10412
10459
|
var classStr;
|
|
10413
|
-
// DOM element: check className
|
|
10460
|
+
// DOM element: check className (SVG elements use getAttribute for string value)
|
|
10414
10461
|
if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
|
|
10415
|
-
classStr = tacoOrElement.className;
|
|
10462
|
+
classStr = typeof tacoOrElement.className === 'string' ? tacoOrElement.className : tacoOrElement.getAttribute('class') || '';
|
|
10416
10463
|
}
|
|
10417
10464
|
// TACO object: check a.class
|
|
10418
10465
|
else if (tacoOrElement.a && _is(tacoOrElement.a["class"], 'string')) {
|
|
@@ -10697,7 +10744,7 @@
|
|
|
10697
10744
|
var fnCounterBefore = bw._fnIDCounter;
|
|
10698
10745
|
|
|
10699
10746
|
// Render body content
|
|
10700
|
-
var bodyHTML
|
|
10747
|
+
var bodyHTML;
|
|
10701
10748
|
if (_is(body, 'string')) {
|
|
10702
10749
|
bodyHTML = body;
|
|
10703
10750
|
} else {
|
|
@@ -10873,8 +10920,10 @@
|
|
|
10873
10920
|
_taco$o2 = taco.o,
|
|
10874
10921
|
opts = _taco$o2 === void 0 ? {} : _taco$o2;
|
|
10875
10922
|
|
|
10876
|
-
//
|
|
10877
|
-
|
|
10923
|
+
// SVG namespace: detect SVG context and thread through children.
|
|
10924
|
+
// {t:'svg'} starts SVG context; foreignObject children revert to HTML.
|
|
10925
|
+
var svgCtx = options._svgCtx || tag === 'svg';
|
|
10926
|
+
var el = svgCtx ? document.createElementNS(_SVG_NS, tag) : document.createElement(tag);
|
|
10878
10927
|
|
|
10879
10928
|
// Set attributes
|
|
10880
10929
|
for (var _i2 = 0, _Object$entries2 = Object.entries(attrs); _i2 < _Object$entries2.length; _i2++) {
|
|
@@ -10887,9 +10936,10 @@
|
|
|
10887
10936
|
Object.assign(el.style, value);
|
|
10888
10937
|
} else if (key === 'class') {
|
|
10889
10938
|
// Handle class as array or string
|
|
10939
|
+
// SVG elements use SVGAnimatedString for className, so use setAttribute
|
|
10890
10940
|
var classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
10891
10941
|
if (classStr) {
|
|
10892
|
-
el.className = classStr;
|
|
10942
|
+
if (svgCtx) el.setAttribute('class', classStr);else el.className = classStr;
|
|
10893
10943
|
}
|
|
10894
10944
|
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
10895
10945
|
// Event handlers
|
|
@@ -10910,11 +10960,19 @@
|
|
|
10910
10960
|
// Add children, building _bw_refs for fast parent→child access.
|
|
10911
10961
|
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
10912
10962
|
// so o.render functions can access them without any DOM lookup.
|
|
10963
|
+
// SVG: foreignObject children revert to HTML namespace; otherwise inherit.
|
|
10964
|
+
var childOpts = options;
|
|
10965
|
+
var childSvgCtx = svgCtx && tag !== 'foreignObject';
|
|
10966
|
+
if (childSvgCtx !== (options._svgCtx || false)) {
|
|
10967
|
+
childOpts = Object.assign({}, options, {
|
|
10968
|
+
_svgCtx: childSvgCtx || undefined
|
|
10969
|
+
});
|
|
10970
|
+
}
|
|
10913
10971
|
if (content != null) {
|
|
10914
10972
|
if (_isA(content)) {
|
|
10915
10973
|
content.forEach(function (child) {
|
|
10916
10974
|
if (child != null) {
|
|
10917
|
-
var childEl = bw.createDOM(child,
|
|
10975
|
+
var childEl = bw.createDOM(child, childOpts);
|
|
10918
10976
|
el.appendChild(childEl);
|
|
10919
10977
|
// Build local refs for addressable children
|
|
10920
10978
|
var childRefId = child && child.a ? child.a.id || bw.getUUID(child) : null;
|
|
@@ -10937,7 +10995,7 @@
|
|
|
10937
10995
|
// Raw HTML content — inject via innerHTML
|
|
10938
10996
|
el.innerHTML = content.v;
|
|
10939
10997
|
} else if (_is(content, 'object') && content.t) {
|
|
10940
|
-
var childEl = bw.createDOM(content,
|
|
10998
|
+
var childEl = bw.createDOM(content, childOpts);
|
|
10941
10999
|
el.appendChild(childEl);
|
|
10942
11000
|
var childRefId = content.a ? content.a.id || bw.getUUID(content) : null;
|
|
10943
11001
|
if (childRefId) {
|
|
@@ -10963,13 +11021,21 @@
|
|
|
10963
11021
|
}
|
|
10964
11022
|
|
|
10965
11023
|
// Register UUID class in node cache (bw_uuid_* tokens in class string)
|
|
10966
|
-
|
|
10967
|
-
|
|
11024
|
+
// SVG elements have SVGAnimatedString for className; use getAttribute instead
|
|
11025
|
+
var clsStr = svgCtx ? el.getAttribute('class') || '' : el.className;
|
|
11026
|
+
if (clsStr) {
|
|
11027
|
+
var uuidMatch = clsStr.match(_UUID_RE);
|
|
10968
11028
|
if (uuidMatch) {
|
|
10969
11029
|
bw._nodeMap[uuidMatch[0]] = el;
|
|
10970
11030
|
}
|
|
10971
11031
|
}
|
|
10972
11032
|
|
|
11033
|
+
// Store component type metadata (e.g., 'card', 'tabs') for introspection.
|
|
11034
|
+
// BCCL factories set o.type; custom components can too.
|
|
11035
|
+
if (opts.type) {
|
|
11036
|
+
el._bw_type = opts.type;
|
|
11037
|
+
}
|
|
11038
|
+
|
|
10973
11039
|
// Handle lifecycle hooks and state
|
|
10974
11040
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
10975
11041
|
// Ensure element has a UUID class for identity
|
|
@@ -10998,11 +11064,19 @@
|
|
|
10998
11064
|
} : null);
|
|
10999
11065
|
if (mountFn) {
|
|
11000
11066
|
if (document.body.contains(el)) {
|
|
11001
|
-
|
|
11067
|
+
try {
|
|
11068
|
+
mountFn(el, el._bw_state || {});
|
|
11069
|
+
} catch (e) {
|
|
11070
|
+
_cw('o.mounted error: ' + e.message);
|
|
11071
|
+
}
|
|
11002
11072
|
} else {
|
|
11003
11073
|
requestAnimationFrame(function () {
|
|
11004
11074
|
if (document.body.contains(el)) {
|
|
11005
|
-
|
|
11075
|
+
try {
|
|
11076
|
+
mountFn(el, el._bw_state || {});
|
|
11077
|
+
} catch (e) {
|
|
11078
|
+
_cw('o.mounted error: ' + e.message);
|
|
11079
|
+
}
|
|
11006
11080
|
}
|
|
11007
11081
|
});
|
|
11008
11082
|
}
|
|
@@ -11011,7 +11085,11 @@
|
|
|
11011
11085
|
// Store unmount callback keyed by UUID class
|
|
11012
11086
|
if (opts.unmount) {
|
|
11013
11087
|
bw._unmountCallbacks.set(uuid, function () {
|
|
11014
|
-
|
|
11088
|
+
try {
|
|
11089
|
+
opts.unmount(el, el._bw_state || {});
|
|
11090
|
+
} catch (e) {
|
|
11091
|
+
_cw('o.unmount error: ' + e.message);
|
|
11092
|
+
}
|
|
11015
11093
|
});
|
|
11016
11094
|
}
|
|
11017
11095
|
}
|
|
@@ -11030,24 +11108,25 @@
|
|
|
11030
11108
|
}
|
|
11031
11109
|
|
|
11032
11110
|
// Slot declarations: auto-generate setX/getX pairs
|
|
11111
|
+
// The target element is cached at creation time to avoid repeated
|
|
11112
|
+
// querySelector calls on every get/set invocation.
|
|
11033
11113
|
if (opts.slots) {
|
|
11034
11114
|
for (var sk in opts.slots) {
|
|
11035
11115
|
if (_hop.call(opts.slots, sk)) {
|
|
11036
11116
|
(function (name, selector) {
|
|
11117
|
+
var target = el.querySelector(selector);
|
|
11037
11118
|
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
11038
11119
|
el.bw['set' + cap] = function (value) {
|
|
11039
|
-
|
|
11040
|
-
if (!t) return;
|
|
11120
|
+
if (!target) return;
|
|
11041
11121
|
if (value != null && _typeof(value) === 'object' && value.t) {
|
|
11042
|
-
|
|
11043
|
-
|
|
11122
|
+
target.innerHTML = '';
|
|
11123
|
+
target.appendChild(bw.createDOM(value));
|
|
11044
11124
|
} else {
|
|
11045
|
-
|
|
11125
|
+
target.textContent = value != null ? String(value) : '';
|
|
11046
11126
|
}
|
|
11047
11127
|
};
|
|
11048
11128
|
el.bw['get' + cap] = function () {
|
|
11049
|
-
|
|
11050
|
-
return t ? t.textContent : '';
|
|
11129
|
+
return target ? target.textContent : '';
|
|
11051
11130
|
};
|
|
11052
11131
|
})(sk, opts.slots[sk]);
|
|
11053
11132
|
}
|
|
@@ -11088,7 +11167,7 @@
|
|
|
11088
11167
|
}
|
|
11089
11168
|
|
|
11090
11169
|
// Get target element (use cache-backed lookup)
|
|
11091
|
-
var targetEl = bw.
|
|
11170
|
+
var targetEl = bw.el(target);
|
|
11092
11171
|
if (!targetEl) {
|
|
11093
11172
|
_ce('bw.DOM: Target element not found:', target);
|
|
11094
11173
|
return null;
|
|
@@ -11191,7 +11270,8 @@
|
|
|
11191
11270
|
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
11192
11271
|
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
11193
11272
|
uuidEls.forEach(function (uel) {
|
|
11194
|
-
var
|
|
11273
|
+
var uc = typeof uel.className === 'string' ? uel.className : uel.getAttribute('class') || '';
|
|
11274
|
+
var m = uc && uc.match(_UUID_RE);
|
|
11195
11275
|
if (m) delete bw._nodeMap[m[0]];
|
|
11196
11276
|
});
|
|
11197
11277
|
|
|
@@ -11279,9 +11359,13 @@
|
|
|
11279
11359
|
* bw.update(el); // re-renders, emits bw:statechange
|
|
11280
11360
|
*/
|
|
11281
11361
|
bw.update = function (target) {
|
|
11282
|
-
var el = bw.
|
|
11362
|
+
var el = bw.el(target);
|
|
11283
11363
|
if (el && el._bw_render) {
|
|
11284
|
-
|
|
11364
|
+
try {
|
|
11365
|
+
el._bw_render(el, el._bw_state || {});
|
|
11366
|
+
} catch (e) {
|
|
11367
|
+
_cw('o.render error: ' + e.message);
|
|
11368
|
+
}
|
|
11285
11369
|
bw.emit(el, 'statechange', el._bw_state);
|
|
11286
11370
|
}
|
|
11287
11371
|
return el || null;
|
|
@@ -11308,7 +11392,7 @@
|
|
|
11308
11392
|
* bw.patch('info', { t: 'em', c: 'new' }); // replace children with TACO
|
|
11309
11393
|
*/
|
|
11310
11394
|
bw.patch = function (id, content, attr) {
|
|
11311
|
-
var el = bw.
|
|
11395
|
+
var el = bw.el(id);
|
|
11312
11396
|
if (!el) return null;
|
|
11313
11397
|
if (attr) {
|
|
11314
11398
|
// Patch an attribute
|
|
@@ -11379,7 +11463,7 @@
|
|
|
11379
11463
|
* // Dispatches CustomEvent 'bw:statechange' on the element
|
|
11380
11464
|
*/
|
|
11381
11465
|
bw.emit = function (target, eventName, detail) {
|
|
11382
|
-
var el = bw.
|
|
11466
|
+
var el = bw.el(target);
|
|
11383
11467
|
if (el) {
|
|
11384
11468
|
el.dispatchEvent(new CustomEvent('bw:' + eventName, {
|
|
11385
11469
|
bubbles: true,
|
|
@@ -11408,7 +11492,7 @@
|
|
|
11408
11492
|
* });
|
|
11409
11493
|
*/
|
|
11410
11494
|
bw.on = function (target, eventName, handler) {
|
|
11411
|
-
var el = bw.
|
|
11495
|
+
var el = bw.el(target);
|
|
11412
11496
|
if (el) {
|
|
11413
11497
|
el.addEventListener('bw:' + eventName, function (e) {
|
|
11414
11498
|
handler(e.detail, e);
|
|
@@ -11435,23 +11519,46 @@
|
|
|
11435
11519
|
*
|
|
11436
11520
|
* @param {string} topic - Topic name (plain string, no prefix)
|
|
11437
11521
|
* @param {*} [detail] - Data to pass to subscribers
|
|
11438
|
-
* @returns {number} Count of successfully called subscribers
|
|
11522
|
+
* @returns {number} Count of successfully called subscribers (including wildcard matches)
|
|
11439
11523
|
* @category Pub/Sub
|
|
11440
11524
|
* @see bw.sub
|
|
11441
11525
|
* @example
|
|
11442
11526
|
* bw.pub('score:updated', { player: 'X', score: 10 });
|
|
11527
|
+
* // Wildcard subscribers matching 'score:*' will also fire
|
|
11443
11528
|
*/
|
|
11444
11529
|
bw.pub = function (topic, detail) {
|
|
11445
|
-
var subs = bw._topics[topic];
|
|
11446
|
-
if (!subs || subs.length === 0) return 0;
|
|
11447
|
-
var snapshot = subs.slice(); // safe against unsub during iteration
|
|
11448
11530
|
var called = 0;
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11531
|
+
// Exact-match subscribers
|
|
11532
|
+
var subs = bw._topics[topic];
|
|
11533
|
+
if (subs && subs.length > 0) {
|
|
11534
|
+
var snapshot = subs.slice();
|
|
11535
|
+
for (var i = 0; i < snapshot.length; i++) {
|
|
11536
|
+
try {
|
|
11537
|
+
snapshot[i].handler(detail, topic);
|
|
11538
|
+
called++;
|
|
11539
|
+
} catch (err) {
|
|
11540
|
+
_cw('bw.pub: subscriber error on topic "' + topic + '":', err);
|
|
11541
|
+
}
|
|
11542
|
+
}
|
|
11543
|
+
}
|
|
11544
|
+
// Wildcard subscribers -- patterns ending with '*'
|
|
11545
|
+
var keys = Object.keys(bw._topics);
|
|
11546
|
+
for (var k = 0; k < keys.length; k++) {
|
|
11547
|
+
var pat = keys[k];
|
|
11548
|
+
if (pat.charAt(pat.length - 1) !== '*') continue;
|
|
11549
|
+
var prefix = pat.slice(0, -1); // strip trailing '*'
|
|
11550
|
+
if (topic.length >= prefix.length && topic.substring(0, prefix.length) === prefix && topic !== pat) {
|
|
11551
|
+
var wsubs = bw._topics[pat];
|
|
11552
|
+
if (!wsubs) continue;
|
|
11553
|
+
var wsnap = wsubs.slice();
|
|
11554
|
+
for (var w = 0; w < wsnap.length; w++) {
|
|
11555
|
+
try {
|
|
11556
|
+
wsnap[w].handler(detail, topic);
|
|
11557
|
+
called++;
|
|
11558
|
+
} catch (err) {
|
|
11559
|
+
_cw('bw.pub: wildcard subscriber error on "' + pat + '" for topic "' + topic + '":', err);
|
|
11560
|
+
}
|
|
11561
|
+
}
|
|
11455
11562
|
}
|
|
11456
11563
|
}
|
|
11457
11564
|
return called;
|
|
@@ -11460,12 +11567,17 @@
|
|
|
11460
11567
|
/**
|
|
11461
11568
|
* Subscribe to a topic. Returns an unsub() function.
|
|
11462
11569
|
*
|
|
11463
|
-
*
|
|
11570
|
+
* Supports wildcard patterns: a topic ending in `*` matches any published
|
|
11571
|
+
* topic that starts with the prefix before the `*`. For example,
|
|
11572
|
+
* `'agui:*'` matches `'agui:ready'`, `'agui:error'`, etc. The handler
|
|
11573
|
+
* receives `(detail, topic)` so it can distinguish which topic fired.
|
|
11574
|
+
*
|
|
11575
|
+
* Optional third argument ties the subscription to a DOM element's lifecycle --
|
|
11464
11576
|
* when `bw.cleanup()` is called on that element, the subscription is automatically
|
|
11465
11577
|
* removed, preventing memory leaks.
|
|
11466
11578
|
*
|
|
11467
|
-
* @param {string} topic - Topic name
|
|
11468
|
-
* @param {Function} handler - Called with (detail) on each publish
|
|
11579
|
+
* @param {string} topic - Topic name, or wildcard pattern ending in '*'
|
|
11580
|
+
* @param {Function} handler - Called with (detail, topic) on each publish
|
|
11469
11581
|
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
11470
11582
|
* @returns {Function} Call to unsubscribe
|
|
11471
11583
|
* @category Pub/Sub
|
|
@@ -11476,6 +11588,11 @@
|
|
|
11476
11588
|
* console.log(detail.player, 'scored', detail.score);
|
|
11477
11589
|
* });
|
|
11478
11590
|
* // Later: unsub() to stop listening
|
|
11591
|
+
*
|
|
11592
|
+
* // Wildcard: listen to all 'agui:' topics
|
|
11593
|
+
* bw.sub('agui:*', function(detail, topic) {
|
|
11594
|
+
* console.log('Got', topic, detail);
|
|
11595
|
+
* });
|
|
11479
11596
|
*/
|
|
11480
11597
|
bw.sub = function (topic, handler, el) {
|
|
11481
11598
|
var id = ++bw._subIdCounter;
|
|
@@ -11532,6 +11649,37 @@
|
|
|
11532
11649
|
return removed;
|
|
11533
11650
|
};
|
|
11534
11651
|
|
|
11652
|
+
/**
|
|
11653
|
+
* Subscribe to a topic for a single event only. The subscription is
|
|
11654
|
+
* automatically removed after the first publish. Equivalent to manually
|
|
11655
|
+
* calling unsub() inside a bw.sub() handler, but avoids the common bug
|
|
11656
|
+
* of forgetting to unsubscribe.
|
|
11657
|
+
*
|
|
11658
|
+
* @param {string} topic - Topic name
|
|
11659
|
+
* @param {Function} handler - Called once with (detail) on the next publish
|
|
11660
|
+
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
11661
|
+
* @returns {Function} Call to cancel the subscription before it fires
|
|
11662
|
+
* @category Pub/Sub
|
|
11663
|
+
* @see bw.sub
|
|
11664
|
+
* @see bw.pub
|
|
11665
|
+
* @example
|
|
11666
|
+
* bw.once('data:loaded', function(detail) {
|
|
11667
|
+
* console.log('Received:', detail);
|
|
11668
|
+
* // No need to unsubscribe -- already done automatically
|
|
11669
|
+
* });
|
|
11670
|
+
*
|
|
11671
|
+
* // Cancel before it fires:
|
|
11672
|
+
* var cancel = bw.once('timeout', handler);
|
|
11673
|
+
* cancel(); // handler will never be called
|
|
11674
|
+
*/
|
|
11675
|
+
bw.once = function (topic, handler, el) {
|
|
11676
|
+
var unsub = bw.sub(topic, function (detail) {
|
|
11677
|
+
unsub();
|
|
11678
|
+
handler(detail);
|
|
11679
|
+
}, el);
|
|
11680
|
+
return unsub;
|
|
11681
|
+
};
|
|
11682
|
+
|
|
11535
11683
|
// ===================================================================================
|
|
11536
11684
|
// Function Registry (revived from v1 for string dispatch contexts)
|
|
11537
11685
|
// ===================================================================================
|
|
@@ -11775,7 +11923,7 @@
|
|
|
11775
11923
|
* };
|
|
11776
11924
|
*/
|
|
11777
11925
|
bw.message = function (target, action, data) {
|
|
11778
|
-
var el = bw.
|
|
11926
|
+
var el = bw.el(target);
|
|
11779
11927
|
if (!el) el = bw.$('.' + target)[0];
|
|
11780
11928
|
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
11781
11929
|
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
@@ -11785,6 +11933,217 @@
|
|
|
11785
11933
|
return true;
|
|
11786
11934
|
};
|
|
11787
11935
|
|
|
11936
|
+
/**
|
|
11937
|
+
* Collect form data from all input, select, and textarea elements within a
|
|
11938
|
+
* container. Each element's `name` attribute (or `id` if no name) becomes a
|
|
11939
|
+
* key in the returned object. This provides a lightweight alternative to the
|
|
11940
|
+
* browser FormData API that returns a plain object suitable for JSON
|
|
11941
|
+
* serialization or bw.pub().
|
|
11942
|
+
*
|
|
11943
|
+
* Handles all standard HTML form controls:
|
|
11944
|
+
* - text/number/email/etc inputs: string value
|
|
11945
|
+
* - checkboxes: boolean (true/false)
|
|
11946
|
+
* - radio buttons: string value of the checked radio (unchecked groups omitted)
|
|
11947
|
+
* - multi-select: array of selected option values
|
|
11948
|
+
* - textarea: string value
|
|
11949
|
+
*
|
|
11950
|
+
* Elements without both `name` and `id` attributes are silently skipped.
|
|
11951
|
+
*
|
|
11952
|
+
* @param {string|Element} target - CSS selector, UUID string, or DOM element
|
|
11953
|
+
* @returns {Object} Plain object mapping field names to values
|
|
11954
|
+
* @category Component
|
|
11955
|
+
* @see bw.makeForm
|
|
11956
|
+
* @see bw.makeInput
|
|
11957
|
+
* @example
|
|
11958
|
+
* // Given a form with name="email" input and name="agree" checkbox:
|
|
11959
|
+
* var data = bw.formData('#signup-form');
|
|
11960
|
+
* // => { email: 'user@example.com', agree: true }
|
|
11961
|
+
*
|
|
11962
|
+
* // Collect and publish in one step:
|
|
11963
|
+
* bw.pub('form:submit', bw.formData('#my-form'));
|
|
11964
|
+
*
|
|
11965
|
+
* // Works with any container, not just <form>:
|
|
11966
|
+
* bw.pub('settings:changed', bw.formData('.settings-panel'));
|
|
11967
|
+
*/
|
|
11968
|
+
bw.formData = function (target) {
|
|
11969
|
+
var el = bw.el(target);
|
|
11970
|
+
if (!el) return {};
|
|
11971
|
+
var result = {};
|
|
11972
|
+
var inputs = el.querySelectorAll('input, select, textarea');
|
|
11973
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
11974
|
+
var inp = inputs[i];
|
|
11975
|
+
var key = inp.name || inp.id;
|
|
11976
|
+
if (!key) continue;
|
|
11977
|
+
if (inp.type === 'checkbox') {
|
|
11978
|
+
result[key] = inp.checked;
|
|
11979
|
+
} else if (inp.type === 'radio') {
|
|
11980
|
+
if (inp.checked) result[key] = inp.value;
|
|
11981
|
+
} else if (inp.tagName === 'SELECT' && inp.multiple) {
|
|
11982
|
+
result[key] = [];
|
|
11983
|
+
for (var j = 0; j < inp.options.length; j++) {
|
|
11984
|
+
if (inp.options[j].selected) result[key].push(inp.options[j].value);
|
|
11985
|
+
}
|
|
11986
|
+
} else {
|
|
11987
|
+
result[key] = inp.value;
|
|
11988
|
+
}
|
|
11989
|
+
}
|
|
11990
|
+
return result;
|
|
11991
|
+
};
|
|
11992
|
+
|
|
11993
|
+
// ===================================================================================
|
|
11994
|
+
// bw.jsonPatch() — RFC 6902 JSON Patch on plain objects
|
|
11995
|
+
// ===================================================================================
|
|
11996
|
+
|
|
11997
|
+
/**
|
|
11998
|
+
* Apply RFC 6902 JSON Patch operations to a plain object.
|
|
11999
|
+
*
|
|
12000
|
+
* Supported operations: add, remove, replace, move, copy, test.
|
|
12001
|
+
* Paths use JSON Pointer (RFC 6901) notation: `/foo/bar/0`.
|
|
12002
|
+
* Mutates the target object in place and returns it.
|
|
12003
|
+
*
|
|
12004
|
+
* @param {Object} obj - Target object to patch
|
|
12005
|
+
* @param {Array<Object>} ops - Array of patch operations
|
|
12006
|
+
* @param {string} ops[].op - Operation: 'add', 'remove', 'replace', 'move', 'copy', 'test'
|
|
12007
|
+
* @param {string} ops[].path - JSON Pointer path (e.g. '/a/b/0')
|
|
12008
|
+
* @param {*} [ops[].value] - Value for add/replace/test
|
|
12009
|
+
* @param {string} [ops[].from] - Source path for move/copy
|
|
12010
|
+
* @returns {Object} The patched object (same reference)
|
|
12011
|
+
* @throws {Error} On invalid op, missing path, test failure, or path not found for remove
|
|
12012
|
+
* @category Data Utilities
|
|
12013
|
+
* @see bw.patch
|
|
12014
|
+
* @example
|
|
12015
|
+
* var obj = { a: 1, b: { c: 2 } };
|
|
12016
|
+
* bw.jsonPatch(obj, [
|
|
12017
|
+
* { op: 'replace', path: '/a', value: 10 },
|
|
12018
|
+
* { op: 'add', path: '/b/d', value: 3 },
|
|
12019
|
+
* { op: 'remove', path: '/b/c' }
|
|
12020
|
+
* ]);
|
|
12021
|
+
* // obj => { a: 10, b: { d: 3 } }
|
|
12022
|
+
*/
|
|
12023
|
+
bw.jsonPatch = function (obj, ops) {
|
|
12024
|
+
if (!_isA(ops)) return obj;
|
|
12025
|
+
|
|
12026
|
+
// Parse JSON Pointer path to array of keys
|
|
12027
|
+
function parsePath(path) {
|
|
12028
|
+
if (path === '') return [];
|
|
12029
|
+
if (path.charAt(0) !== '/') throw new Error('Invalid JSON Pointer: ' + path);
|
|
12030
|
+
return path.slice(1).split('/').map(function (s) {
|
|
12031
|
+
return s.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
12032
|
+
});
|
|
12033
|
+
}
|
|
12034
|
+
|
|
12035
|
+
// Walk to parent of final key; return { parent, key }
|
|
12036
|
+
function resolve(root, keys) {
|
|
12037
|
+
var parent = root;
|
|
12038
|
+
for (var i = 0; i < keys.length - 1; i++) {
|
|
12039
|
+
var k = _isA(parent) ? parseInt(keys[i], 10) : keys[i];
|
|
12040
|
+
if (parent[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
12041
|
+
parent = parent[k];
|
|
12042
|
+
}
|
|
12043
|
+
return {
|
|
12044
|
+
parent: parent,
|
|
12045
|
+
key: _isA(parent) ? parseInt(keys[keys.length - 1], 10) : keys[keys.length - 1]
|
|
12046
|
+
};
|
|
12047
|
+
}
|
|
12048
|
+
|
|
12049
|
+
// Get value at path
|
|
12050
|
+
function getVal(root, keys) {
|
|
12051
|
+
var cur = root;
|
|
12052
|
+
for (var i = 0; i < keys.length; i++) {
|
|
12053
|
+
var k = _isA(cur) ? parseInt(keys[i], 10) : keys[i];
|
|
12054
|
+
if (cur[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
12055
|
+
cur = cur[k];
|
|
12056
|
+
}
|
|
12057
|
+
return cur;
|
|
12058
|
+
}
|
|
12059
|
+
for (var i = 0; i < ops.length; i++) {
|
|
12060
|
+
var op = ops[i];
|
|
12061
|
+
if (!op.op || !_is(op.path, 'string')) throw new Error('Invalid patch operation at index ' + i);
|
|
12062
|
+
var keys = parsePath(op.path);
|
|
12063
|
+
var r, val, fromKeys, fr, tr, cr;
|
|
12064
|
+
switch (op.op) {
|
|
12065
|
+
case 'add':
|
|
12066
|
+
{
|
|
12067
|
+
if (keys.length === 0) throw new Error('Cannot add to root');
|
|
12068
|
+
r = resolve(obj, keys);
|
|
12069
|
+
if (_isA(r.parent) && r.key <= r.parent.length) {
|
|
12070
|
+
r.parent.splice(r.key, 0, op.value);
|
|
12071
|
+
} else {
|
|
12072
|
+
r.parent[r.key] = op.value;
|
|
12073
|
+
}
|
|
12074
|
+
break;
|
|
12075
|
+
}
|
|
12076
|
+
case 'remove':
|
|
12077
|
+
{
|
|
12078
|
+
if (keys.length === 0) throw new Error('Cannot remove root');
|
|
12079
|
+
r = resolve(obj, keys);
|
|
12080
|
+
if (_isA(r.parent)) {
|
|
12081
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
12082
|
+
r.parent.splice(r.key, 1);
|
|
12083
|
+
} else {
|
|
12084
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
12085
|
+
delete r.parent[r.key];
|
|
12086
|
+
}
|
|
12087
|
+
break;
|
|
12088
|
+
}
|
|
12089
|
+
case 'replace':
|
|
12090
|
+
{
|
|
12091
|
+
if (keys.length === 0) throw new Error('Cannot replace root');
|
|
12092
|
+
r = resolve(obj, keys);
|
|
12093
|
+
if (_isA(r.parent)) {
|
|
12094
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
12095
|
+
} else {
|
|
12096
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
12097
|
+
}
|
|
12098
|
+
r.parent[r.key] = op.value;
|
|
12099
|
+
break;
|
|
12100
|
+
}
|
|
12101
|
+
case 'move':
|
|
12102
|
+
{
|
|
12103
|
+
if (!_is(op.from, 'string')) throw new Error('move requires "from"');
|
|
12104
|
+
fromKeys = parsePath(op.from);
|
|
12105
|
+
val = getVal(obj, fromKeys);
|
|
12106
|
+
fr = resolve(obj, fromKeys);
|
|
12107
|
+
if (_isA(fr.parent)) {
|
|
12108
|
+
fr.parent.splice(fr.key, 1);
|
|
12109
|
+
} else {
|
|
12110
|
+
delete fr.parent[fr.key];
|
|
12111
|
+
}
|
|
12112
|
+
tr = resolve(obj, keys);
|
|
12113
|
+
if (_isA(tr.parent) && tr.key <= tr.parent.length) {
|
|
12114
|
+
tr.parent.splice(tr.key, 0, val);
|
|
12115
|
+
} else {
|
|
12116
|
+
tr.parent[tr.key] = val;
|
|
12117
|
+
}
|
|
12118
|
+
break;
|
|
12119
|
+
}
|
|
12120
|
+
case 'copy':
|
|
12121
|
+
{
|
|
12122
|
+
if (!_is(op.from, 'string')) throw new Error('copy requires "from"');
|
|
12123
|
+
val = getVal(obj, parsePath(op.from));
|
|
12124
|
+
cr = resolve(obj, keys);
|
|
12125
|
+
if (_isA(cr.parent) && cr.key <= cr.parent.length) {
|
|
12126
|
+
cr.parent.splice(cr.key, 0, val);
|
|
12127
|
+
} else {
|
|
12128
|
+
cr.parent[cr.key] = val;
|
|
12129
|
+
}
|
|
12130
|
+
break;
|
|
12131
|
+
}
|
|
12132
|
+
case 'test':
|
|
12133
|
+
{
|
|
12134
|
+
var actual = getVal(obj, keys);
|
|
12135
|
+
if (JSON.stringify(actual) !== JSON.stringify(op.value)) {
|
|
12136
|
+
throw new Error('Test failed: ' + op.path + ' expected ' + JSON.stringify(op.value) + ' got ' + JSON.stringify(actual));
|
|
12137
|
+
}
|
|
12138
|
+
break;
|
|
12139
|
+
}
|
|
12140
|
+
default:
|
|
12141
|
+
throw new Error('Unknown op: ' + op.op);
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12144
|
+
return obj;
|
|
12145
|
+
};
|
|
12146
|
+
|
|
11788
12147
|
// ===================================================================================
|
|
11789
12148
|
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
11790
12149
|
// ===================================================================================
|
|
@@ -11917,7 +12276,7 @@
|
|
|
11917
12276
|
var type = msg.type;
|
|
11918
12277
|
var target = msg.target;
|
|
11919
12278
|
if (type === 'replace') {
|
|
11920
|
-
var el = bw.
|
|
12279
|
+
var el = bw.el(target);
|
|
11921
12280
|
if (!el) return false;
|
|
11922
12281
|
bw.DOM(el, msg.node);
|
|
11923
12282
|
return true;
|
|
@@ -11925,13 +12284,13 @@
|
|
|
11925
12284
|
var patched = bw.patch(target, msg.content, msg.attr);
|
|
11926
12285
|
return patched !== null;
|
|
11927
12286
|
} else if (type === 'append') {
|
|
11928
|
-
var parent = bw.
|
|
12287
|
+
var parent = bw.el(target);
|
|
11929
12288
|
if (!parent) return false;
|
|
11930
12289
|
var child = bw.createDOM(msg.node);
|
|
11931
12290
|
parent.appendChild(child);
|
|
11932
12291
|
return true;
|
|
11933
12292
|
} else if (type === 'remove') {
|
|
11934
|
-
var toRemove = bw.
|
|
12293
|
+
var toRemove = bw.el(target);
|
|
11935
12294
|
if (!toRemove) return false;
|
|
11936
12295
|
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
11937
12296
|
toRemove.remove();
|
|
@@ -11984,33 +12343,99 @@
|
|
|
11984
12343
|
};
|
|
11985
12344
|
|
|
11986
12345
|
// ===================================================================================
|
|
11987
|
-
// bw.inspect() —
|
|
12346
|
+
// bw.inspect() — DOM introspection with bitwrench metadata
|
|
11988
12347
|
// ===================================================================================
|
|
11989
12348
|
|
|
11990
12349
|
/**
|
|
11991
|
-
* Inspect a DOM element
|
|
11992
|
-
*
|
|
11993
|
-
*
|
|
11994
|
-
*
|
|
11995
|
-
*
|
|
12350
|
+
* Inspect a DOM element and its subtree, returning a plain-object
|
|
12351
|
+
* representation with bitwrench metadata at each node. Useful for debugging,
|
|
12352
|
+
* devtools, MCP/AG-UI tool discovery, and automated testing.
|
|
12353
|
+
*
|
|
12354
|
+
* Each node in the returned tree includes:
|
|
12355
|
+
* - `tag` -- lowercase tag name (or '#text' for text nodes)
|
|
12356
|
+
* - `id` -- element id (if set)
|
|
12357
|
+
* - `uuid` -- bitwrench UUID class (if lifecycle-managed)
|
|
12358
|
+
* - `type` -- component type from o.type (if set, e.g. 'card', 'tabs')
|
|
12359
|
+
* - `classes` -- first 5 CSS classes (string, space-separated)
|
|
12360
|
+
* - `handles` -- array of el.bw method names (if any)
|
|
12361
|
+
* - `state` -- copy of _bw_state (if any)
|
|
12362
|
+
* - `hasRender` -- true if _bw_render is set
|
|
12363
|
+
* - `hasSubs` -- true if element has pub/sub subscriptions
|
|
12364
|
+
* - `refs` -- copy of _bw_refs keys (if any)
|
|
12365
|
+
* - `children` -- array of child node trees (up to depth limit, max 50 per level)
|
|
12366
|
+
*
|
|
12367
|
+
* @param {string|Element} target - CSS selector, UUID, or DOM element
|
|
12368
|
+
* @param {number} [depth=3] - Maximum recursion depth (0 = target only, no children)
|
|
12369
|
+
* @returns {Object|null} Plain object tree, or null if element not found
|
|
11996
12370
|
* @category Component
|
|
11997
12371
|
* @example
|
|
11998
|
-
*
|
|
11999
|
-
* bw.inspect(
|
|
12372
|
+
* // Get full tree from #app, 3 levels deep (default):
|
|
12373
|
+
* var info = bw.inspect('#app');
|
|
12374
|
+
*
|
|
12375
|
+
* // Shallow inspection (just the element, no children):
|
|
12376
|
+
* var info = bw.inspect('#my-carousel', 0);
|
|
12377
|
+
* console.log(info.handles); // ['next', 'prev', 'goToSlide']
|
|
12378
|
+
* console.log(info.type); // 'carousel'
|
|
12379
|
+
*
|
|
12380
|
+
* // Deep inspection for debugging:
|
|
12381
|
+
* console.log(JSON.stringify(bw.inspect('#app', 5), null, 2));
|
|
12000
12382
|
*/
|
|
12001
|
-
bw.inspect = function (target) {
|
|
12002
|
-
var el =
|
|
12003
|
-
if (!el)
|
|
12004
|
-
|
|
12005
|
-
|
|
12383
|
+
bw.inspect = function (target, depth) {
|
|
12384
|
+
var el = bw.el(target);
|
|
12385
|
+
if (!el && _is(target, 'string')) el = bw.$(target)[0];
|
|
12386
|
+
if (!el) return null;
|
|
12387
|
+
if (depth === undefined || depth === null) depth = 3;
|
|
12388
|
+
function walk(node, d) {
|
|
12389
|
+
if (!node) return null;
|
|
12390
|
+
// Skip non-element nodes (text, comment, etc.)
|
|
12391
|
+
if (node.nodeType !== 1) return null;
|
|
12392
|
+
var info = {
|
|
12393
|
+
tag: node.tagName ? node.tagName.toLowerCase() : '#text'
|
|
12394
|
+
};
|
|
12395
|
+
|
|
12396
|
+
// Identity
|
|
12397
|
+
if (node.id) info.id = node.id;
|
|
12398
|
+
var uuid = bw.getUUID(node);
|
|
12399
|
+
if (uuid) info.uuid = uuid;
|
|
12400
|
+
if (node._bw_type) info.type = node._bw_type;
|
|
12401
|
+
|
|
12402
|
+
// CSS classes (first 5 for readability)
|
|
12403
|
+
if (node.className && typeof node.className === 'string') {
|
|
12404
|
+
info.classes = node.className.split(' ').slice(0, 5).join(' ');
|
|
12405
|
+
}
|
|
12406
|
+
|
|
12407
|
+
// Bitwrench handle methods
|
|
12408
|
+
if (node.bw) {
|
|
12409
|
+
var handles = _keys(node.bw);
|
|
12410
|
+
if (handles.length > 0) info.handles = handles;
|
|
12411
|
+
}
|
|
12412
|
+
|
|
12413
|
+
// State
|
|
12414
|
+
if (node._bw_state) info.state = node._bw_state;
|
|
12415
|
+
if (node._bw_render) info.hasRender = true;
|
|
12416
|
+
if (node._bw_subs && node._bw_subs.length > 0) info.hasSubs = true;
|
|
12417
|
+
|
|
12418
|
+
// Refs
|
|
12419
|
+
if (node._bw_refs) info.refs = _keys(node._bw_refs);
|
|
12420
|
+
|
|
12421
|
+
// Children (recurse up to depth limit, max 50 children per level)
|
|
12422
|
+
if (d < depth && node.children && node.children.length > 0) {
|
|
12423
|
+
info.children = [];
|
|
12424
|
+
var max = Math.min(node.children.length, 50);
|
|
12425
|
+
for (var i = 0; i < max; i++) {
|
|
12426
|
+
var child = walk(node.children[i], d + 1);
|
|
12427
|
+
if (child) info.children.push(child);
|
|
12428
|
+
}
|
|
12429
|
+
if (node.children.length > 50) {
|
|
12430
|
+
info.children.push({
|
|
12431
|
+
tag: '...',
|
|
12432
|
+
count: node.children.length - 50
|
|
12433
|
+
});
|
|
12434
|
+
}
|
|
12435
|
+
}
|
|
12436
|
+
return info;
|
|
12006
12437
|
}
|
|
12007
|
-
|
|
12008
|
-
_cl('State:', el._bw_state || '(none)');
|
|
12009
|
-
_cl('Handle:', el.bw ? _keys(el.bw) : '(none)');
|
|
12010
|
-
_cl('Classes:', el.className);
|
|
12011
|
-
_cl('Refs:', el._bw_refs || '(none)');
|
|
12012
|
-
console.groupEnd();
|
|
12013
|
-
return el;
|
|
12438
|
+
return walk(el, 0);
|
|
12014
12439
|
};
|
|
12015
12440
|
bw.compile = function () {
|
|
12016
12441
|
throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.');
|
|
@@ -12249,34 +12674,45 @@
|
|
|
12249
12674
|
* so you can use `.map()`, `.filter()`, etc. directly. Accepts CSS selectors,
|
|
12250
12675
|
* single elements, NodeLists, or arrays.
|
|
12251
12676
|
*
|
|
12677
|
+
* With an optional second argument, applies content or a function to
|
|
12678
|
+
* every matched element (same apply rules as `bw.el()`):
|
|
12679
|
+
* - string/number: sets `el.textContent`
|
|
12680
|
+
* - function: calls `apply(el)` for each element
|
|
12681
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
12682
|
+
* - array: clears children, appends each item
|
|
12683
|
+
*
|
|
12252
12684
|
* @param {string|Element|Array} selector - CSS selector, element, or array
|
|
12685
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
12253
12686
|
* @returns {Array} Array of DOM elements
|
|
12254
12687
|
* @category DOM Selection
|
|
12688
|
+
* @see bw.el
|
|
12255
12689
|
* @example
|
|
12256
|
-
* bw.$('.card')
|
|
12257
|
-
* bw.$(
|
|
12258
|
-
* bw.$('.card'
|
|
12690
|
+
* bw.$('.card') // => [div.card, div.card, ...]
|
|
12691
|
+
* bw.$('.status', 'Online') // set text on all .status elements
|
|
12692
|
+
* bw.$('.card', function(el) { // apply function to each
|
|
12693
|
+
* el.style.opacity = '0.5';
|
|
12694
|
+
* })
|
|
12259
12695
|
*/
|
|
12260
12696
|
if (bw._isBrowser) {
|
|
12261
|
-
bw.$ = function (selector) {
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
if (_isA(selector))
|
|
12266
|
-
|
|
12267
|
-
|
|
12268
|
-
|
|
12269
|
-
|
|
12270
|
-
|
|
12271
|
-
if (
|
|
12272
|
-
|
|
12697
|
+
bw.$ = function (selector, apply) {
|
|
12698
|
+
var els;
|
|
12699
|
+
if (!selector) {
|
|
12700
|
+
els = [];
|
|
12701
|
+
} else if (_isA(selector)) {
|
|
12702
|
+
els = selector;
|
|
12703
|
+
} else if (selector.nodeType) {
|
|
12704
|
+
els = [selector];
|
|
12705
|
+
} else if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
12706
|
+
els = Array.from(selector);
|
|
12707
|
+
} else if (_is(selector, 'string')) {
|
|
12708
|
+
els = Array.from(document.querySelectorAll(selector));
|
|
12709
|
+
} else {
|
|
12710
|
+
els = [];
|
|
12273
12711
|
}
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
if (_is(selector, 'string')) {
|
|
12277
|
-
return Array.from(document.querySelectorAll(selector));
|
|
12712
|
+
if (apply !== undefined) {
|
|
12713
|
+
for (var i = 0; i < els.length; i++) _applyTo(els[i], apply);
|
|
12278
12714
|
}
|
|
12279
|
-
return
|
|
12715
|
+
return els;
|
|
12280
12716
|
};
|
|
12281
12717
|
|
|
12282
12718
|
// Convenience single element selector
|
|
@@ -12495,41 +12931,47 @@
|
|
|
12495
12931
|
};
|
|
12496
12932
|
|
|
12497
12933
|
/**
|
|
12498
|
-
* Toggle between primary and alternate palettes.
|
|
12934
|
+
* Toggle between primary and alternate theme palettes.
|
|
12499
12935
|
*
|
|
12500
|
-
* Adds/removes the `bw_theme_alt` class on the scoping element.
|
|
12936
|
+
* Adds/removes the `bw_theme_alt` class on the scoping element(s).
|
|
12501
12937
|
* Without a scope, toggles on `<html>` (global).
|
|
12502
|
-
* With a scope, toggles on
|
|
12938
|
+
* With a scope, toggles on ALL matching elements.
|
|
12503
12939
|
*
|
|
12504
|
-
* @param {string} [scope] -
|
|
12505
|
-
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
12940
|
+
* @param {string|Element} [scope] - Selector or element. Omit for global.
|
|
12941
|
+
* @returns {string} Active mode after toggle: 'primary' or 'alternate' (based on first element)
|
|
12506
12942
|
* @category CSS & Styling
|
|
12507
12943
|
* @see bw.applyStyles
|
|
12508
12944
|
* @see bw.clearStyles
|
|
12509
12945
|
* @example
|
|
12510
|
-
* bw.
|
|
12511
|
-
* bw.
|
|
12946
|
+
* bw.toggleThemeMode(); // global toggle on <html>
|
|
12947
|
+
* bw.toggleThemeMode('#my-dashboard'); // scoped toggle
|
|
12948
|
+
* bw.toggleThemeMode('.panel'); // toggle on ALL .panel elements
|
|
12512
12949
|
*/
|
|
12513
|
-
bw.
|
|
12950
|
+
bw.toggleThemeMode = function (scope) {
|
|
12514
12951
|
if (!bw._isBrowser) return 'primary';
|
|
12515
|
-
var
|
|
12952
|
+
var els;
|
|
12516
12953
|
if (scope) {
|
|
12517
|
-
|
|
12518
|
-
target = els[0];
|
|
12519
|
-
} else {
|
|
12520
|
-
target = document.documentElement;
|
|
12521
|
-
}
|
|
12522
|
-
if (!target) return 'primary';
|
|
12523
|
-
var hasAlt = target.classList.contains('bw_theme_alt');
|
|
12524
|
-
if (hasAlt) {
|
|
12525
|
-
target.classList.remove('bw_theme_alt');
|
|
12526
|
-
return 'primary';
|
|
12954
|
+
els = bw.$(scope);
|
|
12527
12955
|
} else {
|
|
12528
|
-
|
|
12529
|
-
|
|
12956
|
+
els = [document.documentElement];
|
|
12957
|
+
}
|
|
12958
|
+
if (!els.length) return 'primary';
|
|
12959
|
+
var mode;
|
|
12960
|
+
for (var i = 0; i < els.length; i++) {
|
|
12961
|
+
var hasAlt = els[i].classList.contains('bw_theme_alt');
|
|
12962
|
+
if (hasAlt) {
|
|
12963
|
+
els[i].classList.remove('bw_theme_alt');
|
|
12964
|
+
} else {
|
|
12965
|
+
els[i].classList.add('bw_theme_alt');
|
|
12966
|
+
}
|
|
12967
|
+
if (i === 0) mode = hasAlt ? 'primary' : 'alternate';
|
|
12530
12968
|
}
|
|
12969
|
+
return mode;
|
|
12531
12970
|
};
|
|
12532
12971
|
|
|
12972
|
+
// Alias — kept for one release cycle. Use bw.toggleThemeMode() instead.
|
|
12973
|
+
bw.toggleStyles = bw.toggleThemeMode;
|
|
12974
|
+
|
|
12533
12975
|
/**
|
|
12534
12976
|
* Remove injected styles for a given scope.
|
|
12535
12977
|
*
|
|
@@ -13629,6 +14071,57 @@
|
|
|
13629
14071
|
}
|
|
13630
14072
|
});
|
|
13631
14073
|
|
|
14074
|
+
/**
|
|
14075
|
+
* Query the BCCL component registry. Returns metadata about registered
|
|
14076
|
+
* component types -- their names and factory function names. Useful for
|
|
14077
|
+
* tooling, introspection, documentation generators, and auto-complete
|
|
14078
|
+
* systems (including MCP/AG-UI tool discovery).
|
|
14079
|
+
*
|
|
14080
|
+
* With no arguments, returns an array of all registered component types.
|
|
14081
|
+
* With a type name, returns metadata for that single type (or null if
|
|
14082
|
+
* the type is not registered).
|
|
14083
|
+
*
|
|
14084
|
+
* @param {string} [type] - Optional component type name to look up
|
|
14085
|
+
* @returns {Array<Object>|Object|null} Array of {type, factory} objects,
|
|
14086
|
+
* a single {type, factory} object, or null if the type is not found
|
|
14087
|
+
* @category Component
|
|
14088
|
+
* @see bw.make
|
|
14089
|
+
* @see bw.BCCL
|
|
14090
|
+
* @example
|
|
14091
|
+
* // List all available component types:
|
|
14092
|
+
* bw.catalog();
|
|
14093
|
+
* // => [{ type: 'card', factory: 'makeCard' },
|
|
14094
|
+
* // { type: 'button', factory: 'makeButton' }, ...]
|
|
14095
|
+
*
|
|
14096
|
+
* // Look up a specific type:
|
|
14097
|
+
* bw.catalog('accordion');
|
|
14098
|
+
* // => { type: 'accordion', factory: 'makeAccordion' }
|
|
14099
|
+
*
|
|
14100
|
+
* // Check if a type exists:
|
|
14101
|
+
* if (bw.catalog('chart')) { ... }
|
|
14102
|
+
*
|
|
14103
|
+
* // Get just the type names:
|
|
14104
|
+
* bw.catalog().map(function(c) { return c.type; });
|
|
14105
|
+
* // => ['card', 'button', 'container', 'row', ...]
|
|
14106
|
+
*/
|
|
14107
|
+
bw.catalog = function (type) {
|
|
14108
|
+
if (type) {
|
|
14109
|
+
var def = bw.BCCL[type];
|
|
14110
|
+
if (!def) return null;
|
|
14111
|
+
return {
|
|
14112
|
+
type: type,
|
|
14113
|
+
factory: def.make.name || 'make' + type.charAt(0).toUpperCase() + type.slice(1)
|
|
14114
|
+
};
|
|
14115
|
+
}
|
|
14116
|
+
return Object.keys(bw.BCCL).map(function (k) {
|
|
14117
|
+
var def = bw.BCCL[k];
|
|
14118
|
+
return {
|
|
14119
|
+
type: k,
|
|
14120
|
+
factory: def.make.name || 'make' + k.charAt(0).toUpperCase() + k.slice(1)
|
|
14121
|
+
};
|
|
14122
|
+
});
|
|
14123
|
+
};
|
|
14124
|
+
|
|
13632
14125
|
// Also attach to global in browsers
|
|
13633
14126
|
if (bw._isBrowser && typeof window !== 'undefined') {
|
|
13634
14127
|
window.bw = bw;
|