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