bitwrench 2.0.24 → 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 +17 -9
- 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 +661 -174
- 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 +690 -178
- 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 +661 -174
- 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 +661 -174
- 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 +659 -172
- package/dist/bitwrench.cjs.min.js +6 -6
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +6 -6
- package/dist/bitwrench.d.ts +666 -0
- package/dist/bitwrench.es5.js +687 -175
- package/dist/bitwrench.es5.min.js +6 -6
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +659 -172
- package/dist/bitwrench.esm.min.js +5 -5
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +659 -172
- package/dist/bitwrench.umd.min.js +6 -6
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +96 -96
- package/dist/bwserve.cjs.js +140 -7
- package/dist/bwserve.esm.js +141 -8
- package/dist/sri.json +46 -46
- package/docs/README.md +5 -3
- package/docs/bitwrench-for-wasm.md +851 -0
- package/docs/bitwrench-mcp.md +1 -1
- package/docs/bitwrench-taco-schema-discussion.md +694 -0
- package/docs/bitwrench_api.md +134 -24
- package/docs/bitwrench_typescript_usage.md +441 -0
- package/docs/component-cheatsheet.md +1 -1
- package/docs/framework-translation-table.md +1 -1
- package/docs/llm-bitwrench-guide.md +34 -6
- package/docs/routing.md +1 -1
- package/docs/state-management.md +27 -3
- package/docs/thinking-in-bitwrench.md +6 -5
- package/docs/tutorial-bwserve.md +1 -1
- package/docs/tutorial-website.md +1 -1
- package/package.json +16 -10
- package/readme.html +29 -14
- package/src/bitwrench-styles.js +17 -17
- package/src/bitwrench.d.ts +666 -0
- package/src/bitwrench.js +638 -150
- 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 -9
- package/src/mcp/live.js +3 -1
- package/src/mcp/server.js +7 -7
- package/src/version.js +3 -3
package/dist/bitwrench.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bitwrench v2.0.
|
|
1
|
+
/*! bitwrench v2.0.30 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -8,14 +8,14 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const VERSION_INFO = {
|
|
11
|
-
version: '2.0.
|
|
11
|
+
version: '2.0.30',
|
|
12
12
|
name: 'bitwrench',
|
|
13
13
|
description: 'A library for javascript UI functions.',
|
|
14
14
|
license: 'BSD-2-Clause',
|
|
15
15
|
homepage: 'https://deftio.github.com/bitwrench/pages',
|
|
16
16
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
17
17
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
18
|
-
buildDate: '2026-
|
|
18
|
+
buildDate: '2026-04-12T07:51:29.111Z'
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -705,7 +705,7 @@ function generateTypographyThemed(scope, palette, layout) {
|
|
|
705
705
|
'transition': 'color ' + mot.fast + ' ' + mot.easing
|
|
706
706
|
};
|
|
707
707
|
rules[_sx(scope, 'a:hover')] = {
|
|
708
|
-
'color': palette.
|
|
708
|
+
'color': palette.tertiary.hover,
|
|
709
709
|
'text-decoration': 'underline'
|
|
710
710
|
};
|
|
711
711
|
return rules;
|
|
@@ -885,7 +885,7 @@ function generateNavigation(scope, palette, layout) {
|
|
|
885
885
|
'transition': 'color ' + layout.motion.fast + ' ' + layout.motion.easing + ', background-color ' + layout.motion.fast + ' ' + layout.motion.easing
|
|
886
886
|
};
|
|
887
887
|
rules[_sx(scope, '.bw_navbar_nav .bw_nav_link:hover')] = {
|
|
888
|
-
'color': palette.
|
|
888
|
+
'color': palette.tertiary.base,
|
|
889
889
|
'background-color': palette.surfaceAlt
|
|
890
890
|
};
|
|
891
891
|
rules[_sx(scope, '.bw_navbar_nav .bw_nav_link.active')] = {
|
|
@@ -966,7 +966,7 @@ function generateTabs(scope, palette, layout) {
|
|
|
966
966
|
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', border-color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
967
967
|
};
|
|
968
968
|
rules[_sx(scope, '.bw_nav_tabs .bw_nav_link:hover')] = {
|
|
969
|
-
'color': palette.
|
|
969
|
+
'color': palette.tertiary.base,
|
|
970
970
|
'background-color': palette.surfaceAlt,
|
|
971
971
|
'border-bottom-color': palette.light.border
|
|
972
972
|
};
|
|
@@ -992,7 +992,7 @@ function generateListGroups(scope, palette, layout) {
|
|
|
992
992
|
};
|
|
993
993
|
rules[_sx(scope, 'a.bw_list_group_item:hover')] = {
|
|
994
994
|
'background-color': palette.surfaceAlt,
|
|
995
|
-
'color': palette.
|
|
995
|
+
'color': palette.tertiary.base
|
|
996
996
|
};
|
|
997
997
|
rules[_sx(scope, '.bw_list_group_item.active')] = {
|
|
998
998
|
'color': palette.primary.textOn,
|
|
@@ -1089,11 +1089,11 @@ function generateBreadcrumbThemed(scope, palette, layout) {
|
|
|
1089
1089
|
'color': palette.secondary.base
|
|
1090
1090
|
};
|
|
1091
1091
|
rules[_sx(scope, '.bw_breadcrumb_item a')] = {
|
|
1092
|
-
'color': palette.
|
|
1092
|
+
'color': palette.tertiary.base,
|
|
1093
1093
|
'transition': 'color ' + mo.fast + ' ' + mo.easing
|
|
1094
1094
|
};
|
|
1095
1095
|
rules[_sx(scope, '.bw_breadcrumb_item a:hover')] = {
|
|
1096
|
-
'color': palette.
|
|
1096
|
+
'color': palette.tertiary.hover,
|
|
1097
1097
|
'text-decoration': 'underline'
|
|
1098
1098
|
};
|
|
1099
1099
|
rules[_sx(scope, '.bw_breadcrumb_item.active')] = {
|
|
@@ -1328,11 +1328,11 @@ function generateStepperThemed(scope, palette) {
|
|
|
1328
1328
|
'font-weight': '600'
|
|
1329
1329
|
};
|
|
1330
1330
|
rules[_sx(scope, '.bw_step_completed .bw_step_indicator')] = {
|
|
1331
|
-
'background-color': palette.
|
|
1332
|
-
'color': palette.
|
|
1331
|
+
'background-color': palette.tertiary.base,
|
|
1332
|
+
'color': palette.tertiary.textOn
|
|
1333
1333
|
};
|
|
1334
|
-
rules[_sx(scope, '.bw_step_completed .bw_step_label')] = { 'color': palette.
|
|
1335
|
-
rules[_sx(scope, '.bw_step_completed + .bw_step::before')] = { 'background-color': palette.
|
|
1334
|
+
rules[_sx(scope, '.bw_step_completed .bw_step_label')] = { 'color': palette.tertiary.base };
|
|
1335
|
+
rules[_sx(scope, '.bw_step_completed + .bw_step::before')] = { 'background-color': palette.tertiary.base };
|
|
1336
1336
|
return rules;
|
|
1337
1337
|
}
|
|
1338
1338
|
|
|
@@ -1594,14 +1594,14 @@ function generatePaletteClasses(scope, palette) {
|
|
|
1594
1594
|
};
|
|
1595
1595
|
});
|
|
1596
1596
|
|
|
1597
|
-
// Text muted —
|
|
1598
|
-
rules[_sx(scope, '.bw_text_muted')] = { 'color':
|
|
1597
|
+
// Text muted — uses palette secondary for theme-aware muted text
|
|
1598
|
+
rules[_sx(scope, '.bw_text_muted')] = { 'color': palette.secondary.base };
|
|
1599
1599
|
|
|
1600
|
-
// Common bg/text utilities
|
|
1601
|
-
rules[_sx(scope, '.bw_bg_dark')] = { 'background-color':
|
|
1602
|
-
rules[_sx(scope, '.bw_bg_light')] = { 'background-color':
|
|
1603
|
-
rules[_sx(scope, '.bw_text_light')] = { 'color':
|
|
1604
|
-
rules[_sx(scope, '.bw_text_dark')] = { 'color':
|
|
1600
|
+
// Common bg/text utilities — derive from palette for theme awareness
|
|
1601
|
+
rules[_sx(scope, '.bw_bg_dark')] = { 'background-color': palette.dark.base, 'color': palette.dark.textOn };
|
|
1602
|
+
rules[_sx(scope, '.bw_bg_light')] = { 'background-color': palette.light.base, 'color': palette.light.textOn };
|
|
1603
|
+
rules[_sx(scope, '.bw_text_light')] = { 'color': palette.light.base };
|
|
1604
|
+
rules[_sx(scope, '.bw_text_dark')] = { 'color': palette.dark.base };
|
|
1605
1605
|
|
|
1606
1606
|
return rules;
|
|
1607
1607
|
}
|
|
@@ -7781,7 +7781,6 @@ var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() =
|
|
|
7781
7781
|
// Console aliases use thin wrappers (not direct references) so that test
|
|
7782
7782
|
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
7783
7783
|
var _cw = function() { console.warn.apply(console, arguments); };
|
|
7784
|
-
var _cl = function() { console.log.apply(console, arguments); };
|
|
7785
7784
|
var _ce = function() { console.error.apply(console, arguments); };
|
|
7786
7785
|
|
|
7787
7786
|
/**
|
|
@@ -7918,61 +7917,105 @@ bw.uuid = function(prefix) {
|
|
|
7918
7917
|
};
|
|
7919
7918
|
|
|
7920
7919
|
/**
|
|
7921
|
-
* Look up a DOM element by ID
|
|
7920
|
+
* Look up a single DOM element by ID, CSS selector, UUID, or element ref.
|
|
7921
|
+
* Optionally apply content or a function to the resolved element.
|
|
7922
7922
|
*
|
|
7923
|
-
* Resolution order:
|
|
7924
|
-
* 1. Check `bw._nodeMap[id]`
|
|
7925
|
-
* 2.
|
|
7926
|
-
* 3.
|
|
7927
|
-
* 4.
|
|
7928
|
-
* 5. Cache the result for next time
|
|
7923
|
+
* Resolution order for string targets:
|
|
7924
|
+
* 1. Check `bw._nodeMap[id]` cache (O(1), stale entries auto-pruned)
|
|
7925
|
+
* 2. `document.getElementById(id)`
|
|
7926
|
+
* 3. `document.querySelector(id)` for selectors starting with # or .
|
|
7927
|
+
* 4. Class-based lookup for `bw_uuid_*` tokens
|
|
7929
7928
|
*
|
|
7930
|
-
*
|
|
7931
|
-
*
|
|
7932
|
-
*
|
|
7933
|
-
*
|
|
7929
|
+
* With one argument, returns the element (or null). With two arguments,
|
|
7930
|
+
* applies the second argument to the element and returns the element:
|
|
7931
|
+
* - string/number: sets `el.textContent`
|
|
7932
|
+
* - function: calls `apply(el)`, returns el
|
|
7933
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
7934
|
+
* - array: clears children, appends each item (string -> text node, TACO -> element)
|
|
7934
7935
|
*
|
|
7935
|
-
* @param {string|Element}
|
|
7936
|
+
* @param {string|Element} target - Element ref, ID, CSS selector, or bw_uuid_* class
|
|
7937
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
7936
7938
|
* @returns {Element|null} The DOM element, or null if not found
|
|
7937
|
-
* @category
|
|
7939
|
+
* @category DOM Selection
|
|
7940
|
+
* @see bw.$
|
|
7941
|
+
* @see bw.patch
|
|
7942
|
+
* @example
|
|
7943
|
+
* bw.el('#title') // lookup
|
|
7944
|
+
* bw.el('#title', 'Hello') // set text content
|
|
7945
|
+
* bw.el('#app', { t: 'h1', c: 'Hi' }) // mount TACO
|
|
7946
|
+
* bw.el('.card', function(el) { // apply function
|
|
7947
|
+
* el.style.opacity = '0.5';
|
|
7948
|
+
* })
|
|
7938
7949
|
*/
|
|
7939
|
-
bw.
|
|
7940
|
-
//
|
|
7941
|
-
|
|
7942
|
-
if (!
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
if (cached
|
|
7950
|
-
|
|
7950
|
+
bw.el = function(target, apply) {
|
|
7951
|
+
// Resolve target to element
|
|
7952
|
+
var el;
|
|
7953
|
+
if (!_is(target, 'string')) {
|
|
7954
|
+
el = target || null;
|
|
7955
|
+
} else if (!target || !bw._isBrowser) {
|
|
7956
|
+
el = null;
|
|
7957
|
+
} else {
|
|
7958
|
+
// 1. Check cache
|
|
7959
|
+
var cached = bw._nodeMap[target];
|
|
7960
|
+
if (cached) {
|
|
7961
|
+
if (cached.parentNode !== null) {
|
|
7962
|
+
el = cached;
|
|
7963
|
+
} else {
|
|
7964
|
+
delete bw._nodeMap[target];
|
|
7965
|
+
}
|
|
7966
|
+
}
|
|
7967
|
+
if (!el) {
|
|
7968
|
+
// 2. getElementById
|
|
7969
|
+
el = document.getElementById(target);
|
|
7970
|
+
// 3. querySelector for CSS selectors
|
|
7971
|
+
if (!el && (target.charAt(0) === '#' || target.charAt(0) === '.')) {
|
|
7972
|
+
el = document.querySelector(target);
|
|
7973
|
+
}
|
|
7974
|
+
// 4. bw_uuid_* class lookup
|
|
7975
|
+
if (!el && target.indexOf('bw_uuid_') === 0) {
|
|
7976
|
+
el = document.querySelector('.' + target);
|
|
7977
|
+
}
|
|
7978
|
+
// 5. Cache result
|
|
7979
|
+
if (el) bw._nodeMap[target] = el;
|
|
7951
7980
|
}
|
|
7952
|
-
// Stale — remove and fall through
|
|
7953
|
-
delete bw._nodeMap[id];
|
|
7954
7981
|
}
|
|
7955
7982
|
|
|
7956
|
-
//
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
// 3. Try querySelector for CSS selectors (starts with # or .)
|
|
7960
|
-
if (!el && (id.charAt(0) === '#' || id.charAt(0) === '.')) {
|
|
7961
|
-
el = document.querySelector(id);
|
|
7962
|
-
}
|
|
7983
|
+
// Apply (if provided and element found)
|
|
7984
|
+
if (el && apply !== undefined) _applyTo(el, apply);
|
|
7963
7985
|
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
el = document.querySelector('.' + id);
|
|
7967
|
-
}
|
|
7986
|
+
return el;
|
|
7987
|
+
};
|
|
7968
7988
|
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7989
|
+
/**
|
|
7990
|
+
* Internal: apply content or function to a DOM element.
|
|
7991
|
+
* Shared by bw.el() and bw.$().
|
|
7992
|
+
* @private
|
|
7993
|
+
*/
|
|
7994
|
+
function _applyTo(el, apply) {
|
|
7995
|
+
if (_is(apply, 'function')) {
|
|
7996
|
+
apply(el);
|
|
7997
|
+
} else if (_isA(apply)) {
|
|
7998
|
+
el.innerHTML = '';
|
|
7999
|
+
apply.forEach(function(item) {
|
|
8000
|
+
if (item != null) {
|
|
8001
|
+
if (_is(item, 'object') && item.t) {
|
|
8002
|
+
el.appendChild(bw.createDOM(item));
|
|
8003
|
+
} else {
|
|
8004
|
+
el.appendChild(document.createTextNode(String(item)));
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
});
|
|
8008
|
+
} else if (_is(apply, 'object') && apply !== null && apply.t) {
|
|
8009
|
+
el.innerHTML = '';
|
|
8010
|
+
el.appendChild(bw.createDOM(apply));
|
|
8011
|
+
} else {
|
|
8012
|
+
el.textContent = String(apply);
|
|
7972
8013
|
}
|
|
8014
|
+
}
|
|
7973
8015
|
|
|
7974
|
-
|
|
7975
|
-
|
|
8016
|
+
// Internal alias — kept for one release cycle (v2.0.26).
|
|
8017
|
+
// Will be removed in v2.0.27. Use bw.el() instead.
|
|
8018
|
+
bw._el = bw.el;
|
|
7976
8019
|
|
|
7977
8020
|
/**
|
|
7978
8021
|
* Register a DOM element in the node cache under one or more keys.
|
|
@@ -8036,6 +8079,12 @@ var _BW_LC = 'bw_lc';
|
|
|
8036
8079
|
*/
|
|
8037
8080
|
var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
|
|
8038
8081
|
|
|
8082
|
+
/**
|
|
8083
|
+
* SVG namespace URI for createElementNS.
|
|
8084
|
+
* @private
|
|
8085
|
+
*/
|
|
8086
|
+
var _SVG_NS = 'http://www.w3.org/2000/svg';
|
|
8087
|
+
|
|
8039
8088
|
/**
|
|
8040
8089
|
* Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
|
|
8041
8090
|
*
|
|
@@ -8090,9 +8139,10 @@ bw.getUUID = function(tacoOrElement) {
|
|
|
8090
8139
|
if (!tacoOrElement) return null;
|
|
8091
8140
|
|
|
8092
8141
|
var classStr;
|
|
8093
|
-
// DOM element: check className
|
|
8142
|
+
// DOM element: check className (SVG elements use getAttribute for string value)
|
|
8094
8143
|
if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
|
|
8095
|
-
classStr = tacoOrElement.className
|
|
8144
|
+
classStr = typeof tacoOrElement.className === 'string'
|
|
8145
|
+
? tacoOrElement.className : (tacoOrElement.getAttribute('class') || '');
|
|
8096
8146
|
}
|
|
8097
8147
|
// TACO object: check a.class
|
|
8098
8148
|
else if (tacoOrElement.a && _is(tacoOrElement.a.class, 'string')) {
|
|
@@ -8361,7 +8411,7 @@ bw.htmlPage = function(opts) {
|
|
|
8361
8411
|
var fnCounterBefore = bw._fnIDCounter;
|
|
8362
8412
|
|
|
8363
8413
|
// Render body content
|
|
8364
|
-
var bodyHTML
|
|
8414
|
+
var bodyHTML;
|
|
8365
8415
|
if (_is(body, 'string')) {
|
|
8366
8416
|
bodyHTML = body;
|
|
8367
8417
|
} else {
|
|
@@ -8532,9 +8582,11 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8532
8582
|
}
|
|
8533
8583
|
|
|
8534
8584
|
const { t: tag, a: attrs = {}, c: content, o: opts = {} } = taco;
|
|
8535
|
-
|
|
8536
|
-
//
|
|
8537
|
-
|
|
8585
|
+
|
|
8586
|
+
// SVG namespace: detect SVG context and thread through children.
|
|
8587
|
+
// {t:'svg'} starts SVG context; foreignObject children revert to HTML.
|
|
8588
|
+
var svgCtx = options._svgCtx || (tag === 'svg');
|
|
8589
|
+
var el = svgCtx ? document.createElementNS(_SVG_NS, tag) : document.createElement(tag);
|
|
8538
8590
|
|
|
8539
8591
|
// Set attributes
|
|
8540
8592
|
for (const [key, value] of Object.entries(attrs)) {
|
|
@@ -8545,9 +8597,11 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8545
8597
|
Object.assign(el.style, value);
|
|
8546
8598
|
} else if (key === 'class') {
|
|
8547
8599
|
// Handle class as array or string
|
|
8600
|
+
// SVG elements use SVGAnimatedString for className, so use setAttribute
|
|
8548
8601
|
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
8549
8602
|
if (classStr) {
|
|
8550
|
-
el.
|
|
8603
|
+
if (svgCtx) el.setAttribute('class', classStr);
|
|
8604
|
+
else el.className = classStr;
|
|
8551
8605
|
}
|
|
8552
8606
|
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
8553
8607
|
// Event handlers
|
|
@@ -8568,11 +8622,17 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8568
8622
|
// Add children, building _bw_refs for fast parent→child access.
|
|
8569
8623
|
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
8570
8624
|
// so o.render functions can access them without any DOM lookup.
|
|
8625
|
+
// SVG: foreignObject children revert to HTML namespace; otherwise inherit.
|
|
8626
|
+
var childOpts = options;
|
|
8627
|
+
var childSvgCtx = svgCtx && tag !== 'foreignObject';
|
|
8628
|
+
if (childSvgCtx !== (options._svgCtx || false)) {
|
|
8629
|
+
childOpts = Object.assign({}, options, {_svgCtx: childSvgCtx || undefined});
|
|
8630
|
+
}
|
|
8571
8631
|
if (content != null) {
|
|
8572
8632
|
if (_isA(content)) {
|
|
8573
8633
|
content.forEach(child => {
|
|
8574
8634
|
if (child != null) {
|
|
8575
|
-
var childEl = bw.createDOM(child,
|
|
8635
|
+
var childEl = bw.createDOM(child, childOpts);
|
|
8576
8636
|
el.appendChild(childEl);
|
|
8577
8637
|
// Build local refs for addressable children
|
|
8578
8638
|
var childRefId = (child && child.a) ? (child.a.id || bw.getUUID(child)) : null;
|
|
@@ -8595,7 +8655,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8595
8655
|
// Raw HTML content — inject via innerHTML
|
|
8596
8656
|
el.innerHTML = content.v;
|
|
8597
8657
|
} else if (_is(content, 'object') && content.t) {
|
|
8598
|
-
var childEl = bw.createDOM(content,
|
|
8658
|
+
var childEl = bw.createDOM(content, childOpts);
|
|
8599
8659
|
el.appendChild(childEl);
|
|
8600
8660
|
var childRefId = content.a ? (content.a.id || bw.getUUID(content)) : null;
|
|
8601
8661
|
if (childRefId) {
|
|
@@ -8621,13 +8681,21 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8621
8681
|
}
|
|
8622
8682
|
|
|
8623
8683
|
// Register UUID class in node cache (bw_uuid_* tokens in class string)
|
|
8624
|
-
|
|
8625
|
-
|
|
8684
|
+
// SVG elements have SVGAnimatedString for className; use getAttribute instead
|
|
8685
|
+
var clsStr = svgCtx ? (el.getAttribute('class') || '') : el.className;
|
|
8686
|
+
if (clsStr) {
|
|
8687
|
+
var uuidMatch = clsStr.match(_UUID_RE);
|
|
8626
8688
|
if (uuidMatch) {
|
|
8627
8689
|
bw._nodeMap[uuidMatch[0]] = el;
|
|
8628
8690
|
}
|
|
8629
8691
|
}
|
|
8630
8692
|
|
|
8693
|
+
// Store component type metadata (e.g., 'card', 'tabs') for introspection.
|
|
8694
|
+
// BCCL factories set o.type; custom components can too.
|
|
8695
|
+
if (opts.type) {
|
|
8696
|
+
el._bw_type = opts.type;
|
|
8697
|
+
}
|
|
8698
|
+
|
|
8631
8699
|
// Handle lifecycle hooks and state
|
|
8632
8700
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
8633
8701
|
// Ensure element has a UUID class for identity
|
|
@@ -8657,11 +8725,13 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8657
8725
|
|
|
8658
8726
|
if (mountFn) {
|
|
8659
8727
|
if (document.body.contains(el)) {
|
|
8660
|
-
mountFn(el, el._bw_state || {});
|
|
8728
|
+
try { mountFn(el, el._bw_state || {}); }
|
|
8729
|
+
catch (e) { _cw('o.mounted error: ' + e.message); }
|
|
8661
8730
|
} else {
|
|
8662
8731
|
requestAnimationFrame(() => {
|
|
8663
8732
|
if (document.body.contains(el)) {
|
|
8664
|
-
mountFn(el, el._bw_state || {});
|
|
8733
|
+
try { mountFn(el, el._bw_state || {}); }
|
|
8734
|
+
catch (e) { _cw('o.mounted error: ' + e.message); }
|
|
8665
8735
|
}
|
|
8666
8736
|
});
|
|
8667
8737
|
}
|
|
@@ -8670,7 +8740,8 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8670
8740
|
// Store unmount callback keyed by UUID class
|
|
8671
8741
|
if (opts.unmount) {
|
|
8672
8742
|
bw._unmountCallbacks.set(uuid, () => {
|
|
8673
|
-
opts.unmount(el, el._bw_state || {});
|
|
8743
|
+
try { opts.unmount(el, el._bw_state || {}); }
|
|
8744
|
+
catch (e) { _cw('o.unmount error: ' + e.message); }
|
|
8674
8745
|
});
|
|
8675
8746
|
}
|
|
8676
8747
|
}
|
|
@@ -8689,24 +8760,25 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8689
8760
|
}
|
|
8690
8761
|
|
|
8691
8762
|
// Slot declarations: auto-generate setX/getX pairs
|
|
8763
|
+
// The target element is cached at creation time to avoid repeated
|
|
8764
|
+
// querySelector calls on every get/set invocation.
|
|
8692
8765
|
if (opts.slots) {
|
|
8693
8766
|
for (var sk in opts.slots) {
|
|
8694
8767
|
if (_hop.call(opts.slots, sk)) {
|
|
8695
8768
|
(function(name, selector) {
|
|
8769
|
+
var target = el.querySelector(selector);
|
|
8696
8770
|
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
8697
8771
|
el.bw['set' + cap] = function(value) {
|
|
8698
|
-
|
|
8699
|
-
if (!t) return;
|
|
8772
|
+
if (!target) return;
|
|
8700
8773
|
if (value != null && typeof value === 'object' && value.t) {
|
|
8701
|
-
|
|
8702
|
-
|
|
8774
|
+
target.innerHTML = '';
|
|
8775
|
+
target.appendChild(bw.createDOM(value));
|
|
8703
8776
|
} else {
|
|
8704
|
-
|
|
8777
|
+
target.textContent = (value != null) ? String(value) : '';
|
|
8705
8778
|
}
|
|
8706
8779
|
};
|
|
8707
8780
|
el.bw['get' + cap] = function() {
|
|
8708
|
-
|
|
8709
|
-
return t ? t.textContent : '';
|
|
8781
|
+
return target ? target.textContent : '';
|
|
8710
8782
|
};
|
|
8711
8783
|
})(sk, opts.slots[sk]);
|
|
8712
8784
|
}
|
|
@@ -8747,7 +8819,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
8747
8819
|
}
|
|
8748
8820
|
|
|
8749
8821
|
// Get target element (use cache-backed lookup)
|
|
8750
|
-
const targetEl = bw.
|
|
8822
|
+
const targetEl = bw.el(target);
|
|
8751
8823
|
|
|
8752
8824
|
if (!targetEl) {
|
|
8753
8825
|
_ce('bw.DOM: Target element not found:', target);
|
|
@@ -8850,7 +8922,8 @@ bw.cleanup = function(element) {
|
|
|
8850
8922
|
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
8851
8923
|
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
8852
8924
|
uuidEls.forEach(function(uel) {
|
|
8853
|
-
var
|
|
8925
|
+
var uc = typeof uel.className === 'string' ? uel.className : (uel.getAttribute('class') || '');
|
|
8926
|
+
var m = uc && uc.match(_UUID_RE);
|
|
8854
8927
|
if (m) delete bw._nodeMap[m[0]];
|
|
8855
8928
|
});
|
|
8856
8929
|
|
|
@@ -8936,9 +9009,10 @@ bw.cleanup = function(element) {
|
|
|
8936
9009
|
* bw.update(el); // re-renders, emits bw:statechange
|
|
8937
9010
|
*/
|
|
8938
9011
|
bw.update = function(target) {
|
|
8939
|
-
var el = bw.
|
|
9012
|
+
var el = bw.el(target);
|
|
8940
9013
|
if (el && el._bw_render) {
|
|
8941
|
-
el._bw_render(el, el._bw_state || {});
|
|
9014
|
+
try { el._bw_render(el, el._bw_state || {}); }
|
|
9015
|
+
catch (e) { _cw('o.render error: ' + e.message); }
|
|
8942
9016
|
bw.emit(el, 'statechange', el._bw_state);
|
|
8943
9017
|
}
|
|
8944
9018
|
return el || null;
|
|
@@ -8965,7 +9039,7 @@ bw.update = function(target) {
|
|
|
8965
9039
|
* bw.patch('info', { t: 'em', c: 'new' }); // replace children with TACO
|
|
8966
9040
|
*/
|
|
8967
9041
|
bw.patch = function(id, content, attr) {
|
|
8968
|
-
var el = bw.
|
|
9042
|
+
var el = bw.el(id);
|
|
8969
9043
|
if (!el) return null;
|
|
8970
9044
|
|
|
8971
9045
|
if (attr) {
|
|
@@ -9037,7 +9111,7 @@ bw.patchAll = function(patches) {
|
|
|
9037
9111
|
* // Dispatches CustomEvent 'bw:statechange' on the element
|
|
9038
9112
|
*/
|
|
9039
9113
|
bw.emit = function(target, eventName, detail) {
|
|
9040
|
-
var el = bw.
|
|
9114
|
+
var el = bw.el(target);
|
|
9041
9115
|
if (el) {
|
|
9042
9116
|
el.dispatchEvent(new CustomEvent('bw:' + eventName, {
|
|
9043
9117
|
bubbles: true,
|
|
@@ -9066,7 +9140,7 @@ bw.emit = function(target, eventName, detail) {
|
|
|
9066
9140
|
* });
|
|
9067
9141
|
*/
|
|
9068
9142
|
bw.on = function(target, eventName, handler) {
|
|
9069
|
-
var el = bw.
|
|
9143
|
+
var el = bw.el(target);
|
|
9070
9144
|
if (el) {
|
|
9071
9145
|
el.addEventListener('bw:' + eventName, function(e) {
|
|
9072
9146
|
handler(e.detail, e);
|
|
@@ -9093,23 +9167,38 @@ bw.on = function(target, eventName, handler) {
|
|
|
9093
9167
|
*
|
|
9094
9168
|
* @param {string} topic - Topic name (plain string, no prefix)
|
|
9095
9169
|
* @param {*} [detail] - Data to pass to subscribers
|
|
9096
|
-
* @returns {number} Count of successfully called subscribers
|
|
9170
|
+
* @returns {number} Count of successfully called subscribers (including wildcard matches)
|
|
9097
9171
|
* @category Pub/Sub
|
|
9098
9172
|
* @see bw.sub
|
|
9099
9173
|
* @example
|
|
9100
9174
|
* bw.pub('score:updated', { player: 'X', score: 10 });
|
|
9175
|
+
* // Wildcard subscribers matching 'score:*' will also fire
|
|
9101
9176
|
*/
|
|
9102
9177
|
bw.pub = function(topic, detail) {
|
|
9103
|
-
var subs = bw._topics[topic];
|
|
9104
|
-
if (!subs || subs.length === 0) return 0;
|
|
9105
|
-
var snapshot = subs.slice(); // safe against unsub during iteration
|
|
9106
9178
|
var called = 0;
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9179
|
+
// Exact-match subscribers
|
|
9180
|
+
var subs = bw._topics[topic];
|
|
9181
|
+
if (subs && subs.length > 0) {
|
|
9182
|
+
var snapshot = subs.slice();
|
|
9183
|
+
for (var i = 0; i < snapshot.length; i++) {
|
|
9184
|
+
try { snapshot[i].handler(detail, topic); called++; }
|
|
9185
|
+
catch (err) { _cw('bw.pub: subscriber error on topic "' + topic + '":', err); }
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
// Wildcard subscribers -- patterns ending with '*'
|
|
9189
|
+
var keys = Object.keys(bw._topics);
|
|
9190
|
+
for (var k = 0; k < keys.length; k++) {
|
|
9191
|
+
var pat = keys[k];
|
|
9192
|
+
if (pat.charAt(pat.length - 1) !== '*') continue;
|
|
9193
|
+
var prefix = pat.slice(0, -1); // strip trailing '*'
|
|
9194
|
+
if (topic.length >= prefix.length && topic.substring(0, prefix.length) === prefix && topic !== pat) {
|
|
9195
|
+
var wsubs = bw._topics[pat];
|
|
9196
|
+
if (!wsubs) continue;
|
|
9197
|
+
var wsnap = wsubs.slice();
|
|
9198
|
+
for (var w = 0; w < wsnap.length; w++) {
|
|
9199
|
+
try { wsnap[w].handler(detail, topic); called++; }
|
|
9200
|
+
catch (err) { _cw('bw.pub: wildcard subscriber error on "' + pat + '" for topic "' + topic + '":', err); }
|
|
9201
|
+
}
|
|
9113
9202
|
}
|
|
9114
9203
|
}
|
|
9115
9204
|
return called;
|
|
@@ -9118,12 +9207,17 @@ bw.pub = function(topic, detail) {
|
|
|
9118
9207
|
/**
|
|
9119
9208
|
* Subscribe to a topic. Returns an unsub() function.
|
|
9120
9209
|
*
|
|
9121
|
-
*
|
|
9210
|
+
* Supports wildcard patterns: a topic ending in `*` matches any published
|
|
9211
|
+
* topic that starts with the prefix before the `*`. For example,
|
|
9212
|
+
* `'agui:*'` matches `'agui:ready'`, `'agui:error'`, etc. The handler
|
|
9213
|
+
* receives `(detail, topic)` so it can distinguish which topic fired.
|
|
9214
|
+
*
|
|
9215
|
+
* Optional third argument ties the subscription to a DOM element's lifecycle --
|
|
9122
9216
|
* when `bw.cleanup()` is called on that element, the subscription is automatically
|
|
9123
9217
|
* removed, preventing memory leaks.
|
|
9124
9218
|
*
|
|
9125
|
-
* @param {string} topic - Topic name
|
|
9126
|
-
* @param {Function} handler - Called with (detail) on each publish
|
|
9219
|
+
* @param {string} topic - Topic name, or wildcard pattern ending in '*'
|
|
9220
|
+
* @param {Function} handler - Called with (detail, topic) on each publish
|
|
9127
9221
|
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
9128
9222
|
* @returns {Function} Call to unsubscribe
|
|
9129
9223
|
* @category Pub/Sub
|
|
@@ -9134,6 +9228,11 @@ bw.pub = function(topic, detail) {
|
|
|
9134
9228
|
* console.log(detail.player, 'scored', detail.score);
|
|
9135
9229
|
* });
|
|
9136
9230
|
* // Later: unsub() to stop listening
|
|
9231
|
+
*
|
|
9232
|
+
* // Wildcard: listen to all 'agui:' topics
|
|
9233
|
+
* bw.sub('agui:*', function(detail, topic) {
|
|
9234
|
+
* console.log('Got', topic, detail);
|
|
9235
|
+
* });
|
|
9137
9236
|
*/
|
|
9138
9237
|
bw.sub = function(topic, handler, el) {
|
|
9139
9238
|
var id = ++bw._subIdCounter;
|
|
@@ -9185,6 +9284,37 @@ bw.unsub = function(topic, handler) {
|
|
|
9185
9284
|
return removed;
|
|
9186
9285
|
};
|
|
9187
9286
|
|
|
9287
|
+
/**
|
|
9288
|
+
* Subscribe to a topic for a single event only. The subscription is
|
|
9289
|
+
* automatically removed after the first publish. Equivalent to manually
|
|
9290
|
+
* calling unsub() inside a bw.sub() handler, but avoids the common bug
|
|
9291
|
+
* of forgetting to unsubscribe.
|
|
9292
|
+
*
|
|
9293
|
+
* @param {string} topic - Topic name
|
|
9294
|
+
* @param {Function} handler - Called once with (detail) on the next publish
|
|
9295
|
+
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
9296
|
+
* @returns {Function} Call to cancel the subscription before it fires
|
|
9297
|
+
* @category Pub/Sub
|
|
9298
|
+
* @see bw.sub
|
|
9299
|
+
* @see bw.pub
|
|
9300
|
+
* @example
|
|
9301
|
+
* bw.once('data:loaded', function(detail) {
|
|
9302
|
+
* console.log('Received:', detail);
|
|
9303
|
+
* // No need to unsubscribe -- already done automatically
|
|
9304
|
+
* });
|
|
9305
|
+
*
|
|
9306
|
+
* // Cancel before it fires:
|
|
9307
|
+
* var cancel = bw.once('timeout', handler);
|
|
9308
|
+
* cancel(); // handler will never be called
|
|
9309
|
+
*/
|
|
9310
|
+
bw.once = function(topic, handler, el) {
|
|
9311
|
+
var unsub = bw.sub(topic, function(detail) {
|
|
9312
|
+
unsub();
|
|
9313
|
+
handler(detail);
|
|
9314
|
+
}, el);
|
|
9315
|
+
return unsub;
|
|
9316
|
+
};
|
|
9317
|
+
|
|
9188
9318
|
// ===================================================================================
|
|
9189
9319
|
// Function Registry (revived from v1 for string dispatch contexts)
|
|
9190
9320
|
// ===================================================================================
|
|
@@ -9425,7 +9555,7 @@ bw.component = function() { throw new Error('bw.component() removed in v2.0.19.
|
|
|
9425
9555
|
* };
|
|
9426
9556
|
*/
|
|
9427
9557
|
bw.message = function(target, action, data) {
|
|
9428
|
-
var el = bw.
|
|
9558
|
+
var el = bw.el(target);
|
|
9429
9559
|
if (!el) el = bw.$('.' + target)[0];
|
|
9430
9560
|
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
9431
9561
|
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
@@ -9435,6 +9565,207 @@ bw.message = function(target, action, data) {
|
|
|
9435
9565
|
return true;
|
|
9436
9566
|
};
|
|
9437
9567
|
|
|
9568
|
+
/**
|
|
9569
|
+
* Collect form data from all input, select, and textarea elements within a
|
|
9570
|
+
* container. Each element's `name` attribute (or `id` if no name) becomes a
|
|
9571
|
+
* key in the returned object. This provides a lightweight alternative to the
|
|
9572
|
+
* browser FormData API that returns a plain object suitable for JSON
|
|
9573
|
+
* serialization or bw.pub().
|
|
9574
|
+
*
|
|
9575
|
+
* Handles all standard HTML form controls:
|
|
9576
|
+
* - text/number/email/etc inputs: string value
|
|
9577
|
+
* - checkboxes: boolean (true/false)
|
|
9578
|
+
* - radio buttons: string value of the checked radio (unchecked groups omitted)
|
|
9579
|
+
* - multi-select: array of selected option values
|
|
9580
|
+
* - textarea: string value
|
|
9581
|
+
*
|
|
9582
|
+
* Elements without both `name` and `id` attributes are silently skipped.
|
|
9583
|
+
*
|
|
9584
|
+
* @param {string|Element} target - CSS selector, UUID string, or DOM element
|
|
9585
|
+
* @returns {Object} Plain object mapping field names to values
|
|
9586
|
+
* @category Component
|
|
9587
|
+
* @see bw.makeForm
|
|
9588
|
+
* @see bw.makeInput
|
|
9589
|
+
* @example
|
|
9590
|
+
* // Given a form with name="email" input and name="agree" checkbox:
|
|
9591
|
+
* var data = bw.formData('#signup-form');
|
|
9592
|
+
* // => { email: 'user@example.com', agree: true }
|
|
9593
|
+
*
|
|
9594
|
+
* // Collect and publish in one step:
|
|
9595
|
+
* bw.pub('form:submit', bw.formData('#my-form'));
|
|
9596
|
+
*
|
|
9597
|
+
* // Works with any container, not just <form>:
|
|
9598
|
+
* bw.pub('settings:changed', bw.formData('.settings-panel'));
|
|
9599
|
+
*/
|
|
9600
|
+
bw.formData = function(target) {
|
|
9601
|
+
var el = bw.el(target);
|
|
9602
|
+
if (!el) return {};
|
|
9603
|
+
var result = {};
|
|
9604
|
+
var inputs = el.querySelectorAll('input, select, textarea');
|
|
9605
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
9606
|
+
var inp = inputs[i];
|
|
9607
|
+
var key = inp.name || inp.id;
|
|
9608
|
+
if (!key) continue;
|
|
9609
|
+
if (inp.type === 'checkbox') {
|
|
9610
|
+
result[key] = inp.checked;
|
|
9611
|
+
} else if (inp.type === 'radio') {
|
|
9612
|
+
if (inp.checked) result[key] = inp.value;
|
|
9613
|
+
} else if (inp.tagName === 'SELECT' && inp.multiple) {
|
|
9614
|
+
result[key] = [];
|
|
9615
|
+
for (var j = 0; j < inp.options.length; j++) {
|
|
9616
|
+
if (inp.options[j].selected) result[key].push(inp.options[j].value);
|
|
9617
|
+
}
|
|
9618
|
+
} else {
|
|
9619
|
+
result[key] = inp.value;
|
|
9620
|
+
}
|
|
9621
|
+
}
|
|
9622
|
+
return result;
|
|
9623
|
+
};
|
|
9624
|
+
|
|
9625
|
+
// ===================================================================================
|
|
9626
|
+
// bw.jsonPatch() — RFC 6902 JSON Patch on plain objects
|
|
9627
|
+
// ===================================================================================
|
|
9628
|
+
|
|
9629
|
+
/**
|
|
9630
|
+
* Apply RFC 6902 JSON Patch operations to a plain object.
|
|
9631
|
+
*
|
|
9632
|
+
* Supported operations: add, remove, replace, move, copy, test.
|
|
9633
|
+
* Paths use JSON Pointer (RFC 6901) notation: `/foo/bar/0`.
|
|
9634
|
+
* Mutates the target object in place and returns it.
|
|
9635
|
+
*
|
|
9636
|
+
* @param {Object} obj - Target object to patch
|
|
9637
|
+
* @param {Array<Object>} ops - Array of patch operations
|
|
9638
|
+
* @param {string} ops[].op - Operation: 'add', 'remove', 'replace', 'move', 'copy', 'test'
|
|
9639
|
+
* @param {string} ops[].path - JSON Pointer path (e.g. '/a/b/0')
|
|
9640
|
+
* @param {*} [ops[].value] - Value for add/replace/test
|
|
9641
|
+
* @param {string} [ops[].from] - Source path for move/copy
|
|
9642
|
+
* @returns {Object} The patched object (same reference)
|
|
9643
|
+
* @throws {Error} On invalid op, missing path, test failure, or path not found for remove
|
|
9644
|
+
* @category Data Utilities
|
|
9645
|
+
* @see bw.patch
|
|
9646
|
+
* @example
|
|
9647
|
+
* var obj = { a: 1, b: { c: 2 } };
|
|
9648
|
+
* bw.jsonPatch(obj, [
|
|
9649
|
+
* { op: 'replace', path: '/a', value: 10 },
|
|
9650
|
+
* { op: 'add', path: '/b/d', value: 3 },
|
|
9651
|
+
* { op: 'remove', path: '/b/c' }
|
|
9652
|
+
* ]);
|
|
9653
|
+
* // obj => { a: 10, b: { d: 3 } }
|
|
9654
|
+
*/
|
|
9655
|
+
bw.jsonPatch = function(obj, ops) {
|
|
9656
|
+
if (!_isA(ops)) return obj;
|
|
9657
|
+
|
|
9658
|
+
// Parse JSON Pointer path to array of keys
|
|
9659
|
+
function parsePath(path) {
|
|
9660
|
+
if (path === '') return [];
|
|
9661
|
+
if (path.charAt(0) !== '/') throw new Error('Invalid JSON Pointer: ' + path);
|
|
9662
|
+
return path.slice(1).split('/').map(function(s) {
|
|
9663
|
+
return s.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
9664
|
+
});
|
|
9665
|
+
}
|
|
9666
|
+
|
|
9667
|
+
// Walk to parent of final key; return { parent, key }
|
|
9668
|
+
function resolve(root, keys) {
|
|
9669
|
+
var parent = root;
|
|
9670
|
+
for (var i = 0; i < keys.length - 1; i++) {
|
|
9671
|
+
var k = _isA(parent) ? parseInt(keys[i], 10) : keys[i];
|
|
9672
|
+
if (parent[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
9673
|
+
parent = parent[k];
|
|
9674
|
+
}
|
|
9675
|
+
return { parent: parent, key: _isA(parent) ? parseInt(keys[keys.length - 1], 10) : keys[keys.length - 1] };
|
|
9676
|
+
}
|
|
9677
|
+
|
|
9678
|
+
// Get value at path
|
|
9679
|
+
function getVal(root, keys) {
|
|
9680
|
+
var cur = root;
|
|
9681
|
+
for (var i = 0; i < keys.length; i++) {
|
|
9682
|
+
var k = _isA(cur) ? parseInt(keys[i], 10) : keys[i];
|
|
9683
|
+
if (cur[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
9684
|
+
cur = cur[k];
|
|
9685
|
+
}
|
|
9686
|
+
return cur;
|
|
9687
|
+
}
|
|
9688
|
+
|
|
9689
|
+
for (var i = 0; i < ops.length; i++) {
|
|
9690
|
+
var op = ops[i];
|
|
9691
|
+
if (!op.op || !_is(op.path, 'string')) throw new Error('Invalid patch operation at index ' + i);
|
|
9692
|
+
var keys = parsePath(op.path);
|
|
9693
|
+
|
|
9694
|
+
var r, val, fromKeys, fr, tr, cr;
|
|
9695
|
+
switch (op.op) {
|
|
9696
|
+
case 'add': {
|
|
9697
|
+
if (keys.length === 0) throw new Error('Cannot add to root');
|
|
9698
|
+
r = resolve(obj, keys);
|
|
9699
|
+
if (_isA(r.parent) && r.key <= r.parent.length) {
|
|
9700
|
+
r.parent.splice(r.key, 0, op.value);
|
|
9701
|
+
} else {
|
|
9702
|
+
r.parent[r.key] = op.value;
|
|
9703
|
+
}
|
|
9704
|
+
break;
|
|
9705
|
+
}
|
|
9706
|
+
case 'remove': {
|
|
9707
|
+
if (keys.length === 0) throw new Error('Cannot remove root');
|
|
9708
|
+
r = resolve(obj, keys);
|
|
9709
|
+
if (_isA(r.parent)) {
|
|
9710
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
9711
|
+
r.parent.splice(r.key, 1);
|
|
9712
|
+
} else {
|
|
9713
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
9714
|
+
delete r.parent[r.key];
|
|
9715
|
+
}
|
|
9716
|
+
break;
|
|
9717
|
+
}
|
|
9718
|
+
case 'replace': {
|
|
9719
|
+
if (keys.length === 0) throw new Error('Cannot replace root');
|
|
9720
|
+
r = resolve(obj, keys);
|
|
9721
|
+
if (_isA(r.parent)) {
|
|
9722
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
9723
|
+
} else {
|
|
9724
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
9725
|
+
}
|
|
9726
|
+
r.parent[r.key] = op.value;
|
|
9727
|
+
break;
|
|
9728
|
+
}
|
|
9729
|
+
case 'move': {
|
|
9730
|
+
if (!_is(op.from, 'string')) throw new Error('move requires "from"');
|
|
9731
|
+
fromKeys = parsePath(op.from);
|
|
9732
|
+
val = getVal(obj, fromKeys);
|
|
9733
|
+
fr = resolve(obj, fromKeys);
|
|
9734
|
+
if (_isA(fr.parent)) { fr.parent.splice(fr.key, 1); }
|
|
9735
|
+
else { delete fr.parent[fr.key]; }
|
|
9736
|
+
tr = resolve(obj, keys);
|
|
9737
|
+
if (_isA(tr.parent) && tr.key <= tr.parent.length) {
|
|
9738
|
+
tr.parent.splice(tr.key, 0, val);
|
|
9739
|
+
} else {
|
|
9740
|
+
tr.parent[tr.key] = val;
|
|
9741
|
+
}
|
|
9742
|
+
break;
|
|
9743
|
+
}
|
|
9744
|
+
case 'copy': {
|
|
9745
|
+
if (!_is(op.from, 'string')) throw new Error('copy requires "from"');
|
|
9746
|
+
val = getVal(obj, parsePath(op.from));
|
|
9747
|
+
cr = resolve(obj, keys);
|
|
9748
|
+
if (_isA(cr.parent) && cr.key <= cr.parent.length) {
|
|
9749
|
+
cr.parent.splice(cr.key, 0, val);
|
|
9750
|
+
} else {
|
|
9751
|
+
cr.parent[cr.key] = val;
|
|
9752
|
+
}
|
|
9753
|
+
break;
|
|
9754
|
+
}
|
|
9755
|
+
case 'test': {
|
|
9756
|
+
var actual = getVal(obj, keys);
|
|
9757
|
+
if (JSON.stringify(actual) !== JSON.stringify(op.value)) {
|
|
9758
|
+
throw new Error('Test failed: ' + op.path + ' expected ' + JSON.stringify(op.value) + ' got ' + JSON.stringify(actual));
|
|
9759
|
+
}
|
|
9760
|
+
break;
|
|
9761
|
+
}
|
|
9762
|
+
default:
|
|
9763
|
+
throw new Error('Unknown op: ' + op.op);
|
|
9764
|
+
}
|
|
9765
|
+
}
|
|
9766
|
+
return obj;
|
|
9767
|
+
};
|
|
9768
|
+
|
|
9438
9769
|
// ===================================================================================
|
|
9439
9770
|
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
9440
9771
|
// ===================================================================================
|
|
@@ -9576,7 +9907,7 @@ bw.apply = function(msg) {
|
|
|
9576
9907
|
var target = msg.target;
|
|
9577
9908
|
|
|
9578
9909
|
if (type === 'replace') {
|
|
9579
|
-
var el = bw.
|
|
9910
|
+
var el = bw.el(target);
|
|
9580
9911
|
if (!el) return false;
|
|
9581
9912
|
bw.DOM(el, msg.node);
|
|
9582
9913
|
return true;
|
|
@@ -9586,14 +9917,14 @@ bw.apply = function(msg) {
|
|
|
9586
9917
|
return patched !== null;
|
|
9587
9918
|
|
|
9588
9919
|
} else if (type === 'append') {
|
|
9589
|
-
var parent = bw.
|
|
9920
|
+
var parent = bw.el(target);
|
|
9590
9921
|
if (!parent) return false;
|
|
9591
9922
|
var child = bw.createDOM(msg.node);
|
|
9592
9923
|
parent.appendChild(child);
|
|
9593
9924
|
return true;
|
|
9594
9925
|
|
|
9595
9926
|
} else if (type === 'remove') {
|
|
9596
|
-
var toRemove = bw.
|
|
9927
|
+
var toRemove = bw.el(target);
|
|
9597
9928
|
if (!toRemove) return false;
|
|
9598
9929
|
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
9599
9930
|
toRemove.remove();
|
|
@@ -9653,30 +9984,98 @@ bw.apply = function(msg) {
|
|
|
9653
9984
|
|
|
9654
9985
|
|
|
9655
9986
|
// ===================================================================================
|
|
9656
|
-
// bw.inspect() —
|
|
9987
|
+
// bw.inspect() — DOM introspection with bitwrench metadata
|
|
9657
9988
|
// ===================================================================================
|
|
9658
9989
|
|
|
9659
9990
|
/**
|
|
9660
|
-
* Inspect a DOM element
|
|
9661
|
-
*
|
|
9662
|
-
*
|
|
9663
|
-
*
|
|
9664
|
-
*
|
|
9991
|
+
* Inspect a DOM element and its subtree, returning a plain-object
|
|
9992
|
+
* representation with bitwrench metadata at each node. Useful for debugging,
|
|
9993
|
+
* devtools, MCP/AG-UI tool discovery, and automated testing.
|
|
9994
|
+
*
|
|
9995
|
+
* Each node in the returned tree includes:
|
|
9996
|
+
* - `tag` -- lowercase tag name (or '#text' for text nodes)
|
|
9997
|
+
* - `id` -- element id (if set)
|
|
9998
|
+
* - `uuid` -- bitwrench UUID class (if lifecycle-managed)
|
|
9999
|
+
* - `type` -- component type from o.type (if set, e.g. 'card', 'tabs')
|
|
10000
|
+
* - `classes` -- first 5 CSS classes (string, space-separated)
|
|
10001
|
+
* - `handles` -- array of el.bw method names (if any)
|
|
10002
|
+
* - `state` -- copy of _bw_state (if any)
|
|
10003
|
+
* - `hasRender` -- true if _bw_render is set
|
|
10004
|
+
* - `hasSubs` -- true if element has pub/sub subscriptions
|
|
10005
|
+
* - `refs` -- copy of _bw_refs keys (if any)
|
|
10006
|
+
* - `children` -- array of child node trees (up to depth limit, max 50 per level)
|
|
10007
|
+
*
|
|
10008
|
+
* @param {string|Element} target - CSS selector, UUID, or DOM element
|
|
10009
|
+
* @param {number} [depth=3] - Maximum recursion depth (0 = target only, no children)
|
|
10010
|
+
* @returns {Object|null} Plain object tree, or null if element not found
|
|
9665
10011
|
* @category Component
|
|
9666
10012
|
* @example
|
|
9667
|
-
*
|
|
9668
|
-
* bw.inspect(
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
10013
|
+
* // Get full tree from #app, 3 levels deep (default):
|
|
10014
|
+
* var info = bw.inspect('#app');
|
|
10015
|
+
*
|
|
10016
|
+
* // Shallow inspection (just the element, no children):
|
|
10017
|
+
* var info = bw.inspect('#my-carousel', 0);
|
|
10018
|
+
* console.log(info.handles); // ['next', 'prev', 'goToSlide']
|
|
10019
|
+
* console.log(info.type); // 'carousel'
|
|
10020
|
+
*
|
|
10021
|
+
* // Deep inspection for debugging:
|
|
10022
|
+
* console.log(JSON.stringify(bw.inspect('#app', 5), null, 2));
|
|
10023
|
+
*/
|
|
10024
|
+
bw.inspect = function(target, depth) {
|
|
10025
|
+
var el = bw.el(target);
|
|
10026
|
+
if (!el && _is(target, 'string')) el = bw.$(target)[0];
|
|
10027
|
+
if (!el) return null;
|
|
10028
|
+
if (depth === undefined || depth === null) depth = 3;
|
|
10029
|
+
|
|
10030
|
+
function walk(node, d) {
|
|
10031
|
+
if (!node) return null;
|
|
10032
|
+
// Skip non-element nodes (text, comment, etc.)
|
|
10033
|
+
if (node.nodeType !== 1) return null;
|
|
10034
|
+
|
|
10035
|
+
var info = { tag: node.tagName ? node.tagName.toLowerCase() : '#text' };
|
|
10036
|
+
|
|
10037
|
+
// Identity
|
|
10038
|
+
if (node.id) info.id = node.id;
|
|
10039
|
+
var uuid = bw.getUUID(node);
|
|
10040
|
+
if (uuid) info.uuid = uuid;
|
|
10041
|
+
if (node._bw_type) info.type = node._bw_type;
|
|
10042
|
+
|
|
10043
|
+
// CSS classes (first 5 for readability)
|
|
10044
|
+
if (node.className && typeof node.className === 'string') {
|
|
10045
|
+
info.classes = node.className.split(' ').slice(0, 5).join(' ');
|
|
10046
|
+
}
|
|
10047
|
+
|
|
10048
|
+
// Bitwrench handle methods
|
|
10049
|
+
if (node.bw) {
|
|
10050
|
+
var handles = _keys(node.bw);
|
|
10051
|
+
if (handles.length > 0) info.handles = handles;
|
|
10052
|
+
}
|
|
10053
|
+
|
|
10054
|
+
// State
|
|
10055
|
+
if (node._bw_state) info.state = node._bw_state;
|
|
10056
|
+
if (node._bw_render) info.hasRender = true;
|
|
10057
|
+
if (node._bw_subs && node._bw_subs.length > 0) info.hasSubs = true;
|
|
10058
|
+
|
|
10059
|
+
// Refs
|
|
10060
|
+
if (node._bw_refs) info.refs = _keys(node._bw_refs);
|
|
10061
|
+
|
|
10062
|
+
// Children (recurse up to depth limit, max 50 children per level)
|
|
10063
|
+
if (d < depth && node.children && node.children.length > 0) {
|
|
10064
|
+
info.children = [];
|
|
10065
|
+
var max = Math.min(node.children.length, 50);
|
|
10066
|
+
for (var i = 0; i < max; i++) {
|
|
10067
|
+
var child = walk(node.children[i], d + 1);
|
|
10068
|
+
if (child) info.children.push(child);
|
|
10069
|
+
}
|
|
10070
|
+
if (node.children.length > 50) {
|
|
10071
|
+
info.children.push({ tag: '...', count: node.children.length - 50 });
|
|
10072
|
+
}
|
|
10073
|
+
}
|
|
10074
|
+
|
|
10075
|
+
return info;
|
|
10076
|
+
}
|
|
10077
|
+
|
|
10078
|
+
return walk(el, 0);
|
|
9680
10079
|
};
|
|
9681
10080
|
|
|
9682
10081
|
bw.compile = function() { throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
@@ -9898,37 +10297,49 @@ bw.clip = clip;
|
|
|
9898
10297
|
* so you can use `.map()`, `.filter()`, etc. directly. Accepts CSS selectors,
|
|
9899
10298
|
* single elements, NodeLists, or arrays.
|
|
9900
10299
|
*
|
|
10300
|
+
* With an optional second argument, applies content or a function to
|
|
10301
|
+
* every matched element (same apply rules as `bw.el()`):
|
|
10302
|
+
* - string/number: sets `el.textContent`
|
|
10303
|
+
* - function: calls `apply(el)` for each element
|
|
10304
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
10305
|
+
* - array: clears children, appends each item
|
|
10306
|
+
*
|
|
9901
10307
|
* @param {string|Element|Array} selector - CSS selector, element, or array
|
|
10308
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
9902
10309
|
* @returns {Array} Array of DOM elements
|
|
9903
10310
|
* @category DOM Selection
|
|
10311
|
+
* @see bw.el
|
|
9904
10312
|
* @example
|
|
9905
|
-
* bw.$('.card')
|
|
9906
|
-
* bw.$(
|
|
9907
|
-
* bw.$('.card'
|
|
10313
|
+
* bw.$('.card') // => [div.card, div.card, ...]
|
|
10314
|
+
* bw.$('.status', 'Online') // set text on all .status elements
|
|
10315
|
+
* bw.$('.card', function(el) { // apply function to each
|
|
10316
|
+
* el.style.opacity = '0.5';
|
|
10317
|
+
* })
|
|
9908
10318
|
*/
|
|
9909
10319
|
if (bw._isBrowser) {
|
|
9910
|
-
bw.$ = function(selector) {
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
if (_isA(selector))
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
if (
|
|
9921
|
-
|
|
10320
|
+
bw.$ = function(selector, apply) {
|
|
10321
|
+
var els;
|
|
10322
|
+
if (!selector) {
|
|
10323
|
+
els = [];
|
|
10324
|
+
} else if (_isA(selector)) {
|
|
10325
|
+
els = selector;
|
|
10326
|
+
} else if (selector.nodeType) {
|
|
10327
|
+
els = [selector];
|
|
10328
|
+
} else if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
10329
|
+
els = Array.from(selector);
|
|
10330
|
+
} else if (_is(selector, 'string')) {
|
|
10331
|
+
els = Array.from(document.querySelectorAll(selector));
|
|
10332
|
+
} else {
|
|
10333
|
+
els = [];
|
|
9922
10334
|
}
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
return Array.from(document.querySelectorAll(selector));
|
|
10335
|
+
|
|
10336
|
+
if (apply !== undefined) {
|
|
10337
|
+
for (var i = 0; i < els.length; i++) _applyTo(els[i], apply);
|
|
9927
10338
|
}
|
|
9928
|
-
|
|
9929
|
-
return
|
|
10339
|
+
|
|
10340
|
+
return els;
|
|
9930
10341
|
};
|
|
9931
|
-
|
|
10342
|
+
|
|
9932
10343
|
// Convenience single element selector
|
|
9933
10344
|
bw.$.one = function(selector) {
|
|
9934
10345
|
return bw.$(selector)[0] || null;
|
|
@@ -10082,7 +10493,8 @@ bw.applyStyles = function(styles, scope) {
|
|
|
10082
10493
|
*
|
|
10083
10494
|
* @param {Object} [config] - Style configuration (same as `makeStyles`)
|
|
10084
10495
|
* @param {string} [scope] - Scope selector (same as `applyStyles`)
|
|
10085
|
-
* @returns {
|
|
10496
|
+
* @returns {Object} The styles object (same as `makeStyles` return value:
|
|
10497
|
+
* `{css, alternateCss, palette, alternatePalette, rules, alternateRules, isLightPrimary}`)
|
|
10086
10498
|
* @category CSS & Styling
|
|
10087
10499
|
* @see bw.makeStyles
|
|
10088
10500
|
* @see bw.applyStyles
|
|
@@ -10100,9 +10512,27 @@ bw.loadStyles = function(config, scope) {
|
|
|
10100
10512
|
bw.injectCSS(structuralCSS, { id: 'bw_structural', append: false });
|
|
10101
10513
|
}
|
|
10102
10514
|
}
|
|
10103
|
-
|
|
10515
|
+
var styles = bw.makeStyles(config);
|
|
10516
|
+
bw.applyStyles(styles, scope);
|
|
10517
|
+
return styles;
|
|
10104
10518
|
};
|
|
10105
10519
|
|
|
10520
|
+
/**
|
|
10521
|
+
* Prefix every selector in a rules object with a scope selector.
|
|
10522
|
+
* Useful for wrapping site-level CSS under `.bw_theme_alt` for dark mode.
|
|
10523
|
+
*
|
|
10524
|
+
* @param {Object} rules - CSS rules object (selector -> declarations)
|
|
10525
|
+
* @param {string} prefix - Scope prefix (e.g. '.bw_theme_alt')
|
|
10526
|
+
* @returns {Object} New rules object with scoped selectors
|
|
10527
|
+
* @category CSS & Styling
|
|
10528
|
+
* @see bw.applyStyles
|
|
10529
|
+
* @see bw.css
|
|
10530
|
+
* @example
|
|
10531
|
+
* var altRules = bw.scopeRulesUnder(myRules, '.bw_theme_alt');
|
|
10532
|
+
* bw.injectCSS(bw.css(altRules));
|
|
10533
|
+
*/
|
|
10534
|
+
bw.scopeRulesUnder = scopeRulesUnder;
|
|
10535
|
+
|
|
10106
10536
|
/**
|
|
10107
10537
|
* Inject the CSS reset (box-sizing, html/body font, reduced-motion).
|
|
10108
10538
|
* Idempotent — if already injected, returns the existing `<style>` element.
|
|
@@ -10122,42 +10552,48 @@ bw.loadReset = function() {
|
|
|
10122
10552
|
};
|
|
10123
10553
|
|
|
10124
10554
|
/**
|
|
10125
|
-
* Toggle between primary and alternate palettes.
|
|
10555
|
+
* Toggle between primary and alternate theme palettes.
|
|
10126
10556
|
*
|
|
10127
|
-
* Adds/removes the `bw_theme_alt` class on the scoping element.
|
|
10557
|
+
* Adds/removes the `bw_theme_alt` class on the scoping element(s).
|
|
10128
10558
|
* Without a scope, toggles on `<html>` (global).
|
|
10129
|
-
* With a scope, toggles on
|
|
10559
|
+
* With a scope, toggles on ALL matching elements.
|
|
10130
10560
|
*
|
|
10131
|
-
* @param {string} [scope] -
|
|
10132
|
-
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
10561
|
+
* @param {string|Element} [scope] - Selector or element. Omit for global.
|
|
10562
|
+
* @returns {string} Active mode after toggle: 'primary' or 'alternate' (based on first element)
|
|
10133
10563
|
* @category CSS & Styling
|
|
10134
10564
|
* @see bw.applyStyles
|
|
10135
10565
|
* @see bw.clearStyles
|
|
10136
10566
|
* @example
|
|
10137
|
-
* bw.
|
|
10138
|
-
* bw.
|
|
10567
|
+
* bw.toggleThemeMode(); // global toggle on <html>
|
|
10568
|
+
* bw.toggleThemeMode('#my-dashboard'); // scoped toggle
|
|
10569
|
+
* bw.toggleThemeMode('.panel'); // toggle on ALL .panel elements
|
|
10139
10570
|
*/
|
|
10140
|
-
bw.
|
|
10571
|
+
bw.toggleThemeMode = function(scope) {
|
|
10141
10572
|
if (!bw._isBrowser) return 'primary';
|
|
10142
|
-
var
|
|
10573
|
+
var els;
|
|
10143
10574
|
if (scope) {
|
|
10144
|
-
|
|
10145
|
-
target = els[0];
|
|
10575
|
+
els = bw.$(scope);
|
|
10146
10576
|
} else {
|
|
10147
|
-
|
|
10577
|
+
els = [document.documentElement];
|
|
10148
10578
|
}
|
|
10149
|
-
if (!
|
|
10579
|
+
if (!els.length) return 'primary';
|
|
10150
10580
|
|
|
10151
|
-
var
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10581
|
+
var mode;
|
|
10582
|
+
for (var i = 0; i < els.length; i++) {
|
|
10583
|
+
var hasAlt = els[i].classList.contains('bw_theme_alt');
|
|
10584
|
+
if (hasAlt) {
|
|
10585
|
+
els[i].classList.remove('bw_theme_alt');
|
|
10586
|
+
} else {
|
|
10587
|
+
els[i].classList.add('bw_theme_alt');
|
|
10588
|
+
}
|
|
10589
|
+
if (i === 0) mode = hasAlt ? 'primary' : 'alternate';
|
|
10158
10590
|
}
|
|
10591
|
+
return mode;
|
|
10159
10592
|
};
|
|
10160
10593
|
|
|
10594
|
+
// Alias — kept for one release cycle. Use bw.toggleThemeMode() instead.
|
|
10595
|
+
bw.toggleStyles = bw.toggleThemeMode;
|
|
10596
|
+
|
|
10161
10597
|
/**
|
|
10162
10598
|
* Remove injected styles for a given scope.
|
|
10163
10599
|
*
|
|
@@ -11197,6 +11633,57 @@ Object.entries(components).forEach(([name, fn]) => {
|
|
|
11197
11633
|
}
|
|
11198
11634
|
});
|
|
11199
11635
|
|
|
11636
|
+
/**
|
|
11637
|
+
* Query the BCCL component registry. Returns metadata about registered
|
|
11638
|
+
* component types -- their names and factory function names. Useful for
|
|
11639
|
+
* tooling, introspection, documentation generators, and auto-complete
|
|
11640
|
+
* systems (including MCP/AG-UI tool discovery).
|
|
11641
|
+
*
|
|
11642
|
+
* With no arguments, returns an array of all registered component types.
|
|
11643
|
+
* With a type name, returns metadata for that single type (or null if
|
|
11644
|
+
* the type is not registered).
|
|
11645
|
+
*
|
|
11646
|
+
* @param {string} [type] - Optional component type name to look up
|
|
11647
|
+
* @returns {Array<Object>|Object|null} Array of {type, factory} objects,
|
|
11648
|
+
* a single {type, factory} object, or null if the type is not found
|
|
11649
|
+
* @category Component
|
|
11650
|
+
* @see bw.make
|
|
11651
|
+
* @see bw.BCCL
|
|
11652
|
+
* @example
|
|
11653
|
+
* // List all available component types:
|
|
11654
|
+
* bw.catalog();
|
|
11655
|
+
* // => [{ type: 'card', factory: 'makeCard' },
|
|
11656
|
+
* // { type: 'button', factory: 'makeButton' }, ...]
|
|
11657
|
+
*
|
|
11658
|
+
* // Look up a specific type:
|
|
11659
|
+
* bw.catalog('accordion');
|
|
11660
|
+
* // => { type: 'accordion', factory: 'makeAccordion' }
|
|
11661
|
+
*
|
|
11662
|
+
* // Check if a type exists:
|
|
11663
|
+
* if (bw.catalog('chart')) { ... }
|
|
11664
|
+
*
|
|
11665
|
+
* // Get just the type names:
|
|
11666
|
+
* bw.catalog().map(function(c) { return c.type; });
|
|
11667
|
+
* // => ['card', 'button', 'container', 'row', ...]
|
|
11668
|
+
*/
|
|
11669
|
+
bw.catalog = function(type) {
|
|
11670
|
+
if (type) {
|
|
11671
|
+
var def = bw.BCCL[type];
|
|
11672
|
+
if (!def) return null;
|
|
11673
|
+
return {
|
|
11674
|
+
type: type,
|
|
11675
|
+
factory: def.make.name || ('make' + type.charAt(0).toUpperCase() + type.slice(1))
|
|
11676
|
+
};
|
|
11677
|
+
}
|
|
11678
|
+
return Object.keys(bw.BCCL).map(function(k) {
|
|
11679
|
+
var def = bw.BCCL[k];
|
|
11680
|
+
return {
|
|
11681
|
+
type: k,
|
|
11682
|
+
factory: def.make.name || ('make' + k.charAt(0).toUpperCase() + k.slice(1))
|
|
11683
|
+
};
|
|
11684
|
+
});
|
|
11685
|
+
};
|
|
11686
|
+
|
|
11200
11687
|
// Also attach to global in browsers
|
|
11201
11688
|
if (bw._isBrowser && typeof window !== 'undefined') {
|
|
11202
11689
|
window.bw = bw;
|