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