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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bitwrench-lean v2.0.
|
|
1
|
+
/*! bitwrench-lean v2.0.30 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
(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
|
}
|
|
@@ -3955,7 +3955,6 @@
|
|
|
3955
3955
|
// Console aliases use thin wrappers (not direct references) so that test
|
|
3956
3956
|
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
3957
3957
|
var _cw = function() { console.warn.apply(console, arguments); };
|
|
3958
|
-
var _cl = function() { console.log.apply(console, arguments); };
|
|
3959
3958
|
var _ce = function() { console.error.apply(console, arguments); };
|
|
3960
3959
|
|
|
3961
3960
|
/**
|
|
@@ -4092,61 +4091,105 @@
|
|
|
4092
4091
|
};
|
|
4093
4092
|
|
|
4094
4093
|
/**
|
|
4095
|
-
* Look up a DOM element by ID
|
|
4096
|
-
*
|
|
4097
|
-
*
|
|
4098
|
-
*
|
|
4099
|
-
*
|
|
4100
|
-
*
|
|
4101
|
-
*
|
|
4102
|
-
*
|
|
4103
|
-
*
|
|
4104
|
-
*
|
|
4105
|
-
*
|
|
4106
|
-
*
|
|
4107
|
-
*
|
|
4108
|
-
*
|
|
4109
|
-
*
|
|
4094
|
+
* Look up a single DOM element by ID, CSS selector, UUID, or element ref.
|
|
4095
|
+
* Optionally apply content or a function to the resolved element.
|
|
4096
|
+
*
|
|
4097
|
+
* Resolution order for string targets:
|
|
4098
|
+
* 1. Check `bw._nodeMap[id]` cache (O(1), stale entries auto-pruned)
|
|
4099
|
+
* 2. `document.getElementById(id)`
|
|
4100
|
+
* 3. `document.querySelector(id)` for selectors starting with # or .
|
|
4101
|
+
* 4. Class-based lookup for `bw_uuid_*` tokens
|
|
4102
|
+
*
|
|
4103
|
+
* With one argument, returns the element (or null). With two arguments,
|
|
4104
|
+
* applies the second argument to the element and returns the element:
|
|
4105
|
+
* - string/number: sets `el.textContent`
|
|
4106
|
+
* - function: calls `apply(el)`, returns el
|
|
4107
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
4108
|
+
* - array: clears children, appends each item (string -> text node, TACO -> element)
|
|
4109
|
+
*
|
|
4110
|
+
* @param {string|Element} target - Element ref, ID, CSS selector, or bw_uuid_* class
|
|
4111
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
4110
4112
|
* @returns {Element|null} The DOM element, or null if not found
|
|
4111
|
-
* @category
|
|
4113
|
+
* @category DOM Selection
|
|
4114
|
+
* @see bw.$
|
|
4115
|
+
* @see bw.patch
|
|
4116
|
+
* @example
|
|
4117
|
+
* bw.el('#title') // lookup
|
|
4118
|
+
* bw.el('#title', 'Hello') // set text content
|
|
4119
|
+
* bw.el('#app', { t: 'h1', c: 'Hi' }) // mount TACO
|
|
4120
|
+
* bw.el('.card', function(el) { // apply function
|
|
4121
|
+
* el.style.opacity = '0.5';
|
|
4122
|
+
* })
|
|
4112
4123
|
*/
|
|
4113
|
-
bw.
|
|
4114
|
-
//
|
|
4115
|
-
|
|
4116
|
-
if (!
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
if (cached
|
|
4124
|
-
|
|
4124
|
+
bw.el = function(target, apply) {
|
|
4125
|
+
// Resolve target to element
|
|
4126
|
+
var el;
|
|
4127
|
+
if (!_is(target, 'string')) {
|
|
4128
|
+
el = target || null;
|
|
4129
|
+
} else if (!target || !bw._isBrowser) {
|
|
4130
|
+
el = null;
|
|
4131
|
+
} else {
|
|
4132
|
+
// 1. Check cache
|
|
4133
|
+
var cached = bw._nodeMap[target];
|
|
4134
|
+
if (cached) {
|
|
4135
|
+
if (cached.parentNode !== null) {
|
|
4136
|
+
el = cached;
|
|
4137
|
+
} else {
|
|
4138
|
+
delete bw._nodeMap[target];
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
if (!el) {
|
|
4142
|
+
// 2. getElementById
|
|
4143
|
+
el = document.getElementById(target);
|
|
4144
|
+
// 3. querySelector for CSS selectors
|
|
4145
|
+
if (!el && (target.charAt(0) === '#' || target.charAt(0) === '.')) {
|
|
4146
|
+
el = document.querySelector(target);
|
|
4147
|
+
}
|
|
4148
|
+
// 4. bw_uuid_* class lookup
|
|
4149
|
+
if (!el && target.indexOf('bw_uuid_') === 0) {
|
|
4150
|
+
el = document.querySelector('.' + target);
|
|
4151
|
+
}
|
|
4152
|
+
// 5. Cache result
|
|
4153
|
+
if (el) bw._nodeMap[target] = el;
|
|
4125
4154
|
}
|
|
4126
|
-
// Stale — remove and fall through
|
|
4127
|
-
delete bw._nodeMap[id];
|
|
4128
4155
|
}
|
|
4129
4156
|
|
|
4130
|
-
//
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
// 3. Try querySelector for CSS selectors (starts with # or .)
|
|
4134
|
-
if (!el && (id.charAt(0) === '#' || id.charAt(0) === '.')) {
|
|
4135
|
-
el = document.querySelector(id);
|
|
4136
|
-
}
|
|
4157
|
+
// Apply (if provided and element found)
|
|
4158
|
+
if (el && apply !== undefined) _applyTo(el, apply);
|
|
4137
4159
|
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
el = document.querySelector('.' + id);
|
|
4141
|
-
}
|
|
4160
|
+
return el;
|
|
4161
|
+
};
|
|
4142
4162
|
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4163
|
+
/**
|
|
4164
|
+
* Internal: apply content or function to a DOM element.
|
|
4165
|
+
* Shared by bw.el() and bw.$().
|
|
4166
|
+
* @private
|
|
4167
|
+
*/
|
|
4168
|
+
function _applyTo(el, apply) {
|
|
4169
|
+
if (_is(apply, 'function')) {
|
|
4170
|
+
apply(el);
|
|
4171
|
+
} else if (_isA(apply)) {
|
|
4172
|
+
el.innerHTML = '';
|
|
4173
|
+
apply.forEach(function(item) {
|
|
4174
|
+
if (item != null) {
|
|
4175
|
+
if (_is(item, 'object') && item.t) {
|
|
4176
|
+
el.appendChild(bw.createDOM(item));
|
|
4177
|
+
} else {
|
|
4178
|
+
el.appendChild(document.createTextNode(String(item)));
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
});
|
|
4182
|
+
} else if (_is(apply, 'object') && apply !== null && apply.t) {
|
|
4183
|
+
el.innerHTML = '';
|
|
4184
|
+
el.appendChild(bw.createDOM(apply));
|
|
4185
|
+
} else {
|
|
4186
|
+
el.textContent = String(apply);
|
|
4146
4187
|
}
|
|
4188
|
+
}
|
|
4147
4189
|
|
|
4148
|
-
|
|
4149
|
-
|
|
4190
|
+
// Internal alias — kept for one release cycle (v2.0.26).
|
|
4191
|
+
// Will be removed in v2.0.27. Use bw.el() instead.
|
|
4192
|
+
bw._el = bw.el;
|
|
4150
4193
|
|
|
4151
4194
|
/**
|
|
4152
4195
|
* Register a DOM element in the node cache under one or more keys.
|
|
@@ -4210,6 +4253,12 @@
|
|
|
4210
4253
|
*/
|
|
4211
4254
|
var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
|
|
4212
4255
|
|
|
4256
|
+
/**
|
|
4257
|
+
* SVG namespace URI for createElementNS.
|
|
4258
|
+
* @private
|
|
4259
|
+
*/
|
|
4260
|
+
var _SVG_NS = 'http://www.w3.org/2000/svg';
|
|
4261
|
+
|
|
4213
4262
|
/**
|
|
4214
4263
|
* Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
|
|
4215
4264
|
*
|
|
@@ -4264,9 +4313,10 @@
|
|
|
4264
4313
|
if (!tacoOrElement) return null;
|
|
4265
4314
|
|
|
4266
4315
|
var classStr;
|
|
4267
|
-
// DOM element: check className
|
|
4316
|
+
// DOM element: check className (SVG elements use getAttribute for string value)
|
|
4268
4317
|
if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
|
|
4269
|
-
classStr = tacoOrElement.className
|
|
4318
|
+
classStr = typeof tacoOrElement.className === 'string'
|
|
4319
|
+
? tacoOrElement.className : (tacoOrElement.getAttribute('class') || '');
|
|
4270
4320
|
}
|
|
4271
4321
|
// TACO object: check a.class
|
|
4272
4322
|
else if (tacoOrElement.a && _is(tacoOrElement.a.class, 'string')) {
|
|
@@ -4535,7 +4585,7 @@
|
|
|
4535
4585
|
var fnCounterBefore = bw._fnIDCounter;
|
|
4536
4586
|
|
|
4537
4587
|
// Render body content
|
|
4538
|
-
var bodyHTML
|
|
4588
|
+
var bodyHTML;
|
|
4539
4589
|
if (_is(body, 'string')) {
|
|
4540
4590
|
bodyHTML = body;
|
|
4541
4591
|
} else {
|
|
@@ -4706,9 +4756,11 @@
|
|
|
4706
4756
|
}
|
|
4707
4757
|
|
|
4708
4758
|
const { t: tag, a: attrs = {}, c: content, o: opts = {} } = taco;
|
|
4709
|
-
|
|
4710
|
-
//
|
|
4711
|
-
|
|
4759
|
+
|
|
4760
|
+
// SVG namespace: detect SVG context and thread through children.
|
|
4761
|
+
// {t:'svg'} starts SVG context; foreignObject children revert to HTML.
|
|
4762
|
+
var svgCtx = options._svgCtx || (tag === 'svg');
|
|
4763
|
+
var el = svgCtx ? document.createElementNS(_SVG_NS, tag) : document.createElement(tag);
|
|
4712
4764
|
|
|
4713
4765
|
// Set attributes
|
|
4714
4766
|
for (const [key, value] of Object.entries(attrs)) {
|
|
@@ -4719,9 +4771,11 @@
|
|
|
4719
4771
|
Object.assign(el.style, value);
|
|
4720
4772
|
} else if (key === 'class') {
|
|
4721
4773
|
// Handle class as array or string
|
|
4774
|
+
// SVG elements use SVGAnimatedString for className, so use setAttribute
|
|
4722
4775
|
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
4723
4776
|
if (classStr) {
|
|
4724
|
-
el.
|
|
4777
|
+
if (svgCtx) el.setAttribute('class', classStr);
|
|
4778
|
+
else el.className = classStr;
|
|
4725
4779
|
}
|
|
4726
4780
|
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
4727
4781
|
// Event handlers
|
|
@@ -4742,11 +4796,17 @@
|
|
|
4742
4796
|
// Add children, building _bw_refs for fast parent→child access.
|
|
4743
4797
|
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
4744
4798
|
// so o.render functions can access them without any DOM lookup.
|
|
4799
|
+
// SVG: foreignObject children revert to HTML namespace; otherwise inherit.
|
|
4800
|
+
var childOpts = options;
|
|
4801
|
+
var childSvgCtx = svgCtx && tag !== 'foreignObject';
|
|
4802
|
+
if (childSvgCtx !== (options._svgCtx || false)) {
|
|
4803
|
+
childOpts = Object.assign({}, options, {_svgCtx: childSvgCtx || undefined});
|
|
4804
|
+
}
|
|
4745
4805
|
if (content != null) {
|
|
4746
4806
|
if (_isA(content)) {
|
|
4747
4807
|
content.forEach(child => {
|
|
4748
4808
|
if (child != null) {
|
|
4749
|
-
var childEl = bw.createDOM(child,
|
|
4809
|
+
var childEl = bw.createDOM(child, childOpts);
|
|
4750
4810
|
el.appendChild(childEl);
|
|
4751
4811
|
// Build local refs for addressable children
|
|
4752
4812
|
var childRefId = (child && child.a) ? (child.a.id || bw.getUUID(child)) : null;
|
|
@@ -4769,7 +4829,7 @@
|
|
|
4769
4829
|
// Raw HTML content — inject via innerHTML
|
|
4770
4830
|
el.innerHTML = content.v;
|
|
4771
4831
|
} else if (_is(content, 'object') && content.t) {
|
|
4772
|
-
var childEl = bw.createDOM(content,
|
|
4832
|
+
var childEl = bw.createDOM(content, childOpts);
|
|
4773
4833
|
el.appendChild(childEl);
|
|
4774
4834
|
var childRefId = content.a ? (content.a.id || bw.getUUID(content)) : null;
|
|
4775
4835
|
if (childRefId) {
|
|
@@ -4795,13 +4855,21 @@
|
|
|
4795
4855
|
}
|
|
4796
4856
|
|
|
4797
4857
|
// Register UUID class in node cache (bw_uuid_* tokens in class string)
|
|
4798
|
-
|
|
4799
|
-
|
|
4858
|
+
// SVG elements have SVGAnimatedString for className; use getAttribute instead
|
|
4859
|
+
var clsStr = svgCtx ? (el.getAttribute('class') || '') : el.className;
|
|
4860
|
+
if (clsStr) {
|
|
4861
|
+
var uuidMatch = clsStr.match(_UUID_RE);
|
|
4800
4862
|
if (uuidMatch) {
|
|
4801
4863
|
bw._nodeMap[uuidMatch[0]] = el;
|
|
4802
4864
|
}
|
|
4803
4865
|
}
|
|
4804
4866
|
|
|
4867
|
+
// Store component type metadata (e.g., 'card', 'tabs') for introspection.
|
|
4868
|
+
// BCCL factories set o.type; custom components can too.
|
|
4869
|
+
if (opts.type) {
|
|
4870
|
+
el._bw_type = opts.type;
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4805
4873
|
// Handle lifecycle hooks and state
|
|
4806
4874
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
4807
4875
|
// Ensure element has a UUID class for identity
|
|
@@ -4831,11 +4899,13 @@
|
|
|
4831
4899
|
|
|
4832
4900
|
if (mountFn) {
|
|
4833
4901
|
if (document.body.contains(el)) {
|
|
4834
|
-
mountFn(el, el._bw_state || {});
|
|
4902
|
+
try { mountFn(el, el._bw_state || {}); }
|
|
4903
|
+
catch (e) { _cw('o.mounted error: ' + e.message); }
|
|
4835
4904
|
} else {
|
|
4836
4905
|
requestAnimationFrame(() => {
|
|
4837
4906
|
if (document.body.contains(el)) {
|
|
4838
|
-
mountFn(el, el._bw_state || {});
|
|
4907
|
+
try { mountFn(el, el._bw_state || {}); }
|
|
4908
|
+
catch (e) { _cw('o.mounted error: ' + e.message); }
|
|
4839
4909
|
}
|
|
4840
4910
|
});
|
|
4841
4911
|
}
|
|
@@ -4844,7 +4914,8 @@
|
|
|
4844
4914
|
// Store unmount callback keyed by UUID class
|
|
4845
4915
|
if (opts.unmount) {
|
|
4846
4916
|
bw._unmountCallbacks.set(uuid, () => {
|
|
4847
|
-
opts.unmount(el, el._bw_state || {});
|
|
4917
|
+
try { opts.unmount(el, el._bw_state || {}); }
|
|
4918
|
+
catch (e) { _cw('o.unmount error: ' + e.message); }
|
|
4848
4919
|
});
|
|
4849
4920
|
}
|
|
4850
4921
|
}
|
|
@@ -4863,24 +4934,25 @@
|
|
|
4863
4934
|
}
|
|
4864
4935
|
|
|
4865
4936
|
// Slot declarations: auto-generate setX/getX pairs
|
|
4937
|
+
// The target element is cached at creation time to avoid repeated
|
|
4938
|
+
// querySelector calls on every get/set invocation.
|
|
4866
4939
|
if (opts.slots) {
|
|
4867
4940
|
for (var sk in opts.slots) {
|
|
4868
4941
|
if (_hop.call(opts.slots, sk)) {
|
|
4869
4942
|
(function(name, selector) {
|
|
4943
|
+
var target = el.querySelector(selector);
|
|
4870
4944
|
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
4871
4945
|
el.bw['set' + cap] = function(value) {
|
|
4872
|
-
|
|
4873
|
-
if (!t) return;
|
|
4946
|
+
if (!target) return;
|
|
4874
4947
|
if (value != null && typeof value === 'object' && value.t) {
|
|
4875
|
-
|
|
4876
|
-
|
|
4948
|
+
target.innerHTML = '';
|
|
4949
|
+
target.appendChild(bw.createDOM(value));
|
|
4877
4950
|
} else {
|
|
4878
|
-
|
|
4951
|
+
target.textContent = (value != null) ? String(value) : '';
|
|
4879
4952
|
}
|
|
4880
4953
|
};
|
|
4881
4954
|
el.bw['get' + cap] = function() {
|
|
4882
|
-
|
|
4883
|
-
return t ? t.textContent : '';
|
|
4955
|
+
return target ? target.textContent : '';
|
|
4884
4956
|
};
|
|
4885
4957
|
})(sk, opts.slots[sk]);
|
|
4886
4958
|
}
|
|
@@ -4921,7 +4993,7 @@
|
|
|
4921
4993
|
}
|
|
4922
4994
|
|
|
4923
4995
|
// Get target element (use cache-backed lookup)
|
|
4924
|
-
const targetEl = bw.
|
|
4996
|
+
const targetEl = bw.el(target);
|
|
4925
4997
|
|
|
4926
4998
|
if (!targetEl) {
|
|
4927
4999
|
_ce('bw.DOM: Target element not found:', target);
|
|
@@ -5024,7 +5096,8 @@
|
|
|
5024
5096
|
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
5025
5097
|
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
5026
5098
|
uuidEls.forEach(function(uel) {
|
|
5027
|
-
var
|
|
5099
|
+
var uc = typeof uel.className === 'string' ? uel.className : (uel.getAttribute('class') || '');
|
|
5100
|
+
var m = uc && uc.match(_UUID_RE);
|
|
5028
5101
|
if (m) delete bw._nodeMap[m[0]];
|
|
5029
5102
|
});
|
|
5030
5103
|
|
|
@@ -5110,9 +5183,10 @@
|
|
|
5110
5183
|
* bw.update(el); // re-renders, emits bw:statechange
|
|
5111
5184
|
*/
|
|
5112
5185
|
bw.update = function(target) {
|
|
5113
|
-
var el = bw.
|
|
5186
|
+
var el = bw.el(target);
|
|
5114
5187
|
if (el && el._bw_render) {
|
|
5115
|
-
el._bw_render(el, el._bw_state || {});
|
|
5188
|
+
try { el._bw_render(el, el._bw_state || {}); }
|
|
5189
|
+
catch (e) { _cw('o.render error: ' + e.message); }
|
|
5116
5190
|
bw.emit(el, 'statechange', el._bw_state);
|
|
5117
5191
|
}
|
|
5118
5192
|
return el || null;
|
|
@@ -5139,7 +5213,7 @@
|
|
|
5139
5213
|
* bw.patch('info', { t: 'em', c: 'new' }); // replace children with TACO
|
|
5140
5214
|
*/
|
|
5141
5215
|
bw.patch = function(id, content, attr) {
|
|
5142
|
-
var el = bw.
|
|
5216
|
+
var el = bw.el(id);
|
|
5143
5217
|
if (!el) return null;
|
|
5144
5218
|
|
|
5145
5219
|
if (attr) {
|
|
@@ -5211,7 +5285,7 @@
|
|
|
5211
5285
|
* // Dispatches CustomEvent 'bw:statechange' on the element
|
|
5212
5286
|
*/
|
|
5213
5287
|
bw.emit = function(target, eventName, detail) {
|
|
5214
|
-
var el = bw.
|
|
5288
|
+
var el = bw.el(target);
|
|
5215
5289
|
if (el) {
|
|
5216
5290
|
el.dispatchEvent(new CustomEvent('bw:' + eventName, {
|
|
5217
5291
|
bubbles: true,
|
|
@@ -5240,7 +5314,7 @@
|
|
|
5240
5314
|
* });
|
|
5241
5315
|
*/
|
|
5242
5316
|
bw.on = function(target, eventName, handler) {
|
|
5243
|
-
var el = bw.
|
|
5317
|
+
var el = bw.el(target);
|
|
5244
5318
|
if (el) {
|
|
5245
5319
|
el.addEventListener('bw:' + eventName, function(e) {
|
|
5246
5320
|
handler(e.detail, e);
|
|
@@ -5267,23 +5341,38 @@
|
|
|
5267
5341
|
*
|
|
5268
5342
|
* @param {string} topic - Topic name (plain string, no prefix)
|
|
5269
5343
|
* @param {*} [detail] - Data to pass to subscribers
|
|
5270
|
-
* @returns {number} Count of successfully called subscribers
|
|
5344
|
+
* @returns {number} Count of successfully called subscribers (including wildcard matches)
|
|
5271
5345
|
* @category Pub/Sub
|
|
5272
5346
|
* @see bw.sub
|
|
5273
5347
|
* @example
|
|
5274
5348
|
* bw.pub('score:updated', { player: 'X', score: 10 });
|
|
5349
|
+
* // Wildcard subscribers matching 'score:*' will also fire
|
|
5275
5350
|
*/
|
|
5276
5351
|
bw.pub = function(topic, detail) {
|
|
5277
|
-
var subs = bw._topics[topic];
|
|
5278
|
-
if (!subs || subs.length === 0) return 0;
|
|
5279
|
-
var snapshot = subs.slice(); // safe against unsub during iteration
|
|
5280
5352
|
var called = 0;
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5353
|
+
// Exact-match subscribers
|
|
5354
|
+
var subs = bw._topics[topic];
|
|
5355
|
+
if (subs && subs.length > 0) {
|
|
5356
|
+
var snapshot = subs.slice();
|
|
5357
|
+
for (var i = 0; i < snapshot.length; i++) {
|
|
5358
|
+
try { snapshot[i].handler(detail, topic); called++; }
|
|
5359
|
+
catch (err) { _cw('bw.pub: subscriber error on topic "' + topic + '":', err); }
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
// Wildcard subscribers -- patterns ending with '*'
|
|
5363
|
+
var keys = Object.keys(bw._topics);
|
|
5364
|
+
for (var k = 0; k < keys.length; k++) {
|
|
5365
|
+
var pat = keys[k];
|
|
5366
|
+
if (pat.charAt(pat.length - 1) !== '*') continue;
|
|
5367
|
+
var prefix = pat.slice(0, -1); // strip trailing '*'
|
|
5368
|
+
if (topic.length >= prefix.length && topic.substring(0, prefix.length) === prefix && topic !== pat) {
|
|
5369
|
+
var wsubs = bw._topics[pat];
|
|
5370
|
+
if (!wsubs) continue;
|
|
5371
|
+
var wsnap = wsubs.slice();
|
|
5372
|
+
for (var w = 0; w < wsnap.length; w++) {
|
|
5373
|
+
try { wsnap[w].handler(detail, topic); called++; }
|
|
5374
|
+
catch (err) { _cw('bw.pub: wildcard subscriber error on "' + pat + '" for topic "' + topic + '":', err); }
|
|
5375
|
+
}
|
|
5287
5376
|
}
|
|
5288
5377
|
}
|
|
5289
5378
|
return called;
|
|
@@ -5292,12 +5381,17 @@
|
|
|
5292
5381
|
/**
|
|
5293
5382
|
* Subscribe to a topic. Returns an unsub() function.
|
|
5294
5383
|
*
|
|
5295
|
-
*
|
|
5384
|
+
* Supports wildcard patterns: a topic ending in `*` matches any published
|
|
5385
|
+
* topic that starts with the prefix before the `*`. For example,
|
|
5386
|
+
* `'agui:*'` matches `'agui:ready'`, `'agui:error'`, etc. The handler
|
|
5387
|
+
* receives `(detail, topic)` so it can distinguish which topic fired.
|
|
5388
|
+
*
|
|
5389
|
+
* Optional third argument ties the subscription to a DOM element's lifecycle --
|
|
5296
5390
|
* when `bw.cleanup()` is called on that element, the subscription is automatically
|
|
5297
5391
|
* removed, preventing memory leaks.
|
|
5298
5392
|
*
|
|
5299
|
-
* @param {string} topic - Topic name
|
|
5300
|
-
* @param {Function} handler - Called with (detail) on each publish
|
|
5393
|
+
* @param {string} topic - Topic name, or wildcard pattern ending in '*'
|
|
5394
|
+
* @param {Function} handler - Called with (detail, topic) on each publish
|
|
5301
5395
|
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
5302
5396
|
* @returns {Function} Call to unsubscribe
|
|
5303
5397
|
* @category Pub/Sub
|
|
@@ -5308,6 +5402,11 @@
|
|
|
5308
5402
|
* console.log(detail.player, 'scored', detail.score);
|
|
5309
5403
|
* });
|
|
5310
5404
|
* // Later: unsub() to stop listening
|
|
5405
|
+
*
|
|
5406
|
+
* // Wildcard: listen to all 'agui:' topics
|
|
5407
|
+
* bw.sub('agui:*', function(detail, topic) {
|
|
5408
|
+
* console.log('Got', topic, detail);
|
|
5409
|
+
* });
|
|
5311
5410
|
*/
|
|
5312
5411
|
bw.sub = function(topic, handler, el) {
|
|
5313
5412
|
var id = ++bw._subIdCounter;
|
|
@@ -5359,6 +5458,37 @@
|
|
|
5359
5458
|
return removed;
|
|
5360
5459
|
};
|
|
5361
5460
|
|
|
5461
|
+
/**
|
|
5462
|
+
* Subscribe to a topic for a single event only. The subscription is
|
|
5463
|
+
* automatically removed after the first publish. Equivalent to manually
|
|
5464
|
+
* calling unsub() inside a bw.sub() handler, but avoids the common bug
|
|
5465
|
+
* of forgetting to unsubscribe.
|
|
5466
|
+
*
|
|
5467
|
+
* @param {string} topic - Topic name
|
|
5468
|
+
* @param {Function} handler - Called once with (detail) on the next publish
|
|
5469
|
+
* @param {Element} [el] - Optional DOM element to tie lifecycle to
|
|
5470
|
+
* @returns {Function} Call to cancel the subscription before it fires
|
|
5471
|
+
* @category Pub/Sub
|
|
5472
|
+
* @see bw.sub
|
|
5473
|
+
* @see bw.pub
|
|
5474
|
+
* @example
|
|
5475
|
+
* bw.once('data:loaded', function(detail) {
|
|
5476
|
+
* console.log('Received:', detail);
|
|
5477
|
+
* // No need to unsubscribe -- already done automatically
|
|
5478
|
+
* });
|
|
5479
|
+
*
|
|
5480
|
+
* // Cancel before it fires:
|
|
5481
|
+
* var cancel = bw.once('timeout', handler);
|
|
5482
|
+
* cancel(); // handler will never be called
|
|
5483
|
+
*/
|
|
5484
|
+
bw.once = function(topic, handler, el) {
|
|
5485
|
+
var unsub = bw.sub(topic, function(detail) {
|
|
5486
|
+
unsub();
|
|
5487
|
+
handler(detail);
|
|
5488
|
+
}, el);
|
|
5489
|
+
return unsub;
|
|
5490
|
+
};
|
|
5491
|
+
|
|
5362
5492
|
// ===================================================================================
|
|
5363
5493
|
// Function Registry (revived from v1 for string dispatch contexts)
|
|
5364
5494
|
// ===================================================================================
|
|
@@ -5599,7 +5729,7 @@
|
|
|
5599
5729
|
* };
|
|
5600
5730
|
*/
|
|
5601
5731
|
bw.message = function(target, action, data) {
|
|
5602
|
-
var el = bw.
|
|
5732
|
+
var el = bw.el(target);
|
|
5603
5733
|
if (!el) el = bw.$('.' + target)[0];
|
|
5604
5734
|
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
5605
5735
|
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
@@ -5609,6 +5739,207 @@
|
|
|
5609
5739
|
return true;
|
|
5610
5740
|
};
|
|
5611
5741
|
|
|
5742
|
+
/**
|
|
5743
|
+
* Collect form data from all input, select, and textarea elements within a
|
|
5744
|
+
* container. Each element's `name` attribute (or `id` if no name) becomes a
|
|
5745
|
+
* key in the returned object. This provides a lightweight alternative to the
|
|
5746
|
+
* browser FormData API that returns a plain object suitable for JSON
|
|
5747
|
+
* serialization or bw.pub().
|
|
5748
|
+
*
|
|
5749
|
+
* Handles all standard HTML form controls:
|
|
5750
|
+
* - text/number/email/etc inputs: string value
|
|
5751
|
+
* - checkboxes: boolean (true/false)
|
|
5752
|
+
* - radio buttons: string value of the checked radio (unchecked groups omitted)
|
|
5753
|
+
* - multi-select: array of selected option values
|
|
5754
|
+
* - textarea: string value
|
|
5755
|
+
*
|
|
5756
|
+
* Elements without both `name` and `id` attributes are silently skipped.
|
|
5757
|
+
*
|
|
5758
|
+
* @param {string|Element} target - CSS selector, UUID string, or DOM element
|
|
5759
|
+
* @returns {Object} Plain object mapping field names to values
|
|
5760
|
+
* @category Component
|
|
5761
|
+
* @see bw.makeForm
|
|
5762
|
+
* @see bw.makeInput
|
|
5763
|
+
* @example
|
|
5764
|
+
* // Given a form with name="email" input and name="agree" checkbox:
|
|
5765
|
+
* var data = bw.formData('#signup-form');
|
|
5766
|
+
* // => { email: 'user@example.com', agree: true }
|
|
5767
|
+
*
|
|
5768
|
+
* // Collect and publish in one step:
|
|
5769
|
+
* bw.pub('form:submit', bw.formData('#my-form'));
|
|
5770
|
+
*
|
|
5771
|
+
* // Works with any container, not just <form>:
|
|
5772
|
+
* bw.pub('settings:changed', bw.formData('.settings-panel'));
|
|
5773
|
+
*/
|
|
5774
|
+
bw.formData = function(target) {
|
|
5775
|
+
var el = bw.el(target);
|
|
5776
|
+
if (!el) return {};
|
|
5777
|
+
var result = {};
|
|
5778
|
+
var inputs = el.querySelectorAll('input, select, textarea');
|
|
5779
|
+
for (var i = 0; i < inputs.length; i++) {
|
|
5780
|
+
var inp = inputs[i];
|
|
5781
|
+
var key = inp.name || inp.id;
|
|
5782
|
+
if (!key) continue;
|
|
5783
|
+
if (inp.type === 'checkbox') {
|
|
5784
|
+
result[key] = inp.checked;
|
|
5785
|
+
} else if (inp.type === 'radio') {
|
|
5786
|
+
if (inp.checked) result[key] = inp.value;
|
|
5787
|
+
} else if (inp.tagName === 'SELECT' && inp.multiple) {
|
|
5788
|
+
result[key] = [];
|
|
5789
|
+
for (var j = 0; j < inp.options.length; j++) {
|
|
5790
|
+
if (inp.options[j].selected) result[key].push(inp.options[j].value);
|
|
5791
|
+
}
|
|
5792
|
+
} else {
|
|
5793
|
+
result[key] = inp.value;
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
return result;
|
|
5797
|
+
};
|
|
5798
|
+
|
|
5799
|
+
// ===================================================================================
|
|
5800
|
+
// bw.jsonPatch() — RFC 6902 JSON Patch on plain objects
|
|
5801
|
+
// ===================================================================================
|
|
5802
|
+
|
|
5803
|
+
/**
|
|
5804
|
+
* Apply RFC 6902 JSON Patch operations to a plain object.
|
|
5805
|
+
*
|
|
5806
|
+
* Supported operations: add, remove, replace, move, copy, test.
|
|
5807
|
+
* Paths use JSON Pointer (RFC 6901) notation: `/foo/bar/0`.
|
|
5808
|
+
* Mutates the target object in place and returns it.
|
|
5809
|
+
*
|
|
5810
|
+
* @param {Object} obj - Target object to patch
|
|
5811
|
+
* @param {Array<Object>} ops - Array of patch operations
|
|
5812
|
+
* @param {string} ops[].op - Operation: 'add', 'remove', 'replace', 'move', 'copy', 'test'
|
|
5813
|
+
* @param {string} ops[].path - JSON Pointer path (e.g. '/a/b/0')
|
|
5814
|
+
* @param {*} [ops[].value] - Value for add/replace/test
|
|
5815
|
+
* @param {string} [ops[].from] - Source path for move/copy
|
|
5816
|
+
* @returns {Object} The patched object (same reference)
|
|
5817
|
+
* @throws {Error} On invalid op, missing path, test failure, or path not found for remove
|
|
5818
|
+
* @category Data Utilities
|
|
5819
|
+
* @see bw.patch
|
|
5820
|
+
* @example
|
|
5821
|
+
* var obj = { a: 1, b: { c: 2 } };
|
|
5822
|
+
* bw.jsonPatch(obj, [
|
|
5823
|
+
* { op: 'replace', path: '/a', value: 10 },
|
|
5824
|
+
* { op: 'add', path: '/b/d', value: 3 },
|
|
5825
|
+
* { op: 'remove', path: '/b/c' }
|
|
5826
|
+
* ]);
|
|
5827
|
+
* // obj => { a: 10, b: { d: 3 } }
|
|
5828
|
+
*/
|
|
5829
|
+
bw.jsonPatch = function(obj, ops) {
|
|
5830
|
+
if (!_isA(ops)) return obj;
|
|
5831
|
+
|
|
5832
|
+
// Parse JSON Pointer path to array of keys
|
|
5833
|
+
function parsePath(path) {
|
|
5834
|
+
if (path === '') return [];
|
|
5835
|
+
if (path.charAt(0) !== '/') throw new Error('Invalid JSON Pointer: ' + path);
|
|
5836
|
+
return path.slice(1).split('/').map(function(s) {
|
|
5837
|
+
return s.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
5838
|
+
});
|
|
5839
|
+
}
|
|
5840
|
+
|
|
5841
|
+
// Walk to parent of final key; return { parent, key }
|
|
5842
|
+
function resolve(root, keys) {
|
|
5843
|
+
var parent = root;
|
|
5844
|
+
for (var i = 0; i < keys.length - 1; i++) {
|
|
5845
|
+
var k = _isA(parent) ? parseInt(keys[i], 10) : keys[i];
|
|
5846
|
+
if (parent[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
5847
|
+
parent = parent[k];
|
|
5848
|
+
}
|
|
5849
|
+
return { parent: parent, key: _isA(parent) ? parseInt(keys[keys.length - 1], 10) : keys[keys.length - 1] };
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5852
|
+
// Get value at path
|
|
5853
|
+
function getVal(root, keys) {
|
|
5854
|
+
var cur = root;
|
|
5855
|
+
for (var i = 0; i < keys.length; i++) {
|
|
5856
|
+
var k = _isA(cur) ? parseInt(keys[i], 10) : keys[i];
|
|
5857
|
+
if (cur[k] === undefined) throw new Error('Path not found: /' + keys.slice(0, i + 1).join('/'));
|
|
5858
|
+
cur = cur[k];
|
|
5859
|
+
}
|
|
5860
|
+
return cur;
|
|
5861
|
+
}
|
|
5862
|
+
|
|
5863
|
+
for (var i = 0; i < ops.length; i++) {
|
|
5864
|
+
var op = ops[i];
|
|
5865
|
+
if (!op.op || !_is(op.path, 'string')) throw new Error('Invalid patch operation at index ' + i);
|
|
5866
|
+
var keys = parsePath(op.path);
|
|
5867
|
+
|
|
5868
|
+
var r, val, fromKeys, fr, tr, cr;
|
|
5869
|
+
switch (op.op) {
|
|
5870
|
+
case 'add': {
|
|
5871
|
+
if (keys.length === 0) throw new Error('Cannot add to root');
|
|
5872
|
+
r = resolve(obj, keys);
|
|
5873
|
+
if (_isA(r.parent) && r.key <= r.parent.length) {
|
|
5874
|
+
r.parent.splice(r.key, 0, op.value);
|
|
5875
|
+
} else {
|
|
5876
|
+
r.parent[r.key] = op.value;
|
|
5877
|
+
}
|
|
5878
|
+
break;
|
|
5879
|
+
}
|
|
5880
|
+
case 'remove': {
|
|
5881
|
+
if (keys.length === 0) throw new Error('Cannot remove root');
|
|
5882
|
+
r = resolve(obj, keys);
|
|
5883
|
+
if (_isA(r.parent)) {
|
|
5884
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
5885
|
+
r.parent.splice(r.key, 1);
|
|
5886
|
+
} else {
|
|
5887
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
5888
|
+
delete r.parent[r.key];
|
|
5889
|
+
}
|
|
5890
|
+
break;
|
|
5891
|
+
}
|
|
5892
|
+
case 'replace': {
|
|
5893
|
+
if (keys.length === 0) throw new Error('Cannot replace root');
|
|
5894
|
+
r = resolve(obj, keys);
|
|
5895
|
+
if (_isA(r.parent)) {
|
|
5896
|
+
if (r.key >= r.parent.length) throw new Error('Index out of bounds: ' + r.key);
|
|
5897
|
+
} else {
|
|
5898
|
+
if (!(r.key in r.parent)) throw new Error('Path not found: ' + op.path);
|
|
5899
|
+
}
|
|
5900
|
+
r.parent[r.key] = op.value;
|
|
5901
|
+
break;
|
|
5902
|
+
}
|
|
5903
|
+
case 'move': {
|
|
5904
|
+
if (!_is(op.from, 'string')) throw new Error('move requires "from"');
|
|
5905
|
+
fromKeys = parsePath(op.from);
|
|
5906
|
+
val = getVal(obj, fromKeys);
|
|
5907
|
+
fr = resolve(obj, fromKeys);
|
|
5908
|
+
if (_isA(fr.parent)) { fr.parent.splice(fr.key, 1); }
|
|
5909
|
+
else { delete fr.parent[fr.key]; }
|
|
5910
|
+
tr = resolve(obj, keys);
|
|
5911
|
+
if (_isA(tr.parent) && tr.key <= tr.parent.length) {
|
|
5912
|
+
tr.parent.splice(tr.key, 0, val);
|
|
5913
|
+
} else {
|
|
5914
|
+
tr.parent[tr.key] = val;
|
|
5915
|
+
}
|
|
5916
|
+
break;
|
|
5917
|
+
}
|
|
5918
|
+
case 'copy': {
|
|
5919
|
+
if (!_is(op.from, 'string')) throw new Error('copy requires "from"');
|
|
5920
|
+
val = getVal(obj, parsePath(op.from));
|
|
5921
|
+
cr = resolve(obj, keys);
|
|
5922
|
+
if (_isA(cr.parent) && cr.key <= cr.parent.length) {
|
|
5923
|
+
cr.parent.splice(cr.key, 0, val);
|
|
5924
|
+
} else {
|
|
5925
|
+
cr.parent[cr.key] = val;
|
|
5926
|
+
}
|
|
5927
|
+
break;
|
|
5928
|
+
}
|
|
5929
|
+
case 'test': {
|
|
5930
|
+
var actual = getVal(obj, keys);
|
|
5931
|
+
if (JSON.stringify(actual) !== JSON.stringify(op.value)) {
|
|
5932
|
+
throw new Error('Test failed: ' + op.path + ' expected ' + JSON.stringify(op.value) + ' got ' + JSON.stringify(actual));
|
|
5933
|
+
}
|
|
5934
|
+
break;
|
|
5935
|
+
}
|
|
5936
|
+
default:
|
|
5937
|
+
throw new Error('Unknown op: ' + op.op);
|
|
5938
|
+
}
|
|
5939
|
+
}
|
|
5940
|
+
return obj;
|
|
5941
|
+
};
|
|
5942
|
+
|
|
5612
5943
|
// ===================================================================================
|
|
5613
5944
|
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
5614
5945
|
// ===================================================================================
|
|
@@ -5750,7 +6081,7 @@
|
|
|
5750
6081
|
var target = msg.target;
|
|
5751
6082
|
|
|
5752
6083
|
if (type === 'replace') {
|
|
5753
|
-
var el = bw.
|
|
6084
|
+
var el = bw.el(target);
|
|
5754
6085
|
if (!el) return false;
|
|
5755
6086
|
bw.DOM(el, msg.node);
|
|
5756
6087
|
return true;
|
|
@@ -5760,14 +6091,14 @@
|
|
|
5760
6091
|
return patched !== null;
|
|
5761
6092
|
|
|
5762
6093
|
} else if (type === 'append') {
|
|
5763
|
-
var parent = bw.
|
|
6094
|
+
var parent = bw.el(target);
|
|
5764
6095
|
if (!parent) return false;
|
|
5765
6096
|
var child = bw.createDOM(msg.node);
|
|
5766
6097
|
parent.appendChild(child);
|
|
5767
6098
|
return true;
|
|
5768
6099
|
|
|
5769
6100
|
} else if (type === 'remove') {
|
|
5770
|
-
var toRemove = bw.
|
|
6101
|
+
var toRemove = bw.el(target);
|
|
5771
6102
|
if (!toRemove) return false;
|
|
5772
6103
|
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
5773
6104
|
toRemove.remove();
|
|
@@ -5827,30 +6158,98 @@
|
|
|
5827
6158
|
|
|
5828
6159
|
|
|
5829
6160
|
// ===================================================================================
|
|
5830
|
-
// bw.inspect() —
|
|
6161
|
+
// bw.inspect() — DOM introspection with bitwrench metadata
|
|
5831
6162
|
// ===================================================================================
|
|
5832
6163
|
|
|
5833
6164
|
/**
|
|
5834
|
-
* Inspect a DOM element
|
|
5835
|
-
*
|
|
5836
|
-
*
|
|
5837
|
-
*
|
|
5838
|
-
*
|
|
6165
|
+
* Inspect a DOM element and its subtree, returning a plain-object
|
|
6166
|
+
* representation with bitwrench metadata at each node. Useful for debugging,
|
|
6167
|
+
* devtools, MCP/AG-UI tool discovery, and automated testing.
|
|
6168
|
+
*
|
|
6169
|
+
* Each node in the returned tree includes:
|
|
6170
|
+
* - `tag` -- lowercase tag name (or '#text' for text nodes)
|
|
6171
|
+
* - `id` -- element id (if set)
|
|
6172
|
+
* - `uuid` -- bitwrench UUID class (if lifecycle-managed)
|
|
6173
|
+
* - `type` -- component type from o.type (if set, e.g. 'card', 'tabs')
|
|
6174
|
+
* - `classes` -- first 5 CSS classes (string, space-separated)
|
|
6175
|
+
* - `handles` -- array of el.bw method names (if any)
|
|
6176
|
+
* - `state` -- copy of _bw_state (if any)
|
|
6177
|
+
* - `hasRender` -- true if _bw_render is set
|
|
6178
|
+
* - `hasSubs` -- true if element has pub/sub subscriptions
|
|
6179
|
+
* - `refs` -- copy of _bw_refs keys (if any)
|
|
6180
|
+
* - `children` -- array of child node trees (up to depth limit, max 50 per level)
|
|
6181
|
+
*
|
|
6182
|
+
* @param {string|Element} target - CSS selector, UUID, or DOM element
|
|
6183
|
+
* @param {number} [depth=3] - Maximum recursion depth (0 = target only, no children)
|
|
6184
|
+
* @returns {Object|null} Plain object tree, or null if element not found
|
|
5839
6185
|
* @category Component
|
|
5840
6186
|
* @example
|
|
5841
|
-
*
|
|
5842
|
-
* bw.inspect(
|
|
6187
|
+
* // Get full tree from #app, 3 levels deep (default):
|
|
6188
|
+
* var info = bw.inspect('#app');
|
|
6189
|
+
*
|
|
6190
|
+
* // Shallow inspection (just the element, no children):
|
|
6191
|
+
* var info = bw.inspect('#my-carousel', 0);
|
|
6192
|
+
* console.log(info.handles); // ['next', 'prev', 'goToSlide']
|
|
6193
|
+
* console.log(info.type); // 'carousel'
|
|
6194
|
+
*
|
|
6195
|
+
* // Deep inspection for debugging:
|
|
6196
|
+
* console.log(JSON.stringify(bw.inspect('#app', 5), null, 2));
|
|
5843
6197
|
*/
|
|
5844
|
-
bw.inspect = function(target) {
|
|
5845
|
-
var el =
|
|
5846
|
-
if (!el
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
6198
|
+
bw.inspect = function(target, depth) {
|
|
6199
|
+
var el = bw.el(target);
|
|
6200
|
+
if (!el && _is(target, 'string')) el = bw.$(target)[0];
|
|
6201
|
+
if (!el) return null;
|
|
6202
|
+
if (depth === undefined || depth === null) depth = 3;
|
|
6203
|
+
|
|
6204
|
+
function walk(node, d) {
|
|
6205
|
+
if (!node) return null;
|
|
6206
|
+
// Skip non-element nodes (text, comment, etc.)
|
|
6207
|
+
if (node.nodeType !== 1) return null;
|
|
6208
|
+
|
|
6209
|
+
var info = { tag: node.tagName ? node.tagName.toLowerCase() : '#text' };
|
|
6210
|
+
|
|
6211
|
+
// Identity
|
|
6212
|
+
if (node.id) info.id = node.id;
|
|
6213
|
+
var uuid = bw.getUUID(node);
|
|
6214
|
+
if (uuid) info.uuid = uuid;
|
|
6215
|
+
if (node._bw_type) info.type = node._bw_type;
|
|
6216
|
+
|
|
6217
|
+
// CSS classes (first 5 for readability)
|
|
6218
|
+
if (node.className && typeof node.className === 'string') {
|
|
6219
|
+
info.classes = node.className.split(' ').slice(0, 5).join(' ');
|
|
6220
|
+
}
|
|
6221
|
+
|
|
6222
|
+
// Bitwrench handle methods
|
|
6223
|
+
if (node.bw) {
|
|
6224
|
+
var handles = _keys(node.bw);
|
|
6225
|
+
if (handles.length > 0) info.handles = handles;
|
|
6226
|
+
}
|
|
6227
|
+
|
|
6228
|
+
// State
|
|
6229
|
+
if (node._bw_state) info.state = node._bw_state;
|
|
6230
|
+
if (node._bw_render) info.hasRender = true;
|
|
6231
|
+
if (node._bw_subs && node._bw_subs.length > 0) info.hasSubs = true;
|
|
6232
|
+
|
|
6233
|
+
// Refs
|
|
6234
|
+
if (node._bw_refs) info.refs = _keys(node._bw_refs);
|
|
6235
|
+
|
|
6236
|
+
// Children (recurse up to depth limit, max 50 children per level)
|
|
6237
|
+
if (d < depth && node.children && node.children.length > 0) {
|
|
6238
|
+
info.children = [];
|
|
6239
|
+
var max = Math.min(node.children.length, 50);
|
|
6240
|
+
for (var i = 0; i < max; i++) {
|
|
6241
|
+
var child = walk(node.children[i], d + 1);
|
|
6242
|
+
if (child) info.children.push(child);
|
|
6243
|
+
}
|
|
6244
|
+
if (node.children.length > 50) {
|
|
6245
|
+
info.children.push({ tag: '...', count: node.children.length - 50 });
|
|
6246
|
+
}
|
|
6247
|
+
}
|
|
6248
|
+
|
|
6249
|
+
return info;
|
|
6250
|
+
}
|
|
6251
|
+
|
|
6252
|
+
return walk(el, 0);
|
|
5854
6253
|
};
|
|
5855
6254
|
|
|
5856
6255
|
bw.compile = function() { throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
@@ -6072,37 +6471,49 @@
|
|
|
6072
6471
|
* so you can use `.map()`, `.filter()`, etc. directly. Accepts CSS selectors,
|
|
6073
6472
|
* single elements, NodeLists, or arrays.
|
|
6074
6473
|
*
|
|
6474
|
+
* With an optional second argument, applies content or a function to
|
|
6475
|
+
* every matched element (same apply rules as `bw.el()`):
|
|
6476
|
+
* - string/number: sets `el.textContent`
|
|
6477
|
+
* - function: calls `apply(el)` for each element
|
|
6478
|
+
* - TACO object: clears children, mounts TACO via `bw.createDOM()`
|
|
6479
|
+
* - array: clears children, appends each item
|
|
6480
|
+
*
|
|
6075
6481
|
* @param {string|Element|Array} selector - CSS selector, element, or array
|
|
6482
|
+
* @param {string|number|Function|Object|Array} [apply] - Content or function to apply
|
|
6076
6483
|
* @returns {Array} Array of DOM elements
|
|
6077
6484
|
* @category DOM Selection
|
|
6485
|
+
* @see bw.el
|
|
6078
6486
|
* @example
|
|
6079
|
-
* bw.$('.card')
|
|
6080
|
-
* bw.$(
|
|
6081
|
-
* bw.$('.card'
|
|
6487
|
+
* bw.$('.card') // => [div.card, div.card, ...]
|
|
6488
|
+
* bw.$('.status', 'Online') // set text on all .status elements
|
|
6489
|
+
* bw.$('.card', function(el) { // apply function to each
|
|
6490
|
+
* el.style.opacity = '0.5';
|
|
6491
|
+
* })
|
|
6082
6492
|
*/
|
|
6083
6493
|
if (bw._isBrowser) {
|
|
6084
|
-
bw.$ = function(selector) {
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
if (_isA(selector))
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
if (
|
|
6095
|
-
|
|
6494
|
+
bw.$ = function(selector, apply) {
|
|
6495
|
+
var els;
|
|
6496
|
+
if (!selector) {
|
|
6497
|
+
els = [];
|
|
6498
|
+
} else if (_isA(selector)) {
|
|
6499
|
+
els = selector;
|
|
6500
|
+
} else if (selector.nodeType) {
|
|
6501
|
+
els = [selector];
|
|
6502
|
+
} else if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
6503
|
+
els = Array.from(selector);
|
|
6504
|
+
} else if (_is(selector, 'string')) {
|
|
6505
|
+
els = Array.from(document.querySelectorAll(selector));
|
|
6506
|
+
} else {
|
|
6507
|
+
els = [];
|
|
6096
6508
|
}
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
return Array.from(document.querySelectorAll(selector));
|
|
6509
|
+
|
|
6510
|
+
if (apply !== undefined) {
|
|
6511
|
+
for (var i = 0; i < els.length; i++) _applyTo(els[i], apply);
|
|
6101
6512
|
}
|
|
6102
|
-
|
|
6103
|
-
return
|
|
6513
|
+
|
|
6514
|
+
return els;
|
|
6104
6515
|
};
|
|
6105
|
-
|
|
6516
|
+
|
|
6106
6517
|
// Convenience single element selector
|
|
6107
6518
|
bw.$.one = function(selector) {
|
|
6108
6519
|
return bw.$(selector)[0] || null;
|
|
@@ -6256,7 +6667,8 @@
|
|
|
6256
6667
|
*
|
|
6257
6668
|
* @param {Object} [config] - Style configuration (same as `makeStyles`)
|
|
6258
6669
|
* @param {string} [scope] - Scope selector (same as `applyStyles`)
|
|
6259
|
-
* @returns {
|
|
6670
|
+
* @returns {Object} The styles object (same as `makeStyles` return value:
|
|
6671
|
+
* `{css, alternateCss, palette, alternatePalette, rules, alternateRules, isLightPrimary}`)
|
|
6260
6672
|
* @category CSS & Styling
|
|
6261
6673
|
* @see bw.makeStyles
|
|
6262
6674
|
* @see bw.applyStyles
|
|
@@ -6274,9 +6686,27 @@
|
|
|
6274
6686
|
bw.injectCSS(structuralCSS, { id: 'bw_structural', append: false });
|
|
6275
6687
|
}
|
|
6276
6688
|
}
|
|
6277
|
-
|
|
6689
|
+
var styles = bw.makeStyles(config);
|
|
6690
|
+
bw.applyStyles(styles, scope);
|
|
6691
|
+
return styles;
|
|
6278
6692
|
};
|
|
6279
6693
|
|
|
6694
|
+
/**
|
|
6695
|
+
* Prefix every selector in a rules object with a scope selector.
|
|
6696
|
+
* Useful for wrapping site-level CSS under `.bw_theme_alt` for dark mode.
|
|
6697
|
+
*
|
|
6698
|
+
* @param {Object} rules - CSS rules object (selector -> declarations)
|
|
6699
|
+
* @param {string} prefix - Scope prefix (e.g. '.bw_theme_alt')
|
|
6700
|
+
* @returns {Object} New rules object with scoped selectors
|
|
6701
|
+
* @category CSS & Styling
|
|
6702
|
+
* @see bw.applyStyles
|
|
6703
|
+
* @see bw.css
|
|
6704
|
+
* @example
|
|
6705
|
+
* var altRules = bw.scopeRulesUnder(myRules, '.bw_theme_alt');
|
|
6706
|
+
* bw.injectCSS(bw.css(altRules));
|
|
6707
|
+
*/
|
|
6708
|
+
bw.scopeRulesUnder = scopeRulesUnder;
|
|
6709
|
+
|
|
6280
6710
|
/**
|
|
6281
6711
|
* Inject the CSS reset (box-sizing, html/body font, reduced-motion).
|
|
6282
6712
|
* Idempotent — if already injected, returns the existing `<style>` element.
|
|
@@ -6296,42 +6726,48 @@
|
|
|
6296
6726
|
};
|
|
6297
6727
|
|
|
6298
6728
|
/**
|
|
6299
|
-
* Toggle between primary and alternate palettes.
|
|
6729
|
+
* Toggle between primary and alternate theme palettes.
|
|
6300
6730
|
*
|
|
6301
|
-
* Adds/removes the `bw_theme_alt` class on the scoping element.
|
|
6731
|
+
* Adds/removes the `bw_theme_alt` class on the scoping element(s).
|
|
6302
6732
|
* Without a scope, toggles on `<html>` (global).
|
|
6303
|
-
* With a scope, toggles on
|
|
6733
|
+
* With a scope, toggles on ALL matching elements.
|
|
6304
6734
|
*
|
|
6305
|
-
* @param {string} [scope] -
|
|
6306
|
-
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
6735
|
+
* @param {string|Element} [scope] - Selector or element. Omit for global.
|
|
6736
|
+
* @returns {string} Active mode after toggle: 'primary' or 'alternate' (based on first element)
|
|
6307
6737
|
* @category CSS & Styling
|
|
6308
6738
|
* @see bw.applyStyles
|
|
6309
6739
|
* @see bw.clearStyles
|
|
6310
6740
|
* @example
|
|
6311
|
-
* bw.
|
|
6312
|
-
* bw.
|
|
6741
|
+
* bw.toggleThemeMode(); // global toggle on <html>
|
|
6742
|
+
* bw.toggleThemeMode('#my-dashboard'); // scoped toggle
|
|
6743
|
+
* bw.toggleThemeMode('.panel'); // toggle on ALL .panel elements
|
|
6313
6744
|
*/
|
|
6314
|
-
bw.
|
|
6745
|
+
bw.toggleThemeMode = function(scope) {
|
|
6315
6746
|
if (!bw._isBrowser) return 'primary';
|
|
6316
|
-
var
|
|
6747
|
+
var els;
|
|
6317
6748
|
if (scope) {
|
|
6318
|
-
|
|
6319
|
-
target = els[0];
|
|
6749
|
+
els = bw.$(scope);
|
|
6320
6750
|
} else {
|
|
6321
|
-
|
|
6751
|
+
els = [document.documentElement];
|
|
6322
6752
|
}
|
|
6323
|
-
if (!
|
|
6753
|
+
if (!els.length) return 'primary';
|
|
6324
6754
|
|
|
6325
|
-
var
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6755
|
+
var mode;
|
|
6756
|
+
for (var i = 0; i < els.length; i++) {
|
|
6757
|
+
var hasAlt = els[i].classList.contains('bw_theme_alt');
|
|
6758
|
+
if (hasAlt) {
|
|
6759
|
+
els[i].classList.remove('bw_theme_alt');
|
|
6760
|
+
} else {
|
|
6761
|
+
els[i].classList.add('bw_theme_alt');
|
|
6762
|
+
}
|
|
6763
|
+
if (i === 0) mode = hasAlt ? 'primary' : 'alternate';
|
|
6332
6764
|
}
|
|
6765
|
+
return mode;
|
|
6333
6766
|
};
|
|
6334
6767
|
|
|
6768
|
+
// Alias — kept for one release cycle. Use bw.toggleThemeMode() instead.
|
|
6769
|
+
bw.toggleStyles = bw.toggleThemeMode;
|
|
6770
|
+
|
|
6335
6771
|
/**
|
|
6336
6772
|
* Remove injected styles for a given scope.
|
|
6337
6773
|
*
|
|
@@ -7371,6 +7807,57 @@
|
|
|
7371
7807
|
}
|
|
7372
7808
|
});
|
|
7373
7809
|
|
|
7810
|
+
/**
|
|
7811
|
+
* Query the BCCL component registry. Returns metadata about registered
|
|
7812
|
+
* component types -- their names and factory function names. Useful for
|
|
7813
|
+
* tooling, introspection, documentation generators, and auto-complete
|
|
7814
|
+
* systems (including MCP/AG-UI tool discovery).
|
|
7815
|
+
*
|
|
7816
|
+
* With no arguments, returns an array of all registered component types.
|
|
7817
|
+
* With a type name, returns metadata for that single type (or null if
|
|
7818
|
+
* the type is not registered).
|
|
7819
|
+
*
|
|
7820
|
+
* @param {string} [type] - Optional component type name to look up
|
|
7821
|
+
* @returns {Array<Object>|Object|null} Array of {type, factory} objects,
|
|
7822
|
+
* a single {type, factory} object, or null if the type is not found
|
|
7823
|
+
* @category Component
|
|
7824
|
+
* @see bw.make
|
|
7825
|
+
* @see bw.BCCL
|
|
7826
|
+
* @example
|
|
7827
|
+
* // List all available component types:
|
|
7828
|
+
* bw.catalog();
|
|
7829
|
+
* // => [{ type: 'card', factory: 'makeCard' },
|
|
7830
|
+
* // { type: 'button', factory: 'makeButton' }, ...]
|
|
7831
|
+
*
|
|
7832
|
+
* // Look up a specific type:
|
|
7833
|
+
* bw.catalog('accordion');
|
|
7834
|
+
* // => { type: 'accordion', factory: 'makeAccordion' }
|
|
7835
|
+
*
|
|
7836
|
+
* // Check if a type exists:
|
|
7837
|
+
* if (bw.catalog('chart')) { ... }
|
|
7838
|
+
*
|
|
7839
|
+
* // Get just the type names:
|
|
7840
|
+
* bw.catalog().map(function(c) { return c.type; });
|
|
7841
|
+
* // => ['card', 'button', 'container', 'row', ...]
|
|
7842
|
+
*/
|
|
7843
|
+
bw.catalog = function(type) {
|
|
7844
|
+
if (type) {
|
|
7845
|
+
var def = bw.BCCL[type];
|
|
7846
|
+
if (!def) return null;
|
|
7847
|
+
return {
|
|
7848
|
+
type: type,
|
|
7849
|
+
factory: def.make.name || ('make' + type.charAt(0).toUpperCase() + type.slice(1))
|
|
7850
|
+
};
|
|
7851
|
+
}
|
|
7852
|
+
return Object.keys(bw.BCCL).map(function(k) {
|
|
7853
|
+
var def = bw.BCCL[k];
|
|
7854
|
+
return {
|
|
7855
|
+
type: k,
|
|
7856
|
+
factory: def.make.name || ('make' + k.charAt(0).toUpperCase() + k.slice(1))
|
|
7857
|
+
};
|
|
7858
|
+
});
|
|
7859
|
+
};
|
|
7860
|
+
|
|
7374
7861
|
// Also attach to global in browsers
|
|
7375
7862
|
if (bw._isBrowser && typeof window !== 'undefined') {
|
|
7376
7863
|
window.bw = bw;
|