bitwrench 2.0.15 → 2.0.17
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 +57 -21
- package/dist/bitwrench-bccl.cjs.js +3750 -0
- package/dist/bitwrench-bccl.cjs.min.js +40 -0
- package/dist/bitwrench-bccl.esm.js +3745 -0
- package/dist/bitwrench-bccl.esm.min.js +40 -0
- package/dist/bitwrench-bccl.umd.js +3756 -0
- package/dist/bitwrench-bccl.umd.min.js +40 -0
- package/dist/bitwrench-code-edit.cjs.js +57 -7
- package/dist/bitwrench-code-edit.cjs.min.js +9 -2
- package/dist/bitwrench-code-edit.es5.js +74 -11
- package/dist/bitwrench-code-edit.es5.min.js +9 -2
- package/dist/bitwrench-code-edit.esm.js +57 -7
- package/dist/bitwrench-code-edit.esm.min.js +9 -2
- package/dist/bitwrench-code-edit.umd.js +57 -7
- package/dist/bitwrench-code-edit.umd.min.js +9 -2
- package/dist/bitwrench-lean.cjs.js +905 -157
- package/dist/bitwrench-lean.cjs.min.js +7 -7
- package/dist/bitwrench-lean.es5.js +931 -157
- package/dist/bitwrench-lean.es5.min.js +5 -5
- package/dist/bitwrench-lean.esm.js +904 -157
- package/dist/bitwrench-lean.esm.min.js +7 -7
- package/dist/bitwrench-lean.umd.js +905 -157
- package/dist/bitwrench-lean.umd.min.js +7 -7
- package/dist/bitwrench.cjs.js +910 -158
- package/dist/bitwrench.cjs.min.js +8 -8
- package/dist/bitwrench.css +60 -17
- package/dist/bitwrench.es5.js +939 -158
- package/dist/bitwrench.es5.min.js +6 -6
- package/dist/bitwrench.esm.js +909 -158
- package/dist/bitwrench.esm.min.js +8 -8
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +910 -158
- package/dist/bitwrench.umd.min.js +8 -8
- package/dist/builds.json +168 -80
- package/dist/bwserve.cjs.js +660 -0
- package/dist/bwserve.esm.js +652 -0
- package/dist/sri.json +36 -28
- package/package.json +20 -3
- package/readme.html +62 -23
- package/src/bitwrench-bccl-entry.js +72 -0
- package/src/bitwrench-bccl.js +5 -1
- package/src/bitwrench-code-edit.js +56 -6
- package/src/bitwrench-color-utils.js +5 -6
- package/src/bitwrench-styles.js +20 -8
- package/src/bitwrench.js +876 -140
- package/src/bwserve/client.js +182 -0
- package/src/bwserve/index.js +363 -0
- package/src/bwserve/shell.js +106 -0
- package/src/cli/index.js +36 -15
- package/src/cli/layout-default.js +47 -32
- package/src/cli/serve.js +325 -0
- package/src/version.js +3 -3
- /package/bin/{bitwrench.js → bwcli.js} +0 -0
package/dist/bitwrench.esm.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/*! bitwrench v2.0.
|
|
1
|
+
/*! bitwrench v2.0.17 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
/**
|
|
3
3
|
* Auto-generated version file from package.json
|
|
4
4
|
* DO NOT EDIT DIRECTLY - Use npm run generate-version
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const VERSION_INFO = {
|
|
8
|
-
version: '2.0.
|
|
8
|
+
version: '2.0.17',
|
|
9
9
|
name: 'bitwrench',
|
|
10
10
|
description: 'A library for javascript UI functions.',
|
|
11
11
|
license: 'BSD-2-Clause',
|
|
12
12
|
homepage: 'https://deftio.github.com/bitwrench/pages',
|
|
13
13
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
14
14
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
15
|
-
buildDate: '2026-03-
|
|
15
|
+
buildDate: '2026-03-13T23:15:10.823Z'
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -430,12 +430,11 @@ function derivePalette(config) {
|
|
|
430
430
|
var lightBase = config.light || hslToHex([h, 8, 97]);
|
|
431
431
|
var darkBase = config.dark || hslToHex([h, 10, 13]);
|
|
432
432
|
|
|
433
|
-
// Background & surface tokens —
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
var
|
|
438
|
-
var surfBase = config.surface || '#f8f9fa';
|
|
433
|
+
// Background & surface tokens — tinted with primary hue for theme personality.
|
|
434
|
+
// Very subtle: bg at L=98/S=6, surface at L=96/S=8.
|
|
435
|
+
// User can override with config.background / config.surface.
|
|
436
|
+
var bgBase = config.background || hslToHex([h, 6, 98]);
|
|
437
|
+
var surfBase = config.surface || hslToHex([h, 8, 96]);
|
|
439
438
|
|
|
440
439
|
var palette = {
|
|
441
440
|
primary: deriveShades(config.primary),
|
|
@@ -1568,7 +1567,7 @@ var structuralRules = {
|
|
|
1568
1567
|
'@media (min-width: 992px)': { '.bw_container': { 'max-width': '960px' } },
|
|
1569
1568
|
'@media (min-width: 1200px)': { '.bw_container': { 'max-width': '1140px' } },
|
|
1570
1569
|
'.bw_container_fluid': {
|
|
1571
|
-
'width': '100%', 'padding-right': '
|
|
1570
|
+
'width': '100%', 'padding-right': '0.75rem', 'padding-left': '0.75rem',
|
|
1572
1571
|
'margin-right': 'auto', 'margin-left': 'auto'
|
|
1573
1572
|
},
|
|
1574
1573
|
'.bw_row': {
|
|
@@ -1729,7 +1728,8 @@ var structuralRules = {
|
|
|
1729
1728
|
'.bw_badge': {
|
|
1730
1729
|
'display': 'inline-block', 'font-size': '0.875rem',
|
|
1731
1730
|
'font-weight': '600', 'line-height': '1.3', 'text-align': 'center',
|
|
1732
|
-
'white-space': 'nowrap', 'vertical-align': 'baseline'
|
|
1731
|
+
'white-space': 'nowrap', 'vertical-align': 'baseline',
|
|
1732
|
+
'padding': '0.35rem 0.65rem', 'border-radius': '0.25rem'
|
|
1733
1733
|
},
|
|
1734
1734
|
'.bw_badge:empty': { 'display': 'none' },
|
|
1735
1735
|
'.bw_badge_sm': { 'font-size': '0.75rem', 'padding': '0.25rem 0.5rem' },
|
|
@@ -1914,7 +1914,7 @@ var structuralRules = {
|
|
|
1914
1914
|
// ---- Code demo ----
|
|
1915
1915
|
codeDemo: {
|
|
1916
1916
|
'.bw_code_demo': { 'margin-bottom': '2rem' },
|
|
1917
|
-
'.bw_code_pre': { 'margin': '0', 'border': 'none', 'overflow-x': 'auto' },
|
|
1917
|
+
'.bw_code_pre': { 'margin': '0', 'border': 'none', 'overflow-x': 'auto', 'max-width': '100%' },
|
|
1918
1918
|
'.bw_code_block': {
|
|
1919
1919
|
'display': 'block', 'padding': '1.25rem',
|
|
1920
1920
|
'font-family': '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace',
|
|
@@ -2011,7 +2011,7 @@ var structuralRules = {
|
|
|
2011
2011
|
},
|
|
2012
2012
|
'.bw_modal.bw_modal_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
|
|
2013
2013
|
'.bw_modal_dialog': {
|
|
2014
|
-
'position': 'relative', 'width': '100%', 'max-width': '500px', 'margin': '1.75rem auto',
|
|
2014
|
+
'position': 'relative', 'width': 'calc(100% - 1rem)', 'max-width': '500px', 'margin': '1.75rem auto',
|
|
2015
2015
|
'pointer-events': 'none'
|
|
2016
2016
|
},
|
|
2017
2017
|
'.bw_modal.bw_modal_show .bw_modal_dialog': { 'transform': 'translateY(0)' },
|
|
@@ -2041,7 +2041,7 @@ var structuralRules = {
|
|
|
2041
2041
|
'.bw_toast_container.bw_toast_top_center': { 'top': '0', 'left': '50%', 'transform': 'translateX(-50%)' },
|
|
2042
2042
|
'.bw_toast_container.bw_toast_bottom_center': { 'bottom': '0', 'left': '50%', 'transform': 'translateX(-50%)' },
|
|
2043
2043
|
'.bw_toast': {
|
|
2044
|
-
'pointer-events': 'auto', 'width': '350px', 'max-width': '
|
|
2044
|
+
'pointer-events': 'auto', 'width': '350px', 'max-width': 'calc(100vw - 2rem)', 'background-clip': 'padding-box',
|
|
2045
2045
|
'opacity': '0'
|
|
2046
2046
|
},
|
|
2047
2047
|
'.bw_toast.bw_toast_show': { 'opacity': '1', 'transform': 'translateY(0)' },
|
|
@@ -2127,7 +2127,7 @@ var structuralRules = {
|
|
|
2127
2127
|
'.bw_tooltip_wrapper': { 'position': 'relative', 'display': 'inline-block' },
|
|
2128
2128
|
'.bw_tooltip': {
|
|
2129
2129
|
'position': 'absolute', 'z-index': '999',
|
|
2130
|
-
'font-size': '0.875rem', 'white-space': 'nowrap', 'pointer-events': 'none',
|
|
2130
|
+
'font-size': '0.875rem', 'white-space': 'nowrap', 'max-width': 'min(300px, calc(100vw - 1rem))', 'pointer-events': 'none',
|
|
2131
2131
|
'opacity': '0', 'visibility': 'hidden'
|
|
2132
2132
|
},
|
|
2133
2133
|
'.bw_tooltip.bw_tooltip_show': { 'opacity': '1', 'visibility': 'visible' },
|
|
@@ -2147,7 +2147,7 @@ var structuralRules = {
|
|
|
2147
2147
|
'.bw_popover_trigger': { 'cursor': 'pointer' },
|
|
2148
2148
|
'.bw_popover': {
|
|
2149
2149
|
'position': 'absolute', 'z-index': '1000',
|
|
2150
|
-
'min-width': '200px', 'max-width': '320px',
|
|
2150
|
+
'min-width': '200px', 'max-width': 'min(320px, calc(100vw - 2rem))',
|
|
2151
2151
|
'pointer-events': 'none', 'opacity': '0', 'visibility': 'hidden'
|
|
2152
2152
|
},
|
|
2153
2153
|
'.bw_popover.bw_popover_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
|
|
@@ -2330,7 +2330,18 @@ var structuralRules = {
|
|
|
2330
2330
|
'.bw_hero, .bw_hero': { 'padding': '2rem 1rem' },
|
|
2331
2331
|
'.bw_cta_actions, .bw_cta-actions': { 'flex-direction': 'column' },
|
|
2332
2332
|
'.bw_hstack, .bw_hstack': { 'flex-direction': 'column' },
|
|
2333
|
-
'.bw_feature_grid, .bw_feature-grid': { 'grid-template-columns': '1fr' }
|
|
2333
|
+
'.bw_feature_grid, .bw_feature-grid': { 'grid-template-columns': '1fr' },
|
|
2334
|
+
'.bw_modal_dialog': { 'margin': '0.5rem auto' },
|
|
2335
|
+
'.bw_modal_lg': { 'max-width': 'calc(100% - 1rem)' },
|
|
2336
|
+
'.bw_modal_xl': { 'max-width': 'calc(100% - 1rem)' },
|
|
2337
|
+
'.bw_navbar': { 'padding': '0.5rem 0.75rem' },
|
|
2338
|
+
'.bw_navbar_brand': { 'margin-right': '0.5rem', 'font-size': '1rem' },
|
|
2339
|
+
'.bw_navbar_nav': { 'flex-wrap': 'wrap' },
|
|
2340
|
+
'.bw_tooltip': { 'white-space': 'normal' },
|
|
2341
|
+
'.bw_table': { 'display': 'block', 'overflow-x': 'auto', '-webkit-overflow-scrolling': 'touch' },
|
|
2342
|
+
'.bw_col, .bw_col_1, .bw_col_2, .bw_col_3, .bw_col_4, .bw_col_5, .bw_col_6, .bw_col_7, .bw_col_8, .bw_col_9, .bw_col_10, .bw_col_11, .bw_col_12': { 'flex': '0 0 100%', 'max-width': '100%' },
|
|
2343
|
+
'.bw_container': { 'padding-right': '0.5rem', 'padding-left': '0.5rem' },
|
|
2344
|
+
'.bw_container_fluid': { 'padding-right': '0.5rem', 'padding-left': '0.5rem' }
|
|
2334
2345
|
}
|
|
2335
2346
|
}
|
|
2336
2347
|
};
|
|
@@ -6863,7 +6874,11 @@ var BCCL = {
|
|
|
6863
6874
|
function make(type, props) {
|
|
6864
6875
|
var def = BCCL[type];
|
|
6865
6876
|
if (!def) throw new Error('bw.make: unknown component type "' + type + '". Available: ' + Object.keys(BCCL).join(', '));
|
|
6866
|
-
|
|
6877
|
+
var taco = def.make(props || {});
|
|
6878
|
+
if (taco && typeof taco === 'object') {
|
|
6879
|
+
taco._bwFactory = { type: type, props: props || {} };
|
|
6880
|
+
}
|
|
6881
|
+
return taco;
|
|
6867
6882
|
}
|
|
6868
6883
|
|
|
6869
6884
|
var components = /*#__PURE__*/Object.freeze({
|
|
@@ -6984,7 +6999,7 @@ const bw = {
|
|
|
6984
6999
|
__monkey_patch_is_nodejs__: {
|
|
6985
7000
|
_value: 'ignore',
|
|
6986
7001
|
set: function(x) {
|
|
6987
|
-
this._value = (
|
|
7002
|
+
this._value = _is(x, 'boolean') ? x : 'ignore';
|
|
6988
7003
|
},
|
|
6989
7004
|
get: function() {
|
|
6990
7005
|
return this._value;
|
|
@@ -7032,6 +7047,67 @@ Object.defineProperty(bw, '_isBrowser', {
|
|
|
7032
7047
|
configurable: true
|
|
7033
7048
|
});
|
|
7034
7049
|
|
|
7050
|
+
// ── Internal aliases ─────────────────────────────────────────────────────
|
|
7051
|
+
// Short names for frequently-used builtins and internal methods.
|
|
7052
|
+
// Same pattern as v1 (_to = bw.typeOf, etc.).
|
|
7053
|
+
//
|
|
7054
|
+
// Why: Terser can't shorten global property chains (console.warn,
|
|
7055
|
+
// Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
|
|
7056
|
+
// because it can't prove they're side-effect-free. We can, so we alias
|
|
7057
|
+
// them here. Each alias saves bytes in the minified output, and the short
|
|
7058
|
+
// names also reduce visual noise in the hot paths (binding pipeline,
|
|
7059
|
+
// createDOM, etc.).
|
|
7060
|
+
//
|
|
7061
|
+
// Alias Target Sites
|
|
7062
|
+
// ───────── ────────────────────────────────────── ─────
|
|
7063
|
+
// _hop Object.prototype.hasOwnProperty 15
|
|
7064
|
+
// _isA Array.isArray 25
|
|
7065
|
+
// _keys Object.keys 7
|
|
7066
|
+
// _to bw.typeOf (type string) 26
|
|
7067
|
+
// _is type check boolean: _is(x,'string') ~50
|
|
7068
|
+
// _cw console.warn 8
|
|
7069
|
+
// _cl console.log 11
|
|
7070
|
+
// _ce console.error 4
|
|
7071
|
+
// _chp ComponentHandle.prototype 28 (defined after constructor)
|
|
7072
|
+
//
|
|
7073
|
+
// Note: document.createElement etc. are NOT aliased because they require
|
|
7074
|
+
// `this === document` and .bind() would add overhead on every call.
|
|
7075
|
+
// Console aliases use thin wrappers (not direct refs) so test monkey-
|
|
7076
|
+
// patching of console.warn/log/error continues to work.
|
|
7077
|
+
//
|
|
7078
|
+
// `typeof x` for UNDECLARED globals (window, document, process, require,
|
|
7079
|
+
// EventSource, navigator, Promise, __filename, import.meta) MUST stay as
|
|
7080
|
+
// raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
|
|
7081
|
+
//
|
|
7082
|
+
// ── v1 functional type helpers (kept for reference, not currently used) ──
|
|
7083
|
+
// _toa(x, type, trueVal, falseVal) — bw.typeAssign:
|
|
7084
|
+
// returns trueVal if _to(x)===type, else falseVal.
|
|
7085
|
+
// Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
|
|
7086
|
+
// _toc(x, type, trueVal, falseVal) — bw.typeConvert:
|
|
7087
|
+
// same as _toa but if trueVal/falseVal are functions, calls them with x.
|
|
7088
|
+
// Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
|
|
7089
|
+
// Uncomment if pattern frequency justifies them:
|
|
7090
|
+
// var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
|
|
7091
|
+
// var _toc = function(x, t, y, n) { var r = _to(x)===t; return r ? (_to(y)==='function'?y(x):y) : (_to(n)==='function'?n(x):n); };
|
|
7092
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7093
|
+
var _hop = Object.prototype.hasOwnProperty;
|
|
7094
|
+
var _isA = Array.isArray;
|
|
7095
|
+
var _keys = Object.keys;
|
|
7096
|
+
var _to = typeOf; // imported from bitwrench-utils.js
|
|
7097
|
+
var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
|
|
7098
|
+
// Console aliases use thin wrappers (not direct references) so that test
|
|
7099
|
+
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
7100
|
+
var _cw = function() { console.warn.apply(console, arguments); };
|
|
7101
|
+
var _cl = function() { console.log.apply(console, arguments); };
|
|
7102
|
+
var _ce = function() { console.error.apply(console, arguments); };
|
|
7103
|
+
|
|
7104
|
+
/**
|
|
7105
|
+
* Debug flag. When true, emits console.warn for silent binding failures
|
|
7106
|
+
* (missing paths, null refs, auto-created intermediate objects).
|
|
7107
|
+
* @type {boolean}
|
|
7108
|
+
*/
|
|
7109
|
+
bw.debug = false;
|
|
7110
|
+
|
|
7035
7111
|
/**
|
|
7036
7112
|
* Lazy-resolve Node.js `fs` module.
|
|
7037
7113
|
* Tries require('fs') first (available in CJS/UMD Node.js builds),
|
|
@@ -7179,7 +7255,7 @@ bw.uuid = function(prefix) {
|
|
|
7179
7255
|
*/
|
|
7180
7256
|
bw._el = function(id) {
|
|
7181
7257
|
// Pass-through for DOM elements
|
|
7182
|
-
if (
|
|
7258
|
+
if (!_is(id, 'string')) return id || null;
|
|
7183
7259
|
if (!id) return null;
|
|
7184
7260
|
if (!bw._isBrowser) return null;
|
|
7185
7261
|
|
|
@@ -7275,7 +7351,7 @@ bw._deregisterNode = function(el, bwId) {
|
|
|
7275
7351
|
* // => '<b>Hello</b> & "world"'
|
|
7276
7352
|
*/
|
|
7277
7353
|
bw.escapeHTML = function(str) {
|
|
7278
|
-
if (
|
|
7354
|
+
if (!_is(str, 'string')) return '';
|
|
7279
7355
|
|
|
7280
7356
|
const escapeMap = {
|
|
7281
7357
|
'&': '&',
|
|
@@ -7348,7 +7424,7 @@ bw.html = function(taco, options = {}) {
|
|
|
7348
7424
|
}
|
|
7349
7425
|
|
|
7350
7426
|
// Handle arrays of TACOs
|
|
7351
|
-
if (
|
|
7427
|
+
if (_isA(taco)) {
|
|
7352
7428
|
return taco.map(t => bw.html(t, options)).join('');
|
|
7353
7429
|
}
|
|
7354
7430
|
|
|
@@ -7371,15 +7447,15 @@ bw.html = function(taco, options = {}) {
|
|
|
7371
7447
|
if (taco && taco._bwEach && options.state) {
|
|
7372
7448
|
var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
7373
7449
|
var arr = bw._evaluatePath(options.state, eachExpr);
|
|
7374
|
-
if (!
|
|
7450
|
+
if (!_isA(arr)) return '';
|
|
7375
7451
|
return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
|
|
7376
7452
|
}
|
|
7377
7453
|
|
|
7378
7454
|
// Handle primitives and non-TACO objects
|
|
7379
|
-
if (
|
|
7455
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
7380
7456
|
var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
|
|
7381
7457
|
// Resolve template bindings if state provided
|
|
7382
|
-
if (options.state &&
|
|
7458
|
+
if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
|
|
7383
7459
|
str = bw._resolveTemplate(str, options.state, !!options.compile);
|
|
7384
7460
|
}
|
|
7385
7461
|
return str;
|
|
@@ -7399,10 +7475,18 @@ bw.html = function(taco, options = {}) {
|
|
|
7399
7475
|
// Skip null, undefined, false
|
|
7400
7476
|
if (value == null || value === false) continue;
|
|
7401
7477
|
|
|
7402
|
-
//
|
|
7403
|
-
if (key.startsWith('on'))
|
|
7478
|
+
// Serialize event handlers via funcRegister
|
|
7479
|
+
if (key.startsWith('on')) {
|
|
7480
|
+
if (_is(value, 'function')) {
|
|
7481
|
+
var fnId = bw.funcRegister(value);
|
|
7482
|
+
attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
|
|
7483
|
+
} else if (_is(value, 'string')) {
|
|
7484
|
+
attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
|
|
7485
|
+
}
|
|
7486
|
+
continue;
|
|
7487
|
+
}
|
|
7404
7488
|
|
|
7405
|
-
if (key === 'style' &&
|
|
7489
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
7406
7490
|
// Convert style object to string
|
|
7407
7491
|
const styleStr = Object.entries(value)
|
|
7408
7492
|
.filter(([, v]) => v != null)
|
|
@@ -7413,7 +7497,7 @@ bw.html = function(taco, options = {}) {
|
|
|
7413
7497
|
}
|
|
7414
7498
|
} else if (key === 'class') {
|
|
7415
7499
|
// Handle class as array or string
|
|
7416
|
-
const classStr =
|
|
7500
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
7417
7501
|
if (classStr) {
|
|
7418
7502
|
attrStr += ` class="${bw.escapeHTML(classStr)}"`;
|
|
7419
7503
|
}
|
|
@@ -7449,13 +7533,184 @@ bw.html = function(taco, options = {}) {
|
|
|
7449
7533
|
// Process content recursively
|
|
7450
7534
|
let contentStr = content != null ? bw.html(content, options) : '';
|
|
7451
7535
|
// Resolve template bindings in content if state provided
|
|
7452
|
-
if (options.state &&
|
|
7536
|
+
if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
|
|
7453
7537
|
contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
|
|
7454
7538
|
}
|
|
7455
7539
|
|
|
7456
7540
|
return `<${tag}${attrStr}>${contentStr}</${tag}>`;
|
|
7457
7541
|
};
|
|
7458
7542
|
|
|
7543
|
+
/**
|
|
7544
|
+
* Generate a complete, self-contained HTML document from TACO content.
|
|
7545
|
+
*
|
|
7546
|
+
* Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
|
|
7547
|
+
* func registry emission (so serialized event handlers work), optional theme,
|
|
7548
|
+
* and extra head elements. Designed for static site generation, offline/airgapped
|
|
7549
|
+
* use, and the "static site that isn't static" workflow.
|
|
7550
|
+
*
|
|
7551
|
+
* @param {Object} [opts={}] - Page options
|
|
7552
|
+
* @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
|
|
7553
|
+
* @param {string} [opts.title='bitwrench'] - Page title
|
|
7554
|
+
* @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
|
|
7555
|
+
* @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
|
|
7556
|
+
* @param {string} [opts.css=''] - Additional CSS for <style> block
|
|
7557
|
+
* @param {string|Object} [opts.theme=null] - Theme preset name or config object
|
|
7558
|
+
* @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
|
|
7559
|
+
* @param {string} [opts.favicon=''] - Favicon URL
|
|
7560
|
+
* @param {string} [opts.lang='en'] - HTML lang attribute
|
|
7561
|
+
* @returns {string} Complete HTML document string
|
|
7562
|
+
* @category DOM Generation
|
|
7563
|
+
* @see bw.html
|
|
7564
|
+
* @example
|
|
7565
|
+
* bw.htmlPage({
|
|
7566
|
+
* title: 'My App',
|
|
7567
|
+
* body: { t: 'h1', c: 'Hello World' },
|
|
7568
|
+
* runtime: 'shim'
|
|
7569
|
+
* })
|
|
7570
|
+
*/
|
|
7571
|
+
bw.htmlPage = function(opts) {
|
|
7572
|
+
opts = opts || {};
|
|
7573
|
+
var title = opts.title || 'bitwrench';
|
|
7574
|
+
var body = opts.body || '';
|
|
7575
|
+
var state = opts.state || undefined;
|
|
7576
|
+
var runtime = opts.runtime || 'shim';
|
|
7577
|
+
var css = opts.css || '';
|
|
7578
|
+
var theme = opts.theme || null;
|
|
7579
|
+
var headExtra = opts.head || [];
|
|
7580
|
+
var favicon = opts.favicon || '';
|
|
7581
|
+
var lang = opts.lang || 'en';
|
|
7582
|
+
|
|
7583
|
+
// Snapshot funcRegistry counter before rendering
|
|
7584
|
+
var fnCounterBefore = bw._fnIDCounter;
|
|
7585
|
+
|
|
7586
|
+
// Render body content
|
|
7587
|
+
var bodyHTML = '';
|
|
7588
|
+
if (_is(body, 'string')) {
|
|
7589
|
+
bodyHTML = body;
|
|
7590
|
+
} else {
|
|
7591
|
+
var htmlOpts = {};
|
|
7592
|
+
if (state) htmlOpts.state = state;
|
|
7593
|
+
bodyHTML = bw.html(body, htmlOpts);
|
|
7594
|
+
}
|
|
7595
|
+
|
|
7596
|
+
// Collect functions registered during this render
|
|
7597
|
+
var fnCounterAfter = bw._fnIDCounter;
|
|
7598
|
+
var registryEntries = '';
|
|
7599
|
+
for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
|
|
7600
|
+
var fnKey = 'bw_fn_' + i;
|
|
7601
|
+
if (bw._fnRegistry[fnKey]) {
|
|
7602
|
+
registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
|
|
7603
|
+
bw._fnRegistry[fnKey].toString() + ';\n';
|
|
7604
|
+
}
|
|
7605
|
+
}
|
|
7606
|
+
|
|
7607
|
+
// Build runtime script for <head>
|
|
7608
|
+
var runtimeHead = '';
|
|
7609
|
+
if (runtime === 'inline') {
|
|
7610
|
+
// Read UMD bundle synchronously if in Node.js
|
|
7611
|
+
var umdSource = null;
|
|
7612
|
+
if (bw._isNode) {
|
|
7613
|
+
try {
|
|
7614
|
+
var fs = (typeof require === 'function') ? require('fs') : null;
|
|
7615
|
+
var pathMod = (typeof require === 'function') ? require('path') : null;
|
|
7616
|
+
if (fs && pathMod) {
|
|
7617
|
+
// Resolve dist/ relative to this source file
|
|
7618
|
+
var srcDir = '';
|
|
7619
|
+
try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
|
|
7620
|
+
catch(e2) { /* ESM: __filename not available */ }
|
|
7621
|
+
if (!srcDir && typeof import.meta !== 'undefined' && import.meta.url) {
|
|
7622
|
+
var url = (typeof require === 'function') ? require('url') : null;
|
|
7623
|
+
if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath(import.meta.url));
|
|
7624
|
+
}
|
|
7625
|
+
if (srcDir) {
|
|
7626
|
+
var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
|
|
7627
|
+
umdSource = fs.readFileSync(distPath, 'utf8');
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
} catch(e) { /* fall through */ }
|
|
7631
|
+
}
|
|
7632
|
+
if (umdSource) {
|
|
7633
|
+
runtimeHead = '<script>' + umdSource + '</script>';
|
|
7634
|
+
} else {
|
|
7635
|
+
// Fallback to shim in browser or if dist not available
|
|
7636
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
7637
|
+
}
|
|
7638
|
+
} else if (runtime === 'cdn') {
|
|
7639
|
+
runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
|
|
7640
|
+
} else if (runtime === 'shim') {
|
|
7641
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
7642
|
+
}
|
|
7643
|
+
// runtime === 'none' → empty
|
|
7644
|
+
|
|
7645
|
+
// Theme CSS
|
|
7646
|
+
var themeCSS = '';
|
|
7647
|
+
if (theme) {
|
|
7648
|
+
var themeConfig = _is(theme, 'string')
|
|
7649
|
+
? (THEME_PRESETS[theme.toLowerCase()] || null)
|
|
7650
|
+
: theme;
|
|
7651
|
+
if (themeConfig) {
|
|
7652
|
+
var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, { inject: false }));
|
|
7653
|
+
themeCSS = themeResult.css;
|
|
7654
|
+
}
|
|
7655
|
+
}
|
|
7656
|
+
|
|
7657
|
+
// Extra <head> elements
|
|
7658
|
+
var headHTML = '';
|
|
7659
|
+
if (_isA(headExtra) && headExtra.length > 0) {
|
|
7660
|
+
headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
|
|
7661
|
+
}
|
|
7662
|
+
|
|
7663
|
+
// Favicon
|
|
7664
|
+
var faviconTag = '';
|
|
7665
|
+
if (favicon) {
|
|
7666
|
+
var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
|
|
7667
|
+
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c];
|
|
7668
|
+
});
|
|
7669
|
+
faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
|
|
7670
|
+
}
|
|
7671
|
+
|
|
7672
|
+
// Escaped title
|
|
7673
|
+
var safeTitle = bw.escapeHTML(title);
|
|
7674
|
+
|
|
7675
|
+
// Combine all CSS
|
|
7676
|
+
var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
|
|
7677
|
+
|
|
7678
|
+
// Body-end script: registry entries + optional loadDefaultStyles
|
|
7679
|
+
var bodyEndScript = '';
|
|
7680
|
+
var bodyEndParts = [];
|
|
7681
|
+
if (registryEntries) {
|
|
7682
|
+
bodyEndParts.push(registryEntries);
|
|
7683
|
+
}
|
|
7684
|
+
if (runtime === 'inline' || runtime === 'cdn') {
|
|
7685
|
+
bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
|
|
7686
|
+
}
|
|
7687
|
+
if (bodyEndParts.length > 0) {
|
|
7688
|
+
bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
|
|
7689
|
+
}
|
|
7690
|
+
|
|
7691
|
+
// Assemble document
|
|
7692
|
+
var parts = [
|
|
7693
|
+
'<!DOCTYPE html>',
|
|
7694
|
+
'<html lang="' + lang + '">',
|
|
7695
|
+
'<head>',
|
|
7696
|
+
'<meta charset="UTF-8">',
|
|
7697
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
7698
|
+
];
|
|
7699
|
+
parts.push('<title>' + safeTitle + '</title>');
|
|
7700
|
+
if (faviconTag) parts.push(faviconTag);
|
|
7701
|
+
if (runtimeHead) parts.push(runtimeHead);
|
|
7702
|
+
if (headHTML) parts.push(headHTML);
|
|
7703
|
+
if (allCSS) parts.push('<style>' + allCSS + '</style>');
|
|
7704
|
+
parts.push('</head>');
|
|
7705
|
+
parts.push('<body>');
|
|
7706
|
+
parts.push(bodyHTML);
|
|
7707
|
+
if (bodyEndScript) parts.push(bodyEndScript);
|
|
7708
|
+
parts.push('</body>');
|
|
7709
|
+
parts.push('</html>');
|
|
7710
|
+
|
|
7711
|
+
return parts.join('\n');
|
|
7712
|
+
};
|
|
7713
|
+
|
|
7459
7714
|
/**
|
|
7460
7715
|
* Create a live DOM element from a TACO object (browser only).
|
|
7461
7716
|
*
|
|
@@ -7500,7 +7755,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
7500
7755
|
}
|
|
7501
7756
|
|
|
7502
7757
|
// Handle text nodes
|
|
7503
|
-
if (
|
|
7758
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
7504
7759
|
return document.createTextNode(String(taco));
|
|
7505
7760
|
}
|
|
7506
7761
|
|
|
@@ -7513,16 +7768,16 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
7513
7768
|
for (const [key, value] of Object.entries(attrs)) {
|
|
7514
7769
|
if (value == null || value === false) continue;
|
|
7515
7770
|
|
|
7516
|
-
if (key === 'style' &&
|
|
7771
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
7517
7772
|
// Apply styles directly
|
|
7518
7773
|
Object.assign(el.style, value);
|
|
7519
7774
|
} else if (key === 'class') {
|
|
7520
7775
|
// Handle class as array or string
|
|
7521
|
-
const classStr =
|
|
7776
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
7522
7777
|
if (classStr) {
|
|
7523
7778
|
el.className = classStr;
|
|
7524
7779
|
}
|
|
7525
|
-
} else if (key.startsWith('on') &&
|
|
7780
|
+
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
7526
7781
|
// Event handlers
|
|
7527
7782
|
const eventName = key.slice(2).toLowerCase();
|
|
7528
7783
|
el.addEventListener(eventName, value);
|
|
@@ -7542,7 +7797,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
7542
7797
|
// Children with data-bw_id or id attributes get local refs on the parent,
|
|
7543
7798
|
// so o.render functions can access them without any DOM lookup.
|
|
7544
7799
|
if (content != null) {
|
|
7545
|
-
if (
|
|
7800
|
+
if (_isA(content)) {
|
|
7546
7801
|
content.forEach(child => {
|
|
7547
7802
|
if (child != null) {
|
|
7548
7803
|
// Handle ComponentHandle in content arrays (Level 2 children)
|
|
@@ -7562,20 +7817,20 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
7562
7817
|
if (childEl._bw_refs) {
|
|
7563
7818
|
if (!el._bw_refs) el._bw_refs = {};
|
|
7564
7819
|
for (var rk in childEl._bw_refs) {
|
|
7565
|
-
if (
|
|
7820
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
7566
7821
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
7567
7822
|
}
|
|
7568
7823
|
}
|
|
7569
7824
|
}
|
|
7570
7825
|
}
|
|
7571
7826
|
});
|
|
7572
|
-
} else if (
|
|
7827
|
+
} else if (_is(content, 'object') && content.__bw_raw) {
|
|
7573
7828
|
// Raw HTML content — inject via innerHTML
|
|
7574
7829
|
el.innerHTML = content.v;
|
|
7575
7830
|
} else if (content._bwComponent === true) {
|
|
7576
7831
|
// Single ComponentHandle as content
|
|
7577
7832
|
content.mount(el);
|
|
7578
|
-
} else if (
|
|
7833
|
+
} else if (_is(content, 'object') && content.t) {
|
|
7579
7834
|
var childEl = bw.createDOM(content, options);
|
|
7580
7835
|
el.appendChild(childEl);
|
|
7581
7836
|
var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
|
|
@@ -7586,7 +7841,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
7586
7841
|
if (childEl._bw_refs) {
|
|
7587
7842
|
if (!el._bw_refs) el._bw_refs = {};
|
|
7588
7843
|
for (var rk in childEl._bw_refs) {
|
|
7589
|
-
if (
|
|
7844
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
7590
7845
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
7591
7846
|
}
|
|
7592
7847
|
}
|
|
@@ -7619,7 +7874,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
7619
7874
|
el._bw_render = opts.render;
|
|
7620
7875
|
|
|
7621
7876
|
if (opts.mounted) {
|
|
7622
|
-
|
|
7877
|
+
_cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
|
|
7623
7878
|
}
|
|
7624
7879
|
|
|
7625
7880
|
// Queue initial render (same timing as mounted)
|
|
@@ -7692,7 +7947,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
7692
7947
|
const targetEl = bw._el(target);
|
|
7693
7948
|
|
|
7694
7949
|
if (!targetEl) {
|
|
7695
|
-
|
|
7950
|
+
_ce('bw.DOM: Target element not found:', target);
|
|
7696
7951
|
return null;
|
|
7697
7952
|
}
|
|
7698
7953
|
|
|
@@ -7732,7 +7987,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
7732
7987
|
targetEl.appendChild(taco.element);
|
|
7733
7988
|
}
|
|
7734
7989
|
// Handle arrays
|
|
7735
|
-
else if (
|
|
7990
|
+
else if (_isA(taco)) {
|
|
7736
7991
|
taco.forEach(t => {
|
|
7737
7992
|
if (t != null) {
|
|
7738
7993
|
if (t._bwComponent === true) {
|
|
@@ -7768,7 +8023,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
7768
8023
|
bw.compileProps = function(handle, props = {}) {
|
|
7769
8024
|
const compiledProps = {};
|
|
7770
8025
|
|
|
7771
|
-
|
|
8026
|
+
_keys(props).forEach(key => {
|
|
7772
8027
|
// Create getter/setter for each prop
|
|
7773
8028
|
Object.defineProperty(compiledProps, key, {
|
|
7774
8029
|
get() {
|
|
@@ -8086,17 +8341,17 @@ bw.patch = function(id, content, attr) {
|
|
|
8086
8341
|
if (attr) {
|
|
8087
8342
|
// Patch an attribute
|
|
8088
8343
|
el.setAttribute(attr, String(content));
|
|
8089
|
-
} else if (
|
|
8344
|
+
} else if (_isA(content)) {
|
|
8090
8345
|
// Patch with array of children (strings and/or TACOs)
|
|
8091
8346
|
el.innerHTML = '';
|
|
8092
8347
|
content.forEach(function(item) {
|
|
8093
|
-
if (
|
|
8348
|
+
if (_is(item, 'string') || _is(item, 'number')) {
|
|
8094
8349
|
el.appendChild(document.createTextNode(String(item)));
|
|
8095
8350
|
} else if (item && item.t) {
|
|
8096
8351
|
el.appendChild(bw.createDOM(item));
|
|
8097
8352
|
}
|
|
8098
8353
|
});
|
|
8099
|
-
} else if (
|
|
8354
|
+
} else if (_is(content, 'object') && content.t) {
|
|
8100
8355
|
// Patch with a TACO — replace children
|
|
8101
8356
|
el.innerHTML = '';
|
|
8102
8357
|
el.appendChild(bw.createDOM(content));
|
|
@@ -8127,7 +8382,7 @@ bw.patch = function(id, content, attr) {
|
|
|
8127
8382
|
bw.patchAll = function(patches) {
|
|
8128
8383
|
var results = {};
|
|
8129
8384
|
for (var id in patches) {
|
|
8130
|
-
if (
|
|
8385
|
+
if (_hop.call(patches, id)) {
|
|
8131
8386
|
results[id] = bw.patch(id, patches[id]);
|
|
8132
8387
|
}
|
|
8133
8388
|
}
|
|
@@ -8224,7 +8479,7 @@ bw.pub = function(topic, detail) {
|
|
|
8224
8479
|
snapshot[i].handler(detail);
|
|
8225
8480
|
called++;
|
|
8226
8481
|
} catch (err) {
|
|
8227
|
-
|
|
8482
|
+
_cw('bw.pub: subscriber error on topic "' + topic + '":', err);
|
|
8228
8483
|
}
|
|
8229
8484
|
}
|
|
8230
8485
|
return called;
|
|
@@ -8320,8 +8575,8 @@ bw._fnIDCounter = 0;
|
|
|
8320
8575
|
* @see bw.funcGetDispatchStr
|
|
8321
8576
|
*/
|
|
8322
8577
|
bw.funcRegister = function(fn, name) {
|
|
8323
|
-
if (
|
|
8324
|
-
var fnID = (
|
|
8578
|
+
if (!_is(fn, 'function')) return '';
|
|
8579
|
+
var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
|
|
8325
8580
|
bw._fnRegistry[fnID] = fn;
|
|
8326
8581
|
return fnID;
|
|
8327
8582
|
};
|
|
@@ -8340,7 +8595,7 @@ bw.funcRegister = function(fn, name) {
|
|
|
8340
8595
|
bw.funcGetById = function(name, errFn) {
|
|
8341
8596
|
name = String(name);
|
|
8342
8597
|
if (name in bw._fnRegistry) return bw._fnRegistry[name];
|
|
8343
|
-
return (
|
|
8598
|
+
return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
|
|
8344
8599
|
};
|
|
8345
8600
|
|
|
8346
8601
|
/**
|
|
@@ -8381,13 +8636,30 @@ bw.funcUnregister = function(name) {
|
|
|
8381
8636
|
bw.funcGetRegistry = function() {
|
|
8382
8637
|
var copy = {};
|
|
8383
8638
|
for (var k in bw._fnRegistry) {
|
|
8384
|
-
if (
|
|
8639
|
+
if (_hop.call(bw._fnRegistry, k)) {
|
|
8385
8640
|
copy[k] = bw._fnRegistry[k];
|
|
8386
8641
|
}
|
|
8387
8642
|
}
|
|
8388
8643
|
return copy;
|
|
8389
8644
|
};
|
|
8390
8645
|
|
|
8646
|
+
/**
|
|
8647
|
+
* Minimal runtime shim for funcRegister dispatch in static HTML.
|
|
8648
|
+
* When embedded in a `<script>` tag, provides just enough infrastructure
|
|
8649
|
+
* for `bw.funcGetById()` calls to resolve. The actual function bodies
|
|
8650
|
+
* are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
|
|
8651
|
+
* @type {string}
|
|
8652
|
+
* @category Function Registry
|
|
8653
|
+
*/
|
|
8654
|
+
bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
|
|
8655
|
+
'if(!bw._fnRegistry)bw._fnRegistry={};' +
|
|
8656
|
+
'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
|
|
8657
|
+
'console.warn("bw: unregistered fn "+n)};};' +
|
|
8658
|
+
'bw.funcRegister=function(fn,name){' +
|
|
8659
|
+
'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
|
|
8660
|
+
'bw._fnRegistry[id]=fn;return id;};' +
|
|
8661
|
+
'window.bw=bw;})();';
|
|
8662
|
+
|
|
8391
8663
|
// ===================================================================================
|
|
8392
8664
|
// Template Binding Utilities
|
|
8393
8665
|
// ===================================================================================
|
|
@@ -8415,7 +8687,10 @@ bw._evaluatePath = function(state, path) {
|
|
|
8415
8687
|
var parts = path.split('.');
|
|
8416
8688
|
var val = state;
|
|
8417
8689
|
for (var i = 0; i < parts.length; i++) {
|
|
8418
|
-
if (val == null)
|
|
8690
|
+
if (val == null) {
|
|
8691
|
+
if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
|
|
8692
|
+
return '';
|
|
8693
|
+
}
|
|
8419
8694
|
val = val[parts[i]];
|
|
8420
8695
|
}
|
|
8421
8696
|
return (val == null) ? '' : val;
|
|
@@ -8435,7 +8710,7 @@ bw._evaluatePath = function(state, path) {
|
|
|
8435
8710
|
*/
|
|
8436
8711
|
bw._compiledExprs = {};
|
|
8437
8712
|
bw._resolveTemplate = function(str, state, compile) {
|
|
8438
|
-
if (
|
|
8713
|
+
if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
|
|
8439
8714
|
var bindings = bw._parseBindings(str);
|
|
8440
8715
|
if (bindings.length === 0) return str;
|
|
8441
8716
|
|
|
@@ -8457,6 +8732,7 @@ bw._resolveTemplate = function(str, state, compile) {
|
|
|
8457
8732
|
try {
|
|
8458
8733
|
val = bw._compiledExprs[b.expr](state);
|
|
8459
8734
|
} catch (e) {
|
|
8735
|
+
if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
|
|
8460
8736
|
val = '';
|
|
8461
8737
|
}
|
|
8462
8738
|
} else {
|
|
@@ -8565,7 +8841,7 @@ function ComponentHandle(taco) {
|
|
|
8565
8841
|
this._state = {};
|
|
8566
8842
|
if (o.state) {
|
|
8567
8843
|
for (var k in o.state) {
|
|
8568
|
-
if (
|
|
8844
|
+
if (_hop.call(o.state, k)) {
|
|
8569
8845
|
this._state[k] = o.state[k];
|
|
8570
8846
|
}
|
|
8571
8847
|
}
|
|
@@ -8574,7 +8850,7 @@ function ComponentHandle(taco) {
|
|
|
8574
8850
|
this._actions = {};
|
|
8575
8851
|
if (o.actions) {
|
|
8576
8852
|
for (var k2 in o.actions) {
|
|
8577
|
-
if (
|
|
8853
|
+
if (_hop.call(o.actions, k2)) {
|
|
8578
8854
|
this._actions[k2] = o.actions[k2];
|
|
8579
8855
|
}
|
|
8580
8856
|
}
|
|
@@ -8584,7 +8860,7 @@ function ComponentHandle(taco) {
|
|
|
8584
8860
|
if (o.methods) {
|
|
8585
8861
|
var self = this;
|
|
8586
8862
|
for (var k3 in o.methods) {
|
|
8587
|
-
if (
|
|
8863
|
+
if (_hop.call(o.methods, k3)) {
|
|
8588
8864
|
this._methods[k3] = o.methods[k3];
|
|
8589
8865
|
(function(methodName, methodFn) {
|
|
8590
8866
|
self[methodName] = function() {
|
|
@@ -8617,14 +8893,23 @@ function ComponentHandle(taco) {
|
|
|
8617
8893
|
this._compile = !!o.compile;
|
|
8618
8894
|
this._bw_refs = {};
|
|
8619
8895
|
this._refCounter = 0;
|
|
8896
|
+
// Child component ownership (Bug #5)
|
|
8897
|
+
this._children = [];
|
|
8898
|
+
this._parent = null;
|
|
8899
|
+
// Factory metadata for BCCL rebuild (Bug #6)
|
|
8900
|
+
this._factory = taco._bwFactory || null;
|
|
8620
8901
|
}
|
|
8621
8902
|
|
|
8903
|
+
// Short alias for ComponentHandle.prototype (see alias block at top of file).
|
|
8904
|
+
// 28 method definitions × 25 chars = ~700B raw savings in minified output.
|
|
8905
|
+
var _chp = ComponentHandle.prototype;
|
|
8906
|
+
|
|
8622
8907
|
// ── State Methods ──
|
|
8623
8908
|
|
|
8624
8909
|
/**
|
|
8625
8910
|
* Get a state value. Dot-path supported: `get('user.name')`
|
|
8626
8911
|
*/
|
|
8627
|
-
|
|
8912
|
+
_chp.get = function(key) {
|
|
8628
8913
|
return bw._evaluatePath(this._state, key);
|
|
8629
8914
|
};
|
|
8630
8915
|
|
|
@@ -8634,12 +8919,13 @@ ComponentHandle.prototype.get = function(key) {
|
|
|
8634
8919
|
* @param {*} value - New value
|
|
8635
8920
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
8636
8921
|
*/
|
|
8637
|
-
|
|
8922
|
+
_chp.set = function(key, value, opts) {
|
|
8638
8923
|
// Dot-path set
|
|
8639
8924
|
var parts = key.split('.');
|
|
8640
8925
|
var obj = this._state;
|
|
8641
8926
|
for (var i = 0; i < parts.length - 1; i++) {
|
|
8642
|
-
if (obj[parts[i]]
|
|
8927
|
+
if (!_is(obj[parts[i]], 'object')) {
|
|
8928
|
+
if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
|
|
8643
8929
|
obj[parts[i]] = {};
|
|
8644
8930
|
}
|
|
8645
8931
|
obj = obj[parts[i]];
|
|
@@ -8659,10 +8945,10 @@ ComponentHandle.prototype.set = function(key, value, opts) {
|
|
|
8659
8945
|
/**
|
|
8660
8946
|
* Get a shallow clone of the full state.
|
|
8661
8947
|
*/
|
|
8662
|
-
|
|
8948
|
+
_chp.getState = function() {
|
|
8663
8949
|
var clone = {};
|
|
8664
8950
|
for (var k in this._state) {
|
|
8665
|
-
if (
|
|
8951
|
+
if (_hop.call(this._state, k)) {
|
|
8666
8952
|
clone[k] = this._state[k];
|
|
8667
8953
|
}
|
|
8668
8954
|
}
|
|
@@ -8674,9 +8960,9 @@ ComponentHandle.prototype.getState = function() {
|
|
|
8674
8960
|
* @param {Object} updates - Key-value pairs to merge
|
|
8675
8961
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
8676
8962
|
*/
|
|
8677
|
-
|
|
8963
|
+
_chp.setState = function(updates, opts) {
|
|
8678
8964
|
for (var k in updates) {
|
|
8679
|
-
if (
|
|
8965
|
+
if (_hop.call(updates, k)) {
|
|
8680
8966
|
this._state[k] = updates[k];
|
|
8681
8967
|
this._dirtyKeys[k] = true;
|
|
8682
8968
|
}
|
|
@@ -8693,9 +8979,9 @@ ComponentHandle.prototype.setState = function(updates, opts) {
|
|
|
8693
8979
|
/**
|
|
8694
8980
|
* Push a value onto an array in state. Clones the array.
|
|
8695
8981
|
*/
|
|
8696
|
-
|
|
8982
|
+
_chp.push = function(key, val) {
|
|
8697
8983
|
var arr = this.get(key);
|
|
8698
|
-
var newArr =
|
|
8984
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
8699
8985
|
newArr.push(val);
|
|
8700
8986
|
this.set(key, newArr);
|
|
8701
8987
|
};
|
|
@@ -8703,9 +8989,9 @@ ComponentHandle.prototype.push = function(key, val) {
|
|
|
8703
8989
|
/**
|
|
8704
8990
|
* Splice an array in state. Clones the array.
|
|
8705
8991
|
*/
|
|
8706
|
-
|
|
8992
|
+
_chp.splice = function(key, start, deleteCount) {
|
|
8707
8993
|
var arr = this.get(key);
|
|
8708
|
-
var newArr =
|
|
8994
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
8709
8995
|
var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
|
|
8710
8996
|
Array.prototype.splice.apply(newArr, args);
|
|
8711
8997
|
this.set(key, newArr);
|
|
@@ -8713,7 +8999,7 @@ ComponentHandle.prototype.splice = function(key, start, deleteCount) {
|
|
|
8713
8999
|
|
|
8714
9000
|
// ── Scheduling ──
|
|
8715
9001
|
|
|
8716
|
-
|
|
9002
|
+
_chp._scheduleDirty = function() {
|
|
8717
9003
|
if (!this._scheduled) {
|
|
8718
9004
|
this._scheduled = true;
|
|
8719
9005
|
bw._dirtyComponents.push(this);
|
|
@@ -8728,17 +9014,17 @@ ComponentHandle.prototype._scheduleDirty = function() {
|
|
|
8728
9014
|
* Creates binding descriptors with refIds for targeted DOM updates.
|
|
8729
9015
|
* @private
|
|
8730
9016
|
*/
|
|
8731
|
-
|
|
9017
|
+
_chp._compileBindings = function() {
|
|
8732
9018
|
this._bindings = [];
|
|
8733
9019
|
this._refCounter = 0;
|
|
8734
|
-
var stateKeys =
|
|
9020
|
+
var stateKeys = _keys(this._state);
|
|
8735
9021
|
var self = this;
|
|
8736
9022
|
|
|
8737
9023
|
function walkTaco(taco, path) {
|
|
8738
|
-
if (taco
|
|
9024
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
8739
9025
|
|
|
8740
9026
|
// Check content for bindings
|
|
8741
|
-
if (
|
|
9027
|
+
if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
|
|
8742
9028
|
var refId = 'bw_ref_' + self._refCounter++;
|
|
8743
9029
|
var parsed = bw._parseBindings(taco.c);
|
|
8744
9030
|
var deps = [];
|
|
@@ -8760,10 +9046,10 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
8760
9046
|
// Check attributes for bindings
|
|
8761
9047
|
if (taco.a) {
|
|
8762
9048
|
for (var attrName in taco.a) {
|
|
8763
|
-
if (!
|
|
9049
|
+
if (!_hop.call(taco.a, attrName)) continue;
|
|
8764
9050
|
if (attrName === 'data-bw_ref') continue;
|
|
8765
9051
|
var attrVal = taco.a[attrName];
|
|
8766
|
-
if (
|
|
9052
|
+
if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
|
|
8767
9053
|
var refId2 = 'bw_ref_' + self._refCounter++;
|
|
8768
9054
|
var parsed2 = bw._parseBindings(attrVal);
|
|
8769
9055
|
var deps2 = [];
|
|
@@ -8789,9 +9075,27 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
8789
9075
|
}
|
|
8790
9076
|
|
|
8791
9077
|
// Recurse into children
|
|
8792
|
-
if (
|
|
9078
|
+
if (_isA(taco.c)) {
|
|
8793
9079
|
for (var i = 0; i < taco.c.length; i++) {
|
|
8794
|
-
|
|
9080
|
+
// Wrap string children with ${expr} in a span so patches target the span, not the parent
|
|
9081
|
+
if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
|
|
9082
|
+
var mixedRefId = 'bw_ref_' + self._refCounter++;
|
|
9083
|
+
var mixedParsed = bw._parseBindings(taco.c[i]);
|
|
9084
|
+
var mixedDeps = [];
|
|
9085
|
+
for (var mi = 0; mi < mixedParsed.length; mi++) {
|
|
9086
|
+
mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
|
|
9087
|
+
}
|
|
9088
|
+
self._bindings.push({
|
|
9089
|
+
expr: taco.c[i],
|
|
9090
|
+
type: 'content',
|
|
9091
|
+
refId: mixedRefId,
|
|
9092
|
+
deps: mixedDeps,
|
|
9093
|
+
template: taco.c[i]
|
|
9094
|
+
});
|
|
9095
|
+
// Replace string with a span wrapper so textContent targets the span only
|
|
9096
|
+
taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
|
|
9097
|
+
}
|
|
9098
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
8795
9099
|
walkTaco(taco.c[i], path.concat(i));
|
|
8796
9100
|
}
|
|
8797
9101
|
// Handle bw.when/bw.each markers
|
|
@@ -8826,7 +9130,7 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
8826
9130
|
taco.c[i]._refId = eachRefId;
|
|
8827
9131
|
}
|
|
8828
9132
|
}
|
|
8829
|
-
} else if (taco.c
|
|
9133
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
8830
9134
|
walkTaco(taco.c, path.concat(0));
|
|
8831
9135
|
}
|
|
8832
9136
|
|
|
@@ -8842,7 +9146,7 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
8842
9146
|
* Build ref map from the live DOM after createDOM.
|
|
8843
9147
|
* @private
|
|
8844
9148
|
*/
|
|
8845
|
-
|
|
9149
|
+
_chp._collectRefs = function() {
|
|
8846
9150
|
this._bw_refs = {};
|
|
8847
9151
|
if (!this.element) return;
|
|
8848
9152
|
var els = this.element.querySelectorAll('[data-bw_ref]');
|
|
@@ -8863,7 +9167,7 @@ ComponentHandle.prototype._collectRefs = function() {
|
|
|
8863
9167
|
* Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
|
|
8864
9168
|
* @param {Element} parentEl - DOM element to mount into
|
|
8865
9169
|
*/
|
|
8866
|
-
|
|
9170
|
+
_chp.mount = function(parentEl) {
|
|
8867
9171
|
// willMount hook
|
|
8868
9172
|
if (this._hooks.willMount) this._hooks.willMount(this);
|
|
8869
9173
|
|
|
@@ -8885,7 +9189,7 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
8885
9189
|
// Register named actions in function registry
|
|
8886
9190
|
var self = this;
|
|
8887
9191
|
for (var actionName in this._actions) {
|
|
8888
|
-
if (
|
|
9192
|
+
if (_hop.call(this._actions, actionName)) {
|
|
8889
9193
|
var registeredName = this._bwId + '_' + actionName;
|
|
8890
9194
|
(function(aName) {
|
|
8891
9195
|
bw.funcRegister(function(evt) {
|
|
@@ -8904,6 +9208,11 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
8904
9208
|
this.element = bw.createDOM(tacoForDOM);
|
|
8905
9209
|
this.element._bwComponentHandle = this;
|
|
8906
9210
|
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
9211
|
+
|
|
9212
|
+
// Restore o.render from original TACO (stripped by _tacoForDOM)
|
|
9213
|
+
if (this.taco.o && this.taco.o.render) {
|
|
9214
|
+
this.element._bw_render = this.taco.o.render;
|
|
9215
|
+
}
|
|
8907
9216
|
if (this._userTag) {
|
|
8908
9217
|
this.element.classList.add(this._userTag);
|
|
8909
9218
|
}
|
|
@@ -8919,6 +9228,16 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
8919
9228
|
|
|
8920
9229
|
this.mounted = true;
|
|
8921
9230
|
|
|
9231
|
+
// Scan for child ComponentHandles and link parent/child (Bug #5)
|
|
9232
|
+
var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
|
|
9233
|
+
for (var ci = 0; ci < childEls.length; ci++) {
|
|
9234
|
+
var ch = childEls[ci]._bwComponentHandle;
|
|
9235
|
+
if (ch && ch !== this && !ch._parent) {
|
|
9236
|
+
ch._parent = this;
|
|
9237
|
+
this._children.push(ch);
|
|
9238
|
+
}
|
|
9239
|
+
}
|
|
9240
|
+
|
|
8922
9241
|
// mounted hook (backward compat: fn.length === 2 wraps (el, state))
|
|
8923
9242
|
if (this._hooks.mounted) {
|
|
8924
9243
|
if (this._hooks.mounted.length === 2) {
|
|
@@ -8927,16 +9246,21 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
8927
9246
|
this._hooks.mounted(this);
|
|
8928
9247
|
}
|
|
8929
9248
|
}
|
|
9249
|
+
|
|
9250
|
+
// Invoke o.render on initial mount (if present)
|
|
9251
|
+
if (this.element._bw_render) {
|
|
9252
|
+
this.element._bw_render(this.element, this._state);
|
|
9253
|
+
}
|
|
8930
9254
|
};
|
|
8931
9255
|
|
|
8932
9256
|
/**
|
|
8933
9257
|
* Prepare TACO for initial render: resolve when/each markers.
|
|
8934
9258
|
* @private
|
|
8935
9259
|
*/
|
|
8936
|
-
|
|
8937
|
-
if (!taco
|
|
9260
|
+
_chp._prepareTaco = function(taco) {
|
|
9261
|
+
if (!_is(taco, 'object')) return;
|
|
8938
9262
|
|
|
8939
|
-
if (
|
|
9263
|
+
if (_isA(taco.c)) {
|
|
8940
9264
|
for (var i = taco.c.length - 1; i >= 0; i--) {
|
|
8941
9265
|
var child = taco.c[i];
|
|
8942
9266
|
if (child && child._bwWhen) {
|
|
@@ -8961,18 +9285,18 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
|
|
|
8961
9285
|
var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
8962
9286
|
var arr = bw._evaluatePath(this._state, eachExprStr);
|
|
8963
9287
|
var items = [];
|
|
8964
|
-
if (
|
|
9288
|
+
if (_isA(arr)) {
|
|
8965
9289
|
for (var j = 0; j < arr.length; j++) {
|
|
8966
9290
|
items.push(child.factory(arr[j], j));
|
|
8967
9291
|
}
|
|
8968
9292
|
}
|
|
8969
9293
|
taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
|
|
8970
9294
|
}
|
|
8971
|
-
if (taco.c[i]
|
|
9295
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
8972
9296
|
this._prepareTaco(taco.c[i]);
|
|
8973
9297
|
}
|
|
8974
9298
|
}
|
|
8975
|
-
} else if (taco.c
|
|
9299
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
8976
9300
|
this._prepareTaco(taco.c);
|
|
8977
9301
|
}
|
|
8978
9302
|
};
|
|
@@ -8981,12 +9305,12 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
|
|
|
8981
9305
|
* Wire action name strings (in onclick etc.) to dispatch function calls.
|
|
8982
9306
|
* @private
|
|
8983
9307
|
*/
|
|
8984
|
-
|
|
8985
|
-
if (!taco
|
|
9308
|
+
_chp._wireActions = function(taco) {
|
|
9309
|
+
if (!_is(taco, 'object') || !taco.t) return;
|
|
8986
9310
|
if (taco.a) {
|
|
8987
9311
|
for (var key in taco.a) {
|
|
8988
|
-
if (!
|
|
8989
|
-
if (key.startsWith('on') &&
|
|
9312
|
+
if (!_hop.call(taco.a, key)) continue;
|
|
9313
|
+
if (key.startsWith('on') && _is(taco.a[key], 'string')) {
|
|
8990
9314
|
var actionName = taco.a[key];
|
|
8991
9315
|
if (actionName in this._actions) {
|
|
8992
9316
|
var registeredName = this._bwId + '_' + actionName;
|
|
@@ -9000,11 +9324,11 @@ ComponentHandle.prototype._wireActions = function(taco) {
|
|
|
9000
9324
|
}
|
|
9001
9325
|
}
|
|
9002
9326
|
}
|
|
9003
|
-
if (
|
|
9327
|
+
if (_isA(taco.c)) {
|
|
9004
9328
|
for (var i = 0; i < taco.c.length; i++) {
|
|
9005
9329
|
this._wireActions(taco.c[i]);
|
|
9006
9330
|
}
|
|
9007
|
-
} else if (taco.c
|
|
9331
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
9008
9332
|
this._wireActions(taco.c);
|
|
9009
9333
|
}
|
|
9010
9334
|
};
|
|
@@ -9013,7 +9337,7 @@ ComponentHandle.prototype._wireActions = function(taco) {
|
|
|
9013
9337
|
* Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
|
|
9014
9338
|
* @private
|
|
9015
9339
|
*/
|
|
9016
|
-
|
|
9340
|
+
_chp._deepCloneTaco = function(taco) {
|
|
9017
9341
|
if (taco == null) return taco;
|
|
9018
9342
|
// Preserve _bwWhen / _bwEach markers (contain functions)
|
|
9019
9343
|
if (taco._bwWhen) {
|
|
@@ -9025,18 +9349,18 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
|
|
|
9025
9349
|
if (taco._bwEach) {
|
|
9026
9350
|
return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
|
|
9027
9351
|
}
|
|
9028
|
-
if (
|
|
9352
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
9029
9353
|
var result = { t: taco.t };
|
|
9030
9354
|
if (taco.a) {
|
|
9031
9355
|
result.a = {};
|
|
9032
9356
|
for (var k in taco.a) {
|
|
9033
|
-
if (
|
|
9357
|
+
if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
|
|
9034
9358
|
}
|
|
9035
9359
|
}
|
|
9036
9360
|
if (taco.c != null) {
|
|
9037
|
-
if (
|
|
9361
|
+
if (_isA(taco.c)) {
|
|
9038
9362
|
result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
|
|
9039
|
-
} else if (
|
|
9363
|
+
} else if (_is(taco.c, 'object')) {
|
|
9040
9364
|
result.c = this._deepCloneTaco(taco.c);
|
|
9041
9365
|
} else {
|
|
9042
9366
|
result.c = taco.c;
|
|
@@ -9050,27 +9374,31 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
|
|
|
9050
9374
|
* Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
|
|
9051
9375
|
* @private
|
|
9052
9376
|
*/
|
|
9053
|
-
|
|
9054
|
-
if (!taco
|
|
9377
|
+
_chp._tacoForDOM = function(taco) {
|
|
9378
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
9055
9379
|
var result = { t: taco.t };
|
|
9056
9380
|
if (taco.a) result.a = taco.a;
|
|
9057
9381
|
if (taco.c != null) {
|
|
9058
|
-
if (
|
|
9382
|
+
if (_isA(taco.c)) {
|
|
9059
9383
|
result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
|
|
9060
|
-
} else if (
|
|
9384
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
9061
9385
|
result.c = this._tacoForDOM(taco.c);
|
|
9062
9386
|
} else {
|
|
9063
9387
|
result.c = taco.c;
|
|
9064
9388
|
}
|
|
9065
9389
|
}
|
|
9066
9390
|
// Intentionally strip o (no mounted/unmount/state/render on sub-elements)
|
|
9391
|
+
if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
|
|
9392
|
+
_cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
|
|
9393
|
+
'>. Use onclick attribute or bw.component() for child interactivity.');
|
|
9394
|
+
}
|
|
9067
9395
|
return result;
|
|
9068
9396
|
};
|
|
9069
9397
|
|
|
9070
9398
|
/**
|
|
9071
9399
|
* Unmount: remove from DOM, deactivate, preserve state for re-mount.
|
|
9072
9400
|
*/
|
|
9073
|
-
|
|
9401
|
+
_chp.unmount = function() {
|
|
9074
9402
|
if (!this.mounted) return;
|
|
9075
9403
|
|
|
9076
9404
|
// unmount hook
|
|
@@ -9105,12 +9433,23 @@ ComponentHandle.prototype.unmount = function() {
|
|
|
9105
9433
|
/**
|
|
9106
9434
|
* Destroy: unmount + clear state + unregister actions.
|
|
9107
9435
|
*/
|
|
9108
|
-
|
|
9436
|
+
_chp.destroy = function() {
|
|
9109
9437
|
// willDestroy hook
|
|
9110
9438
|
if (this._hooks.willDestroy) {
|
|
9111
9439
|
this._hooks.willDestroy(this);
|
|
9112
9440
|
}
|
|
9113
9441
|
|
|
9442
|
+
// Cascade destroy to children depth-first (Bug #5)
|
|
9443
|
+
for (var ci = this._children.length - 1; ci >= 0; ci--) {
|
|
9444
|
+
this._children[ci].destroy();
|
|
9445
|
+
}
|
|
9446
|
+
this._children = [];
|
|
9447
|
+
if (this._parent) {
|
|
9448
|
+
var idx = this._parent._children.indexOf(this);
|
|
9449
|
+
if (idx >= 0) this._parent._children.splice(idx, 1);
|
|
9450
|
+
this._parent = null;
|
|
9451
|
+
}
|
|
9452
|
+
|
|
9114
9453
|
this.unmount();
|
|
9115
9454
|
|
|
9116
9455
|
// Unregister actions from function registry
|
|
@@ -9137,12 +9476,36 @@ ComponentHandle.prototype.destroy = function() {
|
|
|
9137
9476
|
* Flush dirty state: resolve changed bindings and apply to DOM.
|
|
9138
9477
|
* @private
|
|
9139
9478
|
*/
|
|
9140
|
-
|
|
9479
|
+
_chp._flush = function() {
|
|
9141
9480
|
this._scheduled = false;
|
|
9142
|
-
var changedKeys =
|
|
9481
|
+
var changedKeys = _keys(this._dirtyKeys);
|
|
9143
9482
|
this._dirtyKeys = {};
|
|
9144
9483
|
if (changedKeys.length === 0 || !this.mounted) return;
|
|
9145
9484
|
|
|
9485
|
+
// Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
|
|
9486
|
+
// rebuild the TACO from the factory with merged state (Bug #6)
|
|
9487
|
+
if (this._factory) {
|
|
9488
|
+
var rebuildNeeded = false;
|
|
9489
|
+
for (var fi = 0; fi < changedKeys.length; fi++) {
|
|
9490
|
+
if (_hop.call(this._factory.props, changedKeys[fi])) {
|
|
9491
|
+
rebuildNeeded = true; break;
|
|
9492
|
+
}
|
|
9493
|
+
}
|
|
9494
|
+
if (rebuildNeeded) {
|
|
9495
|
+
var merged = {};
|
|
9496
|
+
for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
|
|
9497
|
+
for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
|
|
9498
|
+
this._factory.props = merged;
|
|
9499
|
+
var newTaco = bw.make(this._factory.type, merged);
|
|
9500
|
+
newTaco._bwFactory = this._factory;
|
|
9501
|
+
this.taco = newTaco;
|
|
9502
|
+
this._originalTaco = this._deepCloneTaco(newTaco);
|
|
9503
|
+
this._render();
|
|
9504
|
+
if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
|
|
9505
|
+
return;
|
|
9506
|
+
}
|
|
9507
|
+
}
|
|
9508
|
+
|
|
9146
9509
|
// willUpdate hook
|
|
9147
9510
|
if (this._hooks.willUpdate) {
|
|
9148
9511
|
this._hooks.willUpdate(this, changedKeys);
|
|
@@ -9181,7 +9544,7 @@ ComponentHandle.prototype._flush = function() {
|
|
|
9181
9544
|
* Returns list of patches to apply.
|
|
9182
9545
|
* @private
|
|
9183
9546
|
*/
|
|
9184
|
-
|
|
9547
|
+
_chp._resolveBindings = function(changedKeys) {
|
|
9185
9548
|
var patches = [];
|
|
9186
9549
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
9187
9550
|
var b = this._bindings[i];
|
|
@@ -9217,11 +9580,14 @@ ComponentHandle.prototype._resolveBindings = function(changedKeys) {
|
|
|
9217
9580
|
* Apply patches to DOM.
|
|
9218
9581
|
* @private
|
|
9219
9582
|
*/
|
|
9220
|
-
|
|
9583
|
+
_chp._applyPatches = function(patches) {
|
|
9221
9584
|
for (var i = 0; i < patches.length; i++) {
|
|
9222
9585
|
var p = patches[i];
|
|
9223
9586
|
var el = this._bw_refs[p.refId];
|
|
9224
|
-
if (!el)
|
|
9587
|
+
if (!el) {
|
|
9588
|
+
if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
|
|
9589
|
+
continue;
|
|
9590
|
+
}
|
|
9225
9591
|
if (p.type === 'content') {
|
|
9226
9592
|
el.textContent = p.value;
|
|
9227
9593
|
} else if (p.type === 'attribute') {
|
|
@@ -9238,7 +9604,7 @@ ComponentHandle.prototype._applyPatches = function(patches) {
|
|
|
9238
9604
|
* Resolve all bindings and apply (used for initial render).
|
|
9239
9605
|
* @private
|
|
9240
9606
|
*/
|
|
9241
|
-
|
|
9607
|
+
_chp._resolveAndApplyAll = function() {
|
|
9242
9608
|
var patches = [];
|
|
9243
9609
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
9244
9610
|
var b = this._bindings[i];
|
|
@@ -9261,7 +9627,7 @@ ComponentHandle.prototype._resolveAndApplyAll = function() {
|
|
|
9261
9627
|
* Full re-render for structural changes (when/each branch switches).
|
|
9262
9628
|
* @private
|
|
9263
9629
|
*/
|
|
9264
|
-
|
|
9630
|
+
_chp._render = function() {
|
|
9265
9631
|
if (!this.element || !this.element.parentNode) return;
|
|
9266
9632
|
var parent = this.element.parentNode;
|
|
9267
9633
|
var nextSibling = this.element.nextSibling;
|
|
@@ -9301,7 +9667,7 @@ ComponentHandle.prototype._render = function() {
|
|
|
9301
9667
|
* @param {string} event - Event name (e.g., 'click')
|
|
9302
9668
|
* @param {Function} handler - Event handler
|
|
9303
9669
|
*/
|
|
9304
|
-
|
|
9670
|
+
_chp.on = function(event, handler) {
|
|
9305
9671
|
if (this.element) {
|
|
9306
9672
|
this.element.addEventListener(event, handler);
|
|
9307
9673
|
}
|
|
@@ -9313,7 +9679,7 @@ ComponentHandle.prototype.on = function(event, handler) {
|
|
|
9313
9679
|
* @param {string} event - Event name
|
|
9314
9680
|
* @param {Function} handler - Handler to remove
|
|
9315
9681
|
*/
|
|
9316
|
-
|
|
9682
|
+
_chp.off = function(event, handler) {
|
|
9317
9683
|
if (this.element) {
|
|
9318
9684
|
this.element.removeEventListener(event, handler);
|
|
9319
9685
|
}
|
|
@@ -9328,7 +9694,7 @@ ComponentHandle.prototype.off = function(event, handler) {
|
|
|
9328
9694
|
* @param {Function} handler - Handler function
|
|
9329
9695
|
* @returns {Function} Unsubscribe function
|
|
9330
9696
|
*/
|
|
9331
|
-
|
|
9697
|
+
_chp.sub = function(topic, handler) {
|
|
9332
9698
|
var unsub = bw.sub(topic, handler);
|
|
9333
9699
|
this._subs.push(unsub);
|
|
9334
9700
|
return unsub;
|
|
@@ -9339,10 +9705,10 @@ ComponentHandle.prototype.sub = function(topic, handler) {
|
|
|
9339
9705
|
* @param {string} name - Action name
|
|
9340
9706
|
* @param {...*} args - Arguments passed after comp
|
|
9341
9707
|
*/
|
|
9342
|
-
|
|
9708
|
+
_chp.action = function(name) {
|
|
9343
9709
|
var fn = this._actions[name];
|
|
9344
9710
|
if (!fn) {
|
|
9345
|
-
|
|
9711
|
+
_cw('ComponentHandle.action: unknown action "' + name + '"');
|
|
9346
9712
|
return;
|
|
9347
9713
|
}
|
|
9348
9714
|
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
|
|
@@ -9354,7 +9720,7 @@ ComponentHandle.prototype.action = function(name) {
|
|
|
9354
9720
|
* @param {string} sel - CSS selector
|
|
9355
9721
|
* @returns {Element|null}
|
|
9356
9722
|
*/
|
|
9357
|
-
|
|
9723
|
+
_chp.select = function(sel) {
|
|
9358
9724
|
return this.element ? this.element.querySelector(sel) : null;
|
|
9359
9725
|
};
|
|
9360
9726
|
|
|
@@ -9363,7 +9729,7 @@ ComponentHandle.prototype.select = function(sel) {
|
|
|
9363
9729
|
* @param {string} sel - CSS selector
|
|
9364
9730
|
* @returns {Element[]}
|
|
9365
9731
|
*/
|
|
9366
|
-
|
|
9732
|
+
_chp.selectAll = function(sel) {
|
|
9367
9733
|
if (!this.element) return [];
|
|
9368
9734
|
return Array.prototype.slice.call(this.element.querySelectorAll(sel));
|
|
9369
9735
|
};
|
|
@@ -9374,7 +9740,7 @@ ComponentHandle.prototype.selectAll = function(sel) {
|
|
|
9374
9740
|
* @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
|
|
9375
9741
|
* @returns {ComponentHandle} this (for chaining)
|
|
9376
9742
|
*/
|
|
9377
|
-
|
|
9743
|
+
_chp.userTag = function(tag) {
|
|
9378
9744
|
this._userTag = tag;
|
|
9379
9745
|
if (this.element) {
|
|
9380
9746
|
this.element.classList.add(tag);
|
|
@@ -9475,14 +9841,399 @@ bw.message = function(target, action, data) {
|
|
|
9475
9841
|
}
|
|
9476
9842
|
if (!el || !el._bwComponentHandle) return false;
|
|
9477
9843
|
var comp = el._bwComponentHandle;
|
|
9478
|
-
if (
|
|
9479
|
-
|
|
9844
|
+
if (!_is(comp[action], 'function')) {
|
|
9845
|
+
_cw('bw.message: unknown action "' + action + '" on component ' + target);
|
|
9480
9846
|
return false;
|
|
9481
9847
|
}
|
|
9482
9848
|
comp[action](data);
|
|
9483
9849
|
return true;
|
|
9484
9850
|
};
|
|
9485
9851
|
|
|
9852
|
+
// ===================================================================================
|
|
9853
|
+
// bw.clientApply() / bw.clientConnect() — Server-driven UI protocol
|
|
9854
|
+
// ===================================================================================
|
|
9855
|
+
|
|
9856
|
+
/**
|
|
9857
|
+
* Registry of named functions sent via register messages.
|
|
9858
|
+
* Populated by clientApply({ type: 'register', name, body }).
|
|
9859
|
+
* Invoked by clientApply({ type: 'call', name, args }).
|
|
9860
|
+
* @private
|
|
9861
|
+
*/
|
|
9862
|
+
bw._clientFunctions = {};
|
|
9863
|
+
|
|
9864
|
+
/**
|
|
9865
|
+
* Whether exec messages are allowed. Set by clientConnect opts.allowExec.
|
|
9866
|
+
* Default false — exec messages are rejected unless explicitly opted in.
|
|
9867
|
+
* @private
|
|
9868
|
+
*/
|
|
9869
|
+
bw._allowExec = false;
|
|
9870
|
+
|
|
9871
|
+
/**
|
|
9872
|
+
* Built-in client functions available via call() without registration.
|
|
9873
|
+
* @private
|
|
9874
|
+
*/
|
|
9875
|
+
bw._builtinClientFunctions = {
|
|
9876
|
+
scrollTo: function(selector) {
|
|
9877
|
+
var el = bw._el(selector);
|
|
9878
|
+
if (el) el.scrollTop = el.scrollHeight;
|
|
9879
|
+
},
|
|
9880
|
+
focus: function(selector) {
|
|
9881
|
+
var el = bw._el(selector);
|
|
9882
|
+
if (el && _is(el.focus, 'function')) el.focus();
|
|
9883
|
+
},
|
|
9884
|
+
download: function(filename, content, mimeType) {
|
|
9885
|
+
if (typeof document === 'undefined') return;
|
|
9886
|
+
var blob = new Blob([content], { type: mimeType || 'text/plain' });
|
|
9887
|
+
var a = document.createElement('a');
|
|
9888
|
+
a.href = URL.createObjectURL(blob);
|
|
9889
|
+
a.download = filename;
|
|
9890
|
+
a.click();
|
|
9891
|
+
URL.revokeObjectURL(a.href);
|
|
9892
|
+
},
|
|
9893
|
+
clipboard: function(text) {
|
|
9894
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
9895
|
+
navigator.clipboard.writeText(text);
|
|
9896
|
+
}
|
|
9897
|
+
},
|
|
9898
|
+
redirect: function(url) {
|
|
9899
|
+
if (typeof window !== 'undefined') window.location.href = url;
|
|
9900
|
+
},
|
|
9901
|
+
log: function() {
|
|
9902
|
+
console.log.apply(console, arguments);
|
|
9903
|
+
}
|
|
9904
|
+
};
|
|
9905
|
+
|
|
9906
|
+
/**
|
|
9907
|
+
* Parse a bwserve protocol message string, supporting both strict JSON
|
|
9908
|
+
* and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
|
|
9909
|
+
*
|
|
9910
|
+
* The r-prefix format is designed for C/C++ string literals where
|
|
9911
|
+
* double-quote escaping is painful. The parser is a state machine
|
|
9912
|
+
* that walks character by character — not a regex replace.
|
|
9913
|
+
*
|
|
9914
|
+
* Escaping: apostrophes inside single-quoted values must be escaped
|
|
9915
|
+
* with backslash: r{'name':'Barry\'s room'}
|
|
9916
|
+
*
|
|
9917
|
+
* @param {string} str - JSON or r-prefixed relaxed JSON string
|
|
9918
|
+
* @returns {Object} Parsed message object
|
|
9919
|
+
* @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
|
|
9920
|
+
* @category Server
|
|
9921
|
+
*/
|
|
9922
|
+
bw.clientParse = function(str) {
|
|
9923
|
+
str = (str || '').trim();
|
|
9924
|
+
if (str.charAt(0) !== 'r') return JSON.parse(str);
|
|
9925
|
+
str = str.slice(1);
|
|
9926
|
+
|
|
9927
|
+
var out = [];
|
|
9928
|
+
var i = 0;
|
|
9929
|
+
var len = str.length;
|
|
9930
|
+
|
|
9931
|
+
while (i < len) {
|
|
9932
|
+
var ch = str[i];
|
|
9933
|
+
|
|
9934
|
+
if (ch === "'") {
|
|
9935
|
+
// Single-quoted string → emit as double-quoted
|
|
9936
|
+
out.push('"');
|
|
9937
|
+
i++;
|
|
9938
|
+
while (i < len) {
|
|
9939
|
+
var c = str[i];
|
|
9940
|
+
if (c === '\\' && i + 1 < len) {
|
|
9941
|
+
var next = str[i + 1];
|
|
9942
|
+
if (next === "'") {
|
|
9943
|
+
out.push("'"); // \' in input → ' in output
|
|
9944
|
+
} else {
|
|
9945
|
+
out.push('\\');
|
|
9946
|
+
out.push(next);
|
|
9947
|
+
}
|
|
9948
|
+
i += 2;
|
|
9949
|
+
} else if (c === '"') {
|
|
9950
|
+
out.push('\\"');
|
|
9951
|
+
i++;
|
|
9952
|
+
} else if (c === "'") {
|
|
9953
|
+
break;
|
|
9954
|
+
} else {
|
|
9955
|
+
out.push(c);
|
|
9956
|
+
i++;
|
|
9957
|
+
}
|
|
9958
|
+
}
|
|
9959
|
+
out.push('"');
|
|
9960
|
+
i++; // skip closing '
|
|
9961
|
+
|
|
9962
|
+
} else if (ch === '"') {
|
|
9963
|
+
// Double-quoted string — pass through verbatim
|
|
9964
|
+
out.push(ch);
|
|
9965
|
+
i++;
|
|
9966
|
+
while (i < len) {
|
|
9967
|
+
var c2 = str[i];
|
|
9968
|
+
if (c2 === '\\' && i + 1 < len) {
|
|
9969
|
+
out.push(c2);
|
|
9970
|
+
out.push(str[i + 1]);
|
|
9971
|
+
i += 2;
|
|
9972
|
+
} else {
|
|
9973
|
+
out.push(c2);
|
|
9974
|
+
i++;
|
|
9975
|
+
if (c2 === '"') break;
|
|
9976
|
+
}
|
|
9977
|
+
}
|
|
9978
|
+
|
|
9979
|
+
} else if (ch === ',') {
|
|
9980
|
+
// Trailing comma check: skip comma if next non-whitespace is } or ]
|
|
9981
|
+
var j = i + 1;
|
|
9982
|
+
while (j < len && (str[j] === ' ' || str[j] === '\t' || str[j] === '\n' || str[j] === '\r')) j++;
|
|
9983
|
+
if (j < len && (str[j] === '}' || str[j] === ']')) {
|
|
9984
|
+
i++; // skip trailing comma
|
|
9985
|
+
} else {
|
|
9986
|
+
out.push(ch);
|
|
9987
|
+
i++;
|
|
9988
|
+
}
|
|
9989
|
+
|
|
9990
|
+
} else {
|
|
9991
|
+
out.push(ch);
|
|
9992
|
+
i++;
|
|
9993
|
+
}
|
|
9994
|
+
}
|
|
9995
|
+
|
|
9996
|
+
return JSON.parse(out.join(''));
|
|
9997
|
+
};
|
|
9998
|
+
|
|
9999
|
+
/**
|
|
10000
|
+
* Apply a bwserve protocol message to the DOM.
|
|
10001
|
+
*
|
|
10002
|
+
* Dispatches one of 9 message types:
|
|
10003
|
+
* replace — bw.DOM(target, node)
|
|
10004
|
+
* append — target.appendChild(bw.createDOM(node))
|
|
10005
|
+
* remove — bw.cleanup(target); target.remove()
|
|
10006
|
+
* patch — bw.patch(target, content, attr)
|
|
10007
|
+
* batch — iterate ops, call clientApply for each
|
|
10008
|
+
* message — bw.message(target, action, data)
|
|
10009
|
+
* register — store a named function for later call()
|
|
10010
|
+
* call — invoke a registered or built-in function
|
|
10011
|
+
* exec — execute arbitrary JS (requires allowExec)
|
|
10012
|
+
*
|
|
10013
|
+
* Target resolution:
|
|
10014
|
+
* Starts with '#' or '.' → CSS selector (querySelector)
|
|
10015
|
+
* Otherwise → getElementById, then bw._el fallback
|
|
10016
|
+
*
|
|
10017
|
+
* @param {Object} msg - Protocol message
|
|
10018
|
+
* @returns {boolean} true if the message was applied successfully
|
|
10019
|
+
* @category Server
|
|
10020
|
+
*/
|
|
10021
|
+
bw.clientApply = function(msg) {
|
|
10022
|
+
if (!msg || !msg.type) return false;
|
|
10023
|
+
|
|
10024
|
+
var type = msg.type;
|
|
10025
|
+
var target = msg.target;
|
|
10026
|
+
|
|
10027
|
+
if (type === 'replace') {
|
|
10028
|
+
var el = bw._el(target);
|
|
10029
|
+
if (!el) return false;
|
|
10030
|
+
bw.DOM(el, msg.node);
|
|
10031
|
+
return true;
|
|
10032
|
+
|
|
10033
|
+
} else if (type === 'patch') {
|
|
10034
|
+
var patched = bw.patch(target, msg.content, msg.attr);
|
|
10035
|
+
return patched !== null;
|
|
10036
|
+
|
|
10037
|
+
} else if (type === 'append') {
|
|
10038
|
+
var parent = bw._el(target);
|
|
10039
|
+
if (!parent) return false;
|
|
10040
|
+
var child = bw.createDOM(msg.node);
|
|
10041
|
+
parent.appendChild(child);
|
|
10042
|
+
return true;
|
|
10043
|
+
|
|
10044
|
+
} else if (type === 'remove') {
|
|
10045
|
+
var toRemove = bw._el(target);
|
|
10046
|
+
if (!toRemove) return false;
|
|
10047
|
+
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
10048
|
+
toRemove.remove();
|
|
10049
|
+
return true;
|
|
10050
|
+
|
|
10051
|
+
} else if (type === 'batch') {
|
|
10052
|
+
if (!_isA(msg.ops)) return false;
|
|
10053
|
+
var allOk = true;
|
|
10054
|
+
msg.ops.forEach(function(op) {
|
|
10055
|
+
if (!bw.clientApply(op)) allOk = false;
|
|
10056
|
+
});
|
|
10057
|
+
return allOk;
|
|
10058
|
+
|
|
10059
|
+
} else if (type === 'message') {
|
|
10060
|
+
return bw.message(msg.target, msg.action, msg.data);
|
|
10061
|
+
|
|
10062
|
+
} else if (type === 'register') {
|
|
10063
|
+
if (!msg.name || !msg.body) return false;
|
|
10064
|
+
try {
|
|
10065
|
+
bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
|
|
10066
|
+
return true;
|
|
10067
|
+
} catch (e) {
|
|
10068
|
+
_ce('[bw] register error:', msg.name, e);
|
|
10069
|
+
return false;
|
|
10070
|
+
}
|
|
10071
|
+
|
|
10072
|
+
} else if (type === 'call') {
|
|
10073
|
+
if (!msg.name) return false;
|
|
10074
|
+
var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
|
|
10075
|
+
if (!_is(fn, 'function')) return false;
|
|
10076
|
+
try {
|
|
10077
|
+
var args = _isA(msg.args) ? msg.args : [];
|
|
10078
|
+
fn.apply(null, args);
|
|
10079
|
+
return true;
|
|
10080
|
+
} catch (e) {
|
|
10081
|
+
_ce('[bw] call error:', msg.name, e);
|
|
10082
|
+
return false;
|
|
10083
|
+
}
|
|
10084
|
+
|
|
10085
|
+
} else if (type === 'exec') {
|
|
10086
|
+
if (!bw._allowExec) {
|
|
10087
|
+
_cw('[bw] exec rejected: allowExec is not enabled');
|
|
10088
|
+
return false;
|
|
10089
|
+
}
|
|
10090
|
+
if (!msg.code) return false;
|
|
10091
|
+
try {
|
|
10092
|
+
new Function(msg.code)();
|
|
10093
|
+
return true;
|
|
10094
|
+
} catch (e) {
|
|
10095
|
+
_ce('[bw] exec error:', e);
|
|
10096
|
+
return false;
|
|
10097
|
+
}
|
|
10098
|
+
}
|
|
10099
|
+
|
|
10100
|
+
return false;
|
|
10101
|
+
};
|
|
10102
|
+
|
|
10103
|
+
/**
|
|
10104
|
+
* Connect to a bwserve SSE endpoint and apply protocol messages automatically.
|
|
10105
|
+
*
|
|
10106
|
+
* Returns a connection object with sendAction(), on(), and close() methods.
|
|
10107
|
+
*
|
|
10108
|
+
* @param {string} url - SSE endpoint URL (e.g., '/__bw/events/client-1')
|
|
10109
|
+
* @param {Object} [opts] - Connection options
|
|
10110
|
+
* @param {string} [opts.transport='sse'] - Transport type: 'sse' (default) or 'poll'
|
|
10111
|
+
* @param {number} [opts.interval=2000] - Poll interval in ms (only for 'poll' transport)
|
|
10112
|
+
* @param {string} [opts.actionUrl] - POST endpoint for actions (default: derived from url)
|
|
10113
|
+
* @param {boolean} [opts.reconnect=true] - Auto-reconnect on disconnect
|
|
10114
|
+
* @param {boolean} [opts.allowExec=false] - Enable exec message type (arbitrary JS execution)
|
|
10115
|
+
* @param {Function} [opts.onStatus] - Status callback: 'connecting'|'connected'|'disconnected'
|
|
10116
|
+
* @param {Function} [opts.onMessage] - Raw message callback (before clientApply)
|
|
10117
|
+
* @returns {Object} Connection object { sendAction, on, close, status }
|
|
10118
|
+
* @category Server
|
|
10119
|
+
*/
|
|
10120
|
+
bw.clientConnect = function(url, opts) {
|
|
10121
|
+
opts = opts || {};
|
|
10122
|
+
var transport = opts.transport || 'sse';
|
|
10123
|
+
var actionUrl = opts.actionUrl || url.replace(/\/events\//, '/action/');
|
|
10124
|
+
var reconnect = opts.reconnect !== false;
|
|
10125
|
+
var onStatus = opts.onStatus || function() {};
|
|
10126
|
+
var onMessage = opts.onMessage || null;
|
|
10127
|
+
var handlers = {};
|
|
10128
|
+
// Set the global allowExec flag from connection options
|
|
10129
|
+
bw._allowExec = !!opts.allowExec;
|
|
10130
|
+
var conn = {
|
|
10131
|
+
status: 'connecting',
|
|
10132
|
+
_es: null,
|
|
10133
|
+
_pollTimer: null
|
|
10134
|
+
};
|
|
10135
|
+
|
|
10136
|
+
function setStatus(s) {
|
|
10137
|
+
conn.status = s;
|
|
10138
|
+
onStatus(s);
|
|
10139
|
+
}
|
|
10140
|
+
|
|
10141
|
+
function handleMessage(data) {
|
|
10142
|
+
try {
|
|
10143
|
+
var msg = _is(data, 'string') ? bw.clientParse(data) : data;
|
|
10144
|
+
if (onMessage) onMessage(msg);
|
|
10145
|
+
if (handlers.message) handlers.message(msg);
|
|
10146
|
+
bw.clientApply(msg);
|
|
10147
|
+
} catch (e) {
|
|
10148
|
+
if (handlers.error) handlers.error(e);
|
|
10149
|
+
}
|
|
10150
|
+
}
|
|
10151
|
+
|
|
10152
|
+
if (transport === 'sse' && typeof EventSource !== 'undefined') {
|
|
10153
|
+
setStatus('connecting');
|
|
10154
|
+
var es = new EventSource(url);
|
|
10155
|
+
conn._es = es;
|
|
10156
|
+
|
|
10157
|
+
es.onopen = function() {
|
|
10158
|
+
setStatus('connected');
|
|
10159
|
+
if (handlers.open) handlers.open();
|
|
10160
|
+
};
|
|
10161
|
+
|
|
10162
|
+
es.onmessage = function(e) {
|
|
10163
|
+
handleMessage(e.data);
|
|
10164
|
+
};
|
|
10165
|
+
|
|
10166
|
+
es.onerror = function() {
|
|
10167
|
+
if (conn.status === 'connected') {
|
|
10168
|
+
setStatus('disconnected');
|
|
10169
|
+
}
|
|
10170
|
+
if (handlers.error) handlers.error(new Error('SSE connection error'));
|
|
10171
|
+
if (!reconnect) {
|
|
10172
|
+
es.close();
|
|
10173
|
+
}
|
|
10174
|
+
// EventSource auto-reconnects by default when reconnect=true
|
|
10175
|
+
};
|
|
10176
|
+
} else if (transport === 'poll') {
|
|
10177
|
+
var interval = opts.interval || 2000;
|
|
10178
|
+
setStatus('connected');
|
|
10179
|
+
conn._pollTimer = setInterval(function() {
|
|
10180
|
+
fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
|
|
10181
|
+
if (_isA(msgs)) {
|
|
10182
|
+
msgs.forEach(handleMessage);
|
|
10183
|
+
} else if (msgs && msgs.type) {
|
|
10184
|
+
handleMessage(msgs);
|
|
10185
|
+
}
|
|
10186
|
+
}).catch(function(e) {
|
|
10187
|
+
if (handlers.error) handlers.error(e);
|
|
10188
|
+
});
|
|
10189
|
+
}, interval);
|
|
10190
|
+
}
|
|
10191
|
+
|
|
10192
|
+
/**
|
|
10193
|
+
* Send an action to the server via POST.
|
|
10194
|
+
* @param {string} action - Action name
|
|
10195
|
+
* @param {Object} [data] - Action payload
|
|
10196
|
+
*/
|
|
10197
|
+
conn.sendAction = function(action, data) {
|
|
10198
|
+
var body = JSON.stringify({ type: 'action', action: action, data: data || {} });
|
|
10199
|
+
fetch(actionUrl, {
|
|
10200
|
+
method: 'POST',
|
|
10201
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10202
|
+
body: body
|
|
10203
|
+
}).catch(function(e) {
|
|
10204
|
+
if (handlers.error) handlers.error(e);
|
|
10205
|
+
});
|
|
10206
|
+
};
|
|
10207
|
+
|
|
10208
|
+
/**
|
|
10209
|
+
* Register an event handler.
|
|
10210
|
+
* @param {string} event - 'open'|'message'|'error'|'close'
|
|
10211
|
+
* @param {Function} handler
|
|
10212
|
+
*/
|
|
10213
|
+
conn.on = function(event, handler) {
|
|
10214
|
+
handlers[event] = handler;
|
|
10215
|
+
return conn;
|
|
10216
|
+
};
|
|
10217
|
+
|
|
10218
|
+
/**
|
|
10219
|
+
* Close the connection.
|
|
10220
|
+
*/
|
|
10221
|
+
conn.close = function() {
|
|
10222
|
+
if (conn._es) {
|
|
10223
|
+
conn._es.close();
|
|
10224
|
+
conn._es = null;
|
|
10225
|
+
}
|
|
10226
|
+
if (conn._pollTimer) {
|
|
10227
|
+
clearInterval(conn._pollTimer);
|
|
10228
|
+
conn._pollTimer = null;
|
|
10229
|
+
}
|
|
10230
|
+
setStatus('disconnected');
|
|
10231
|
+
if (handlers.close) handlers.close();
|
|
10232
|
+
};
|
|
10233
|
+
|
|
10234
|
+
return conn;
|
|
10235
|
+
};
|
|
10236
|
+
|
|
9486
10237
|
// ===================================================================================
|
|
9487
10238
|
// bw.inspect() — Debug utility
|
|
9488
10239
|
// ===================================================================================
|
|
@@ -9509,33 +10260,33 @@ bw.inspect = function(target) {
|
|
|
9509
10260
|
el = target.element;
|
|
9510
10261
|
comp = target;
|
|
9511
10262
|
} else {
|
|
9512
|
-
if (
|
|
10263
|
+
if (_is(target, 'string')) {
|
|
9513
10264
|
el = bw.$(target)[0];
|
|
9514
10265
|
}
|
|
9515
10266
|
if (!el) {
|
|
9516
|
-
|
|
10267
|
+
_cw('bw.inspect: element not found');
|
|
9517
10268
|
return null;
|
|
9518
10269
|
}
|
|
9519
10270
|
comp = el._bwComponentHandle;
|
|
9520
10271
|
}
|
|
9521
10272
|
if (!comp) {
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
10273
|
+
_cl('bw.inspect: no ComponentHandle on this element');
|
|
10274
|
+
_cl(' Tag:', el.tagName);
|
|
10275
|
+
_cl(' Classes:', el.className);
|
|
10276
|
+
_cl(' _bw_state:', el._bw_state || '(none)');
|
|
9526
10277
|
return null;
|
|
9527
10278
|
}
|
|
9528
10279
|
var deps = comp._bindings.reduce(function(s, b) {
|
|
9529
10280
|
return s.concat(b.deps || []);
|
|
9530
10281
|
}, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
|
|
9531
10282
|
console.group('Component: ' + comp._bwId);
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
10283
|
+
_cl('State:', comp._state);
|
|
10284
|
+
_cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
|
|
10285
|
+
_cl('Methods:', _keys(comp._methods));
|
|
10286
|
+
_cl('Actions:', _keys(comp._actions));
|
|
10287
|
+
_cl('User tag:', comp._userTag || '(none)');
|
|
10288
|
+
_cl('Mounted:', comp.mounted);
|
|
10289
|
+
_cl('Element:', comp.element);
|
|
9539
10290
|
console.groupEnd();
|
|
9540
10291
|
return comp;
|
|
9541
10292
|
};
|
|
@@ -9558,8 +10309,8 @@ bw.compile = function(taco) {
|
|
|
9558
10309
|
// Pre-extract all binding expressions
|
|
9559
10310
|
var precompiled = [];
|
|
9560
10311
|
function walkExpressions(node) {
|
|
9561
|
-
if (!node
|
|
9562
|
-
if (
|
|
10312
|
+
if (!_is(node, 'object')) return;
|
|
10313
|
+
if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
|
|
9563
10314
|
var parsed = bw._parseBindings(node.c);
|
|
9564
10315
|
for (var i = 0; i < parsed.length; i++) {
|
|
9565
10316
|
try {
|
|
@@ -9574,9 +10325,9 @@ bw.compile = function(taco) {
|
|
|
9574
10325
|
}
|
|
9575
10326
|
if (node.a) {
|
|
9576
10327
|
for (var key in node.a) {
|
|
9577
|
-
if (
|
|
10328
|
+
if (_hop.call(node.a, key)) {
|
|
9578
10329
|
var v = node.a[key];
|
|
9579
|
-
if (
|
|
10330
|
+
if (_is(v, 'string') && v.indexOf('${') >= 0) {
|
|
9580
10331
|
var parsed2 = bw._parseBindings(v);
|
|
9581
10332
|
for (var j = 0; j < parsed2.length; j++) {
|
|
9582
10333
|
try {
|
|
@@ -9592,9 +10343,9 @@ bw.compile = function(taco) {
|
|
|
9592
10343
|
}
|
|
9593
10344
|
}
|
|
9594
10345
|
}
|
|
9595
|
-
if (
|
|
10346
|
+
if (_isA(node.c)) {
|
|
9596
10347
|
for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
|
|
9597
|
-
} else if (node.c
|
|
10348
|
+
} else if (_is(node.c, 'object') && node.c.t) {
|
|
9598
10349
|
walkExpressions(node.c);
|
|
9599
10350
|
}
|
|
9600
10351
|
}
|
|
@@ -9606,7 +10357,7 @@ bw.compile = function(taco) {
|
|
|
9606
10357
|
handle._precompiledBindings = precompiled;
|
|
9607
10358
|
if (initialState) {
|
|
9608
10359
|
for (var k in initialState) {
|
|
9609
|
-
if (
|
|
10360
|
+
if (_hop.call(initialState, k)) {
|
|
9610
10361
|
handle._state[k] = initialState[k];
|
|
9611
10362
|
}
|
|
9612
10363
|
}
|
|
@@ -9637,18 +10388,18 @@ bw.compile = function(taco) {
|
|
|
9637
10388
|
bw.css = function(rules, options = {}) {
|
|
9638
10389
|
const { minify = false, pretty = !minify } = options;
|
|
9639
10390
|
|
|
9640
|
-
if (
|
|
10391
|
+
if (_is(rules, 'string')) return rules;
|
|
9641
10392
|
|
|
9642
10393
|
let css = '';
|
|
9643
10394
|
const indent = pretty ? ' ' : '';
|
|
9644
10395
|
const newline = pretty ? '\n' : '';
|
|
9645
10396
|
const space = pretty ? ' ' : '';
|
|
9646
10397
|
|
|
9647
|
-
if (
|
|
10398
|
+
if (_isA(rules)) {
|
|
9648
10399
|
css = rules.map(rule => bw.css(rule, options)).join(newline);
|
|
9649
|
-
} else if (
|
|
10400
|
+
} else if (_is(rules, 'object')) {
|
|
9650
10401
|
Object.entries(rules).forEach(([selector, styles]) => {
|
|
9651
|
-
if (
|
|
10402
|
+
if (_is(styles, 'object')) {
|
|
9652
10403
|
// Handle @media, @keyframes, @supports — recurse into nested block
|
|
9653
10404
|
if (selector.charAt(0) === '@') {
|
|
9654
10405
|
const inner = bw.css(styles, options);
|
|
@@ -9697,7 +10448,7 @@ bw.css = function(rules, options = {}) {
|
|
|
9697
10448
|
*/
|
|
9698
10449
|
bw.injectCSS = function(css, options = {}) {
|
|
9699
10450
|
if (!bw._isBrowser) {
|
|
9700
|
-
|
|
10451
|
+
_cw('bw.injectCSS requires a DOM environment');
|
|
9701
10452
|
return null;
|
|
9702
10453
|
}
|
|
9703
10454
|
|
|
@@ -9714,7 +10465,7 @@ bw.injectCSS = function(css, options = {}) {
|
|
|
9714
10465
|
}
|
|
9715
10466
|
|
|
9716
10467
|
// Convert CSS if needed
|
|
9717
|
-
const cssStr =
|
|
10468
|
+
const cssStr = _is(css, 'string') ? css : bw.css(css, options);
|
|
9718
10469
|
|
|
9719
10470
|
// Set or append CSS
|
|
9720
10471
|
if (append && styleEl.textContent) {
|
|
@@ -9744,7 +10495,7 @@ bw.s = function() {
|
|
|
9744
10495
|
var result = {};
|
|
9745
10496
|
for (var i = 0; i < arguments.length; i++) {
|
|
9746
10497
|
var arg = arguments[i];
|
|
9747
|
-
if (arg
|
|
10498
|
+
if (_is(arg, 'object')) Object.assign(result, arg);
|
|
9748
10499
|
}
|
|
9749
10500
|
return result;
|
|
9750
10501
|
};
|
|
@@ -9867,7 +10618,7 @@ bw.u = {
|
|
|
9867
10618
|
bw.responsive = function(selector, breakpoints) {
|
|
9868
10619
|
var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
|
|
9869
10620
|
var parts = [];
|
|
9870
|
-
|
|
10621
|
+
_keys(breakpoints).forEach(function(key) {
|
|
9871
10622
|
var rules = {};
|
|
9872
10623
|
if (key === 'base') {
|
|
9873
10624
|
rules[selector] = breakpoints[key];
|
|
@@ -9939,18 +10690,18 @@ if (bw._isBrowser) {
|
|
|
9939
10690
|
if (!selector) return [];
|
|
9940
10691
|
|
|
9941
10692
|
// Already an array
|
|
9942
|
-
if (
|
|
10693
|
+
if (_isA(selector)) return selector;
|
|
9943
10694
|
|
|
9944
10695
|
// Single element
|
|
9945
10696
|
if (selector.nodeType) return [selector];
|
|
9946
10697
|
|
|
9947
10698
|
// NodeList or HTMLCollection
|
|
9948
|
-
if (selector.length !== undefined &&
|
|
10699
|
+
if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
9949
10700
|
return Array.from(selector);
|
|
9950
10701
|
}
|
|
9951
10702
|
|
|
9952
10703
|
// CSS selector string
|
|
9953
|
-
if (
|
|
10704
|
+
if (_is(selector, 'string')) {
|
|
9954
10705
|
return Array.from(document.querySelectorAll(selector));
|
|
9955
10706
|
}
|
|
9956
10707
|
|
|
@@ -10454,7 +11205,7 @@ bw.makeTable = function(config) {
|
|
|
10454
11205
|
|
|
10455
11206
|
// Auto-detect columns if not provided
|
|
10456
11207
|
const cols = columns || (data.length > 0
|
|
10457
|
-
?
|
|
11208
|
+
? _keys(data[0]).map(key => ({ key, label: key }))
|
|
10458
11209
|
: []);
|
|
10459
11210
|
|
|
10460
11211
|
// Current sort state
|
|
@@ -10469,7 +11220,7 @@ bw.makeTable = function(config) {
|
|
|
10469
11220
|
const bVal = b[currentSortColumn];
|
|
10470
11221
|
|
|
10471
11222
|
// Handle different types
|
|
10472
|
-
if (
|
|
11223
|
+
if (_is(aVal, 'number') && _is(bVal, 'number')) {
|
|
10473
11224
|
return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
10474
11225
|
}
|
|
10475
11226
|
|
|
@@ -10579,7 +11330,7 @@ bw.makeTable = function(config) {
|
|
|
10579
11330
|
bw.makeTableFromArray = function(config) {
|
|
10580
11331
|
const { data = [], headerRow = true, columns, ...rest } = config;
|
|
10581
11332
|
|
|
10582
|
-
if (!
|
|
11333
|
+
if (!_isA(data) || data.length === 0) {
|
|
10583
11334
|
return bw.makeTable({ data: [], columns: columns || [], ...rest });
|
|
10584
11335
|
}
|
|
10585
11336
|
|
|
@@ -10661,7 +11412,7 @@ bw.makeBarChart = function(config) {
|
|
|
10661
11412
|
className = ''
|
|
10662
11413
|
} = config;
|
|
10663
11414
|
|
|
10664
|
-
if (!
|
|
11415
|
+
if (!_isA(data) || data.length === 0) {
|
|
10665
11416
|
return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
|
|
10666
11417
|
}
|
|
10667
11418
|
|
|
@@ -10810,7 +11561,7 @@ bw._componentRegistry = new Map();
|
|
|
10810
11561
|
*/
|
|
10811
11562
|
bw.render = function(element, position, taco) {
|
|
10812
11563
|
// Get target element
|
|
10813
|
-
const targetEl =
|
|
11564
|
+
const targetEl = _is(element, 'string')
|
|
10814
11565
|
? document.querySelector(element)
|
|
10815
11566
|
: element;
|
|
10816
11567
|
|
|
@@ -10960,7 +11711,7 @@ bw.render = function(element, position, taco) {
|
|
|
10960
11711
|
setContent(content) {
|
|
10961
11712
|
this._taco.c = content;
|
|
10962
11713
|
if (this.element) {
|
|
10963
|
-
if (
|
|
11714
|
+
if (_is(content, 'string')) {
|
|
10964
11715
|
this.element.textContent = content;
|
|
10965
11716
|
} else {
|
|
10966
11717
|
// Re-render for complex content
|