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
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
/*! bitwrench-lean v2.0.
|
|
1
|
+
/*! bitwrench-lean 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
|
};
|
|
@@ -3334,7 +3346,7 @@ const bw = {
|
|
|
3334
3346
|
__monkey_patch_is_nodejs__: {
|
|
3335
3347
|
_value: 'ignore',
|
|
3336
3348
|
set: function(x) {
|
|
3337
|
-
this._value = (
|
|
3349
|
+
this._value = _is(x, 'boolean') ? x : 'ignore';
|
|
3338
3350
|
},
|
|
3339
3351
|
get: function() {
|
|
3340
3352
|
return this._value;
|
|
@@ -3382,6 +3394,67 @@ Object.defineProperty(bw, '_isBrowser', {
|
|
|
3382
3394
|
configurable: true
|
|
3383
3395
|
});
|
|
3384
3396
|
|
|
3397
|
+
// ── Internal aliases ─────────────────────────────────────────────────────
|
|
3398
|
+
// Short names for frequently-used builtins and internal methods.
|
|
3399
|
+
// Same pattern as v1 (_to = bw.typeOf, etc.).
|
|
3400
|
+
//
|
|
3401
|
+
// Why: Terser can't shorten global property chains (console.warn,
|
|
3402
|
+
// Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
|
|
3403
|
+
// because it can't prove they're side-effect-free. We can, so we alias
|
|
3404
|
+
// them here. Each alias saves bytes in the minified output, and the short
|
|
3405
|
+
// names also reduce visual noise in the hot paths (binding pipeline,
|
|
3406
|
+
// createDOM, etc.).
|
|
3407
|
+
//
|
|
3408
|
+
// Alias Target Sites
|
|
3409
|
+
// ───────── ────────────────────────────────────── ─────
|
|
3410
|
+
// _hop Object.prototype.hasOwnProperty 15
|
|
3411
|
+
// _isA Array.isArray 25
|
|
3412
|
+
// _keys Object.keys 7
|
|
3413
|
+
// _to bw.typeOf (type string) 26
|
|
3414
|
+
// _is type check boolean: _is(x,'string') ~50
|
|
3415
|
+
// _cw console.warn 8
|
|
3416
|
+
// _cl console.log 11
|
|
3417
|
+
// _ce console.error 4
|
|
3418
|
+
// _chp ComponentHandle.prototype 28 (defined after constructor)
|
|
3419
|
+
//
|
|
3420
|
+
// Note: document.createElement etc. are NOT aliased because they require
|
|
3421
|
+
// `this === document` and .bind() would add overhead on every call.
|
|
3422
|
+
// Console aliases use thin wrappers (not direct refs) so test monkey-
|
|
3423
|
+
// patching of console.warn/log/error continues to work.
|
|
3424
|
+
//
|
|
3425
|
+
// `typeof x` for UNDECLARED globals (window, document, process, require,
|
|
3426
|
+
// EventSource, navigator, Promise, __filename, import.meta) MUST stay as
|
|
3427
|
+
// raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
|
|
3428
|
+
//
|
|
3429
|
+
// ── v1 functional type helpers (kept for reference, not currently used) ──
|
|
3430
|
+
// _toa(x, type, trueVal, falseVal) — bw.typeAssign:
|
|
3431
|
+
// returns trueVal if _to(x)===type, else falseVal.
|
|
3432
|
+
// Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
|
|
3433
|
+
// _toc(x, type, trueVal, falseVal) — bw.typeConvert:
|
|
3434
|
+
// same as _toa but if trueVal/falseVal are functions, calls them with x.
|
|
3435
|
+
// Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
|
|
3436
|
+
// Uncomment if pattern frequency justifies them:
|
|
3437
|
+
// var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
|
|
3438
|
+
// 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); };
|
|
3439
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
3440
|
+
var _hop = Object.prototype.hasOwnProperty;
|
|
3441
|
+
var _isA = Array.isArray;
|
|
3442
|
+
var _keys = Object.keys;
|
|
3443
|
+
var _to = typeOf; // imported from bitwrench-utils.js
|
|
3444
|
+
var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
|
|
3445
|
+
// Console aliases use thin wrappers (not direct references) so that test
|
|
3446
|
+
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
3447
|
+
var _cw = function() { console.warn.apply(console, arguments); };
|
|
3448
|
+
var _cl = function() { console.log.apply(console, arguments); };
|
|
3449
|
+
var _ce = function() { console.error.apply(console, arguments); };
|
|
3450
|
+
|
|
3451
|
+
/**
|
|
3452
|
+
* Debug flag. When true, emits console.warn for silent binding failures
|
|
3453
|
+
* (missing paths, null refs, auto-created intermediate objects).
|
|
3454
|
+
* @type {boolean}
|
|
3455
|
+
*/
|
|
3456
|
+
bw.debug = false;
|
|
3457
|
+
|
|
3385
3458
|
/**
|
|
3386
3459
|
* Lazy-resolve Node.js `fs` module.
|
|
3387
3460
|
* Tries require('fs') first (available in CJS/UMD Node.js builds),
|
|
@@ -3529,7 +3602,7 @@ bw.uuid = function(prefix) {
|
|
|
3529
3602
|
*/
|
|
3530
3603
|
bw._el = function(id) {
|
|
3531
3604
|
// Pass-through for DOM elements
|
|
3532
|
-
if (
|
|
3605
|
+
if (!_is(id, 'string')) return id || null;
|
|
3533
3606
|
if (!id) return null;
|
|
3534
3607
|
if (!bw._isBrowser) return null;
|
|
3535
3608
|
|
|
@@ -3625,7 +3698,7 @@ bw._deregisterNode = function(el, bwId) {
|
|
|
3625
3698
|
* // => '<b>Hello</b> & "world"'
|
|
3626
3699
|
*/
|
|
3627
3700
|
bw.escapeHTML = function(str) {
|
|
3628
|
-
if (
|
|
3701
|
+
if (!_is(str, 'string')) return '';
|
|
3629
3702
|
|
|
3630
3703
|
const escapeMap = {
|
|
3631
3704
|
'&': '&',
|
|
@@ -3698,7 +3771,7 @@ bw.html = function(taco, options = {}) {
|
|
|
3698
3771
|
}
|
|
3699
3772
|
|
|
3700
3773
|
// Handle arrays of TACOs
|
|
3701
|
-
if (
|
|
3774
|
+
if (_isA(taco)) {
|
|
3702
3775
|
return taco.map(t => bw.html(t, options)).join('');
|
|
3703
3776
|
}
|
|
3704
3777
|
|
|
@@ -3721,15 +3794,15 @@ bw.html = function(taco, options = {}) {
|
|
|
3721
3794
|
if (taco && taco._bwEach && options.state) {
|
|
3722
3795
|
var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
3723
3796
|
var arr = bw._evaluatePath(options.state, eachExpr);
|
|
3724
|
-
if (!
|
|
3797
|
+
if (!_isA(arr)) return '';
|
|
3725
3798
|
return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
|
|
3726
3799
|
}
|
|
3727
3800
|
|
|
3728
3801
|
// Handle primitives and non-TACO objects
|
|
3729
|
-
if (
|
|
3802
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
3730
3803
|
var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
|
|
3731
3804
|
// Resolve template bindings if state provided
|
|
3732
|
-
if (options.state &&
|
|
3805
|
+
if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
|
|
3733
3806
|
str = bw._resolveTemplate(str, options.state, !!options.compile);
|
|
3734
3807
|
}
|
|
3735
3808
|
return str;
|
|
@@ -3749,10 +3822,18 @@ bw.html = function(taco, options = {}) {
|
|
|
3749
3822
|
// Skip null, undefined, false
|
|
3750
3823
|
if (value == null || value === false) continue;
|
|
3751
3824
|
|
|
3752
|
-
//
|
|
3753
|
-
if (key.startsWith('on'))
|
|
3825
|
+
// Serialize event handlers via funcRegister
|
|
3826
|
+
if (key.startsWith('on')) {
|
|
3827
|
+
if (_is(value, 'function')) {
|
|
3828
|
+
var fnId = bw.funcRegister(value);
|
|
3829
|
+
attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
|
|
3830
|
+
} else if (_is(value, 'string')) {
|
|
3831
|
+
attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
|
|
3832
|
+
}
|
|
3833
|
+
continue;
|
|
3834
|
+
}
|
|
3754
3835
|
|
|
3755
|
-
if (key === 'style' &&
|
|
3836
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
3756
3837
|
// Convert style object to string
|
|
3757
3838
|
const styleStr = Object.entries(value)
|
|
3758
3839
|
.filter(([, v]) => v != null)
|
|
@@ -3763,7 +3844,7 @@ bw.html = function(taco, options = {}) {
|
|
|
3763
3844
|
}
|
|
3764
3845
|
} else if (key === 'class') {
|
|
3765
3846
|
// Handle class as array or string
|
|
3766
|
-
const classStr =
|
|
3847
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
3767
3848
|
if (classStr) {
|
|
3768
3849
|
attrStr += ` class="${bw.escapeHTML(classStr)}"`;
|
|
3769
3850
|
}
|
|
@@ -3799,13 +3880,184 @@ bw.html = function(taco, options = {}) {
|
|
|
3799
3880
|
// Process content recursively
|
|
3800
3881
|
let contentStr = content != null ? bw.html(content, options) : '';
|
|
3801
3882
|
// Resolve template bindings in content if state provided
|
|
3802
|
-
if (options.state &&
|
|
3883
|
+
if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
|
|
3803
3884
|
contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
|
|
3804
3885
|
}
|
|
3805
3886
|
|
|
3806
3887
|
return `<${tag}${attrStr}>${contentStr}</${tag}>`;
|
|
3807
3888
|
};
|
|
3808
3889
|
|
|
3890
|
+
/**
|
|
3891
|
+
* Generate a complete, self-contained HTML document from TACO content.
|
|
3892
|
+
*
|
|
3893
|
+
* Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
|
|
3894
|
+
* func registry emission (so serialized event handlers work), optional theme,
|
|
3895
|
+
* and extra head elements. Designed for static site generation, offline/airgapped
|
|
3896
|
+
* use, and the "static site that isn't static" workflow.
|
|
3897
|
+
*
|
|
3898
|
+
* @param {Object} [opts={}] - Page options
|
|
3899
|
+
* @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
|
|
3900
|
+
* @param {string} [opts.title='bitwrench'] - Page title
|
|
3901
|
+
* @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
|
|
3902
|
+
* @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
|
|
3903
|
+
* @param {string} [opts.css=''] - Additional CSS for <style> block
|
|
3904
|
+
* @param {string|Object} [opts.theme=null] - Theme preset name or config object
|
|
3905
|
+
* @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
|
|
3906
|
+
* @param {string} [opts.favicon=''] - Favicon URL
|
|
3907
|
+
* @param {string} [opts.lang='en'] - HTML lang attribute
|
|
3908
|
+
* @returns {string} Complete HTML document string
|
|
3909
|
+
* @category DOM Generation
|
|
3910
|
+
* @see bw.html
|
|
3911
|
+
* @example
|
|
3912
|
+
* bw.htmlPage({
|
|
3913
|
+
* title: 'My App',
|
|
3914
|
+
* body: { t: 'h1', c: 'Hello World' },
|
|
3915
|
+
* runtime: 'shim'
|
|
3916
|
+
* })
|
|
3917
|
+
*/
|
|
3918
|
+
bw.htmlPage = function(opts) {
|
|
3919
|
+
opts = opts || {};
|
|
3920
|
+
var title = opts.title || 'bitwrench';
|
|
3921
|
+
var body = opts.body || '';
|
|
3922
|
+
var state = opts.state || undefined;
|
|
3923
|
+
var runtime = opts.runtime || 'shim';
|
|
3924
|
+
var css = opts.css || '';
|
|
3925
|
+
var theme = opts.theme || null;
|
|
3926
|
+
var headExtra = opts.head || [];
|
|
3927
|
+
var favicon = opts.favicon || '';
|
|
3928
|
+
var lang = opts.lang || 'en';
|
|
3929
|
+
|
|
3930
|
+
// Snapshot funcRegistry counter before rendering
|
|
3931
|
+
var fnCounterBefore = bw._fnIDCounter;
|
|
3932
|
+
|
|
3933
|
+
// Render body content
|
|
3934
|
+
var bodyHTML = '';
|
|
3935
|
+
if (_is(body, 'string')) {
|
|
3936
|
+
bodyHTML = body;
|
|
3937
|
+
} else {
|
|
3938
|
+
var htmlOpts = {};
|
|
3939
|
+
if (state) htmlOpts.state = state;
|
|
3940
|
+
bodyHTML = bw.html(body, htmlOpts);
|
|
3941
|
+
}
|
|
3942
|
+
|
|
3943
|
+
// Collect functions registered during this render
|
|
3944
|
+
var fnCounterAfter = bw._fnIDCounter;
|
|
3945
|
+
var registryEntries = '';
|
|
3946
|
+
for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
|
|
3947
|
+
var fnKey = 'bw_fn_' + i;
|
|
3948
|
+
if (bw._fnRegistry[fnKey]) {
|
|
3949
|
+
registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
|
|
3950
|
+
bw._fnRegistry[fnKey].toString() + ';\n';
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
// Build runtime script for <head>
|
|
3955
|
+
var runtimeHead = '';
|
|
3956
|
+
if (runtime === 'inline') {
|
|
3957
|
+
// Read UMD bundle synchronously if in Node.js
|
|
3958
|
+
var umdSource = null;
|
|
3959
|
+
if (bw._isNode) {
|
|
3960
|
+
try {
|
|
3961
|
+
var fs = (typeof require === 'function') ? require('fs') : null;
|
|
3962
|
+
var pathMod = (typeof require === 'function') ? require('path') : null;
|
|
3963
|
+
if (fs && pathMod) {
|
|
3964
|
+
// Resolve dist/ relative to this source file
|
|
3965
|
+
var srcDir = '';
|
|
3966
|
+
try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
|
|
3967
|
+
catch(e2) { /* ESM: __filename not available */ }
|
|
3968
|
+
if (!srcDir && typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench-lean.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-lean.cjs.js', document.baseURI).href))) {
|
|
3969
|
+
var url = (typeof require === 'function') ? require('url') : null;
|
|
3970
|
+
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-lean.cjs.js', document.baseURI).href))));
|
|
3971
|
+
}
|
|
3972
|
+
if (srcDir) {
|
|
3973
|
+
var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
|
|
3974
|
+
umdSource = fs.readFileSync(distPath, 'utf8');
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
} catch(e) { /* fall through */ }
|
|
3978
|
+
}
|
|
3979
|
+
if (umdSource) {
|
|
3980
|
+
runtimeHead = '<script>' + umdSource + '</script>';
|
|
3981
|
+
} else {
|
|
3982
|
+
// Fallback to shim in browser or if dist not available
|
|
3983
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
3984
|
+
}
|
|
3985
|
+
} else if (runtime === 'cdn') {
|
|
3986
|
+
runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
|
|
3987
|
+
} else if (runtime === 'shim') {
|
|
3988
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
3989
|
+
}
|
|
3990
|
+
// runtime === 'none' → empty
|
|
3991
|
+
|
|
3992
|
+
// Theme CSS
|
|
3993
|
+
var themeCSS = '';
|
|
3994
|
+
if (theme) {
|
|
3995
|
+
var themeConfig = _is(theme, 'string')
|
|
3996
|
+
? (THEME_PRESETS[theme.toLowerCase()] || null)
|
|
3997
|
+
: theme;
|
|
3998
|
+
if (themeConfig) {
|
|
3999
|
+
var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, { inject: false }));
|
|
4000
|
+
themeCSS = themeResult.css;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
|
|
4004
|
+
// Extra <head> elements
|
|
4005
|
+
var headHTML = '';
|
|
4006
|
+
if (_isA(headExtra) && headExtra.length > 0) {
|
|
4007
|
+
headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
|
|
4008
|
+
}
|
|
4009
|
+
|
|
4010
|
+
// Favicon
|
|
4011
|
+
var faviconTag = '';
|
|
4012
|
+
if (favicon) {
|
|
4013
|
+
var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
|
|
4014
|
+
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c];
|
|
4015
|
+
});
|
|
4016
|
+
faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4019
|
+
// Escaped title
|
|
4020
|
+
var safeTitle = bw.escapeHTML(title);
|
|
4021
|
+
|
|
4022
|
+
// Combine all CSS
|
|
4023
|
+
var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
|
|
4024
|
+
|
|
4025
|
+
// Body-end script: registry entries + optional loadDefaultStyles
|
|
4026
|
+
var bodyEndScript = '';
|
|
4027
|
+
var bodyEndParts = [];
|
|
4028
|
+
if (registryEntries) {
|
|
4029
|
+
bodyEndParts.push(registryEntries);
|
|
4030
|
+
}
|
|
4031
|
+
if (runtime === 'inline' || runtime === 'cdn') {
|
|
4032
|
+
bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
|
|
4033
|
+
}
|
|
4034
|
+
if (bodyEndParts.length > 0) {
|
|
4035
|
+
bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
// Assemble document
|
|
4039
|
+
var parts = [
|
|
4040
|
+
'<!DOCTYPE html>',
|
|
4041
|
+
'<html lang="' + lang + '">',
|
|
4042
|
+
'<head>',
|
|
4043
|
+
'<meta charset="UTF-8">',
|
|
4044
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
4045
|
+
];
|
|
4046
|
+
parts.push('<title>' + safeTitle + '</title>');
|
|
4047
|
+
if (faviconTag) parts.push(faviconTag);
|
|
4048
|
+
if (runtimeHead) parts.push(runtimeHead);
|
|
4049
|
+
if (headHTML) parts.push(headHTML);
|
|
4050
|
+
if (allCSS) parts.push('<style>' + allCSS + '</style>');
|
|
4051
|
+
parts.push('</head>');
|
|
4052
|
+
parts.push('<body>');
|
|
4053
|
+
parts.push(bodyHTML);
|
|
4054
|
+
if (bodyEndScript) parts.push(bodyEndScript);
|
|
4055
|
+
parts.push('</body>');
|
|
4056
|
+
parts.push('</html>');
|
|
4057
|
+
|
|
4058
|
+
return parts.join('\n');
|
|
4059
|
+
};
|
|
4060
|
+
|
|
3809
4061
|
/**
|
|
3810
4062
|
* Create a live DOM element from a TACO object (browser only).
|
|
3811
4063
|
*
|
|
@@ -3850,7 +4102,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
3850
4102
|
}
|
|
3851
4103
|
|
|
3852
4104
|
// Handle text nodes
|
|
3853
|
-
if (
|
|
4105
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
3854
4106
|
return document.createTextNode(String(taco));
|
|
3855
4107
|
}
|
|
3856
4108
|
|
|
@@ -3863,16 +4115,16 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
3863
4115
|
for (const [key, value] of Object.entries(attrs)) {
|
|
3864
4116
|
if (value == null || value === false) continue;
|
|
3865
4117
|
|
|
3866
|
-
if (key === 'style' &&
|
|
4118
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
3867
4119
|
// Apply styles directly
|
|
3868
4120
|
Object.assign(el.style, value);
|
|
3869
4121
|
} else if (key === 'class') {
|
|
3870
4122
|
// Handle class as array or string
|
|
3871
|
-
const classStr =
|
|
4123
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
3872
4124
|
if (classStr) {
|
|
3873
4125
|
el.className = classStr;
|
|
3874
4126
|
}
|
|
3875
|
-
} else if (key.startsWith('on') &&
|
|
4127
|
+
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
3876
4128
|
// Event handlers
|
|
3877
4129
|
const eventName = key.slice(2).toLowerCase();
|
|
3878
4130
|
el.addEventListener(eventName, value);
|
|
@@ -3892,7 +4144,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
3892
4144
|
// Children with data-bw_id or id attributes get local refs on the parent,
|
|
3893
4145
|
// so o.render functions can access them without any DOM lookup.
|
|
3894
4146
|
if (content != null) {
|
|
3895
|
-
if (
|
|
4147
|
+
if (_isA(content)) {
|
|
3896
4148
|
content.forEach(child => {
|
|
3897
4149
|
if (child != null) {
|
|
3898
4150
|
// Handle ComponentHandle in content arrays (Level 2 children)
|
|
@@ -3912,20 +4164,20 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
3912
4164
|
if (childEl._bw_refs) {
|
|
3913
4165
|
if (!el._bw_refs) el._bw_refs = {};
|
|
3914
4166
|
for (var rk in childEl._bw_refs) {
|
|
3915
|
-
if (
|
|
4167
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
3916
4168
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
3917
4169
|
}
|
|
3918
4170
|
}
|
|
3919
4171
|
}
|
|
3920
4172
|
}
|
|
3921
4173
|
});
|
|
3922
|
-
} else if (
|
|
4174
|
+
} else if (_is(content, 'object') && content.__bw_raw) {
|
|
3923
4175
|
// Raw HTML content — inject via innerHTML
|
|
3924
4176
|
el.innerHTML = content.v;
|
|
3925
4177
|
} else if (content._bwComponent === true) {
|
|
3926
4178
|
// Single ComponentHandle as content
|
|
3927
4179
|
content.mount(el);
|
|
3928
|
-
} else if (
|
|
4180
|
+
} else if (_is(content, 'object') && content.t) {
|
|
3929
4181
|
var childEl = bw.createDOM(content, options);
|
|
3930
4182
|
el.appendChild(childEl);
|
|
3931
4183
|
var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
|
|
@@ -3936,7 +4188,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
3936
4188
|
if (childEl._bw_refs) {
|
|
3937
4189
|
if (!el._bw_refs) el._bw_refs = {};
|
|
3938
4190
|
for (var rk in childEl._bw_refs) {
|
|
3939
|
-
if (
|
|
4191
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
3940
4192
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
3941
4193
|
}
|
|
3942
4194
|
}
|
|
@@ -3969,7 +4221,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
3969
4221
|
el._bw_render = opts.render;
|
|
3970
4222
|
|
|
3971
4223
|
if (opts.mounted) {
|
|
3972
|
-
|
|
4224
|
+
_cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
|
|
3973
4225
|
}
|
|
3974
4226
|
|
|
3975
4227
|
// Queue initial render (same timing as mounted)
|
|
@@ -4042,7 +4294,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
4042
4294
|
const targetEl = bw._el(target);
|
|
4043
4295
|
|
|
4044
4296
|
if (!targetEl) {
|
|
4045
|
-
|
|
4297
|
+
_ce('bw.DOM: Target element not found:', target);
|
|
4046
4298
|
return null;
|
|
4047
4299
|
}
|
|
4048
4300
|
|
|
@@ -4082,7 +4334,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
4082
4334
|
targetEl.appendChild(taco.element);
|
|
4083
4335
|
}
|
|
4084
4336
|
// Handle arrays
|
|
4085
|
-
else if (
|
|
4337
|
+
else if (_isA(taco)) {
|
|
4086
4338
|
taco.forEach(t => {
|
|
4087
4339
|
if (t != null) {
|
|
4088
4340
|
if (t._bwComponent === true) {
|
|
@@ -4118,7 +4370,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
4118
4370
|
bw.compileProps = function(handle, props = {}) {
|
|
4119
4371
|
const compiledProps = {};
|
|
4120
4372
|
|
|
4121
|
-
|
|
4373
|
+
_keys(props).forEach(key => {
|
|
4122
4374
|
// Create getter/setter for each prop
|
|
4123
4375
|
Object.defineProperty(compiledProps, key, {
|
|
4124
4376
|
get() {
|
|
@@ -4436,17 +4688,17 @@ bw.patch = function(id, content, attr) {
|
|
|
4436
4688
|
if (attr) {
|
|
4437
4689
|
// Patch an attribute
|
|
4438
4690
|
el.setAttribute(attr, String(content));
|
|
4439
|
-
} else if (
|
|
4691
|
+
} else if (_isA(content)) {
|
|
4440
4692
|
// Patch with array of children (strings and/or TACOs)
|
|
4441
4693
|
el.innerHTML = '';
|
|
4442
4694
|
content.forEach(function(item) {
|
|
4443
|
-
if (
|
|
4695
|
+
if (_is(item, 'string') || _is(item, 'number')) {
|
|
4444
4696
|
el.appendChild(document.createTextNode(String(item)));
|
|
4445
4697
|
} else if (item && item.t) {
|
|
4446
4698
|
el.appendChild(bw.createDOM(item));
|
|
4447
4699
|
}
|
|
4448
4700
|
});
|
|
4449
|
-
} else if (
|
|
4701
|
+
} else if (_is(content, 'object') && content.t) {
|
|
4450
4702
|
// Patch with a TACO — replace children
|
|
4451
4703
|
el.innerHTML = '';
|
|
4452
4704
|
el.appendChild(bw.createDOM(content));
|
|
@@ -4477,7 +4729,7 @@ bw.patch = function(id, content, attr) {
|
|
|
4477
4729
|
bw.patchAll = function(patches) {
|
|
4478
4730
|
var results = {};
|
|
4479
4731
|
for (var id in patches) {
|
|
4480
|
-
if (
|
|
4732
|
+
if (_hop.call(patches, id)) {
|
|
4481
4733
|
results[id] = bw.patch(id, patches[id]);
|
|
4482
4734
|
}
|
|
4483
4735
|
}
|
|
@@ -4574,7 +4826,7 @@ bw.pub = function(topic, detail) {
|
|
|
4574
4826
|
snapshot[i].handler(detail);
|
|
4575
4827
|
called++;
|
|
4576
4828
|
} catch (err) {
|
|
4577
|
-
|
|
4829
|
+
_cw('bw.pub: subscriber error on topic "' + topic + '":', err);
|
|
4578
4830
|
}
|
|
4579
4831
|
}
|
|
4580
4832
|
return called;
|
|
@@ -4670,8 +4922,8 @@ bw._fnIDCounter = 0;
|
|
|
4670
4922
|
* @see bw.funcGetDispatchStr
|
|
4671
4923
|
*/
|
|
4672
4924
|
bw.funcRegister = function(fn, name) {
|
|
4673
|
-
if (
|
|
4674
|
-
var fnID = (
|
|
4925
|
+
if (!_is(fn, 'function')) return '';
|
|
4926
|
+
var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
|
|
4675
4927
|
bw._fnRegistry[fnID] = fn;
|
|
4676
4928
|
return fnID;
|
|
4677
4929
|
};
|
|
@@ -4690,7 +4942,7 @@ bw.funcRegister = function(fn, name) {
|
|
|
4690
4942
|
bw.funcGetById = function(name, errFn) {
|
|
4691
4943
|
name = String(name);
|
|
4692
4944
|
if (name in bw._fnRegistry) return bw._fnRegistry[name];
|
|
4693
|
-
return (
|
|
4945
|
+
return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
|
|
4694
4946
|
};
|
|
4695
4947
|
|
|
4696
4948
|
/**
|
|
@@ -4731,13 +4983,30 @@ bw.funcUnregister = function(name) {
|
|
|
4731
4983
|
bw.funcGetRegistry = function() {
|
|
4732
4984
|
var copy = {};
|
|
4733
4985
|
for (var k in bw._fnRegistry) {
|
|
4734
|
-
if (
|
|
4986
|
+
if (_hop.call(bw._fnRegistry, k)) {
|
|
4735
4987
|
copy[k] = bw._fnRegistry[k];
|
|
4736
4988
|
}
|
|
4737
4989
|
}
|
|
4738
4990
|
return copy;
|
|
4739
4991
|
};
|
|
4740
4992
|
|
|
4993
|
+
/**
|
|
4994
|
+
* Minimal runtime shim for funcRegister dispatch in static HTML.
|
|
4995
|
+
* When embedded in a `<script>` tag, provides just enough infrastructure
|
|
4996
|
+
* for `bw.funcGetById()` calls to resolve. The actual function bodies
|
|
4997
|
+
* are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
|
|
4998
|
+
* @type {string}
|
|
4999
|
+
* @category Function Registry
|
|
5000
|
+
*/
|
|
5001
|
+
bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
|
|
5002
|
+
'if(!bw._fnRegistry)bw._fnRegistry={};' +
|
|
5003
|
+
'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
|
|
5004
|
+
'console.warn("bw: unregistered fn "+n)};};' +
|
|
5005
|
+
'bw.funcRegister=function(fn,name){' +
|
|
5006
|
+
'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
|
|
5007
|
+
'bw._fnRegistry[id]=fn;return id;};' +
|
|
5008
|
+
'window.bw=bw;})();';
|
|
5009
|
+
|
|
4741
5010
|
// ===================================================================================
|
|
4742
5011
|
// Template Binding Utilities
|
|
4743
5012
|
// ===================================================================================
|
|
@@ -4765,7 +5034,10 @@ bw._evaluatePath = function(state, path) {
|
|
|
4765
5034
|
var parts = path.split('.');
|
|
4766
5035
|
var val = state;
|
|
4767
5036
|
for (var i = 0; i < parts.length; i++) {
|
|
4768
|
-
if (val == null)
|
|
5037
|
+
if (val == null) {
|
|
5038
|
+
if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
|
|
5039
|
+
return '';
|
|
5040
|
+
}
|
|
4769
5041
|
val = val[parts[i]];
|
|
4770
5042
|
}
|
|
4771
5043
|
return (val == null) ? '' : val;
|
|
@@ -4785,7 +5057,7 @@ bw._evaluatePath = function(state, path) {
|
|
|
4785
5057
|
*/
|
|
4786
5058
|
bw._compiledExprs = {};
|
|
4787
5059
|
bw._resolveTemplate = function(str, state, compile) {
|
|
4788
|
-
if (
|
|
5060
|
+
if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
|
|
4789
5061
|
var bindings = bw._parseBindings(str);
|
|
4790
5062
|
if (bindings.length === 0) return str;
|
|
4791
5063
|
|
|
@@ -4807,6 +5079,7 @@ bw._resolveTemplate = function(str, state, compile) {
|
|
|
4807
5079
|
try {
|
|
4808
5080
|
val = bw._compiledExprs[b.expr](state);
|
|
4809
5081
|
} catch (e) {
|
|
5082
|
+
if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
|
|
4810
5083
|
val = '';
|
|
4811
5084
|
}
|
|
4812
5085
|
} else {
|
|
@@ -4915,7 +5188,7 @@ function ComponentHandle(taco) {
|
|
|
4915
5188
|
this._state = {};
|
|
4916
5189
|
if (o.state) {
|
|
4917
5190
|
for (var k in o.state) {
|
|
4918
|
-
if (
|
|
5191
|
+
if (_hop.call(o.state, k)) {
|
|
4919
5192
|
this._state[k] = o.state[k];
|
|
4920
5193
|
}
|
|
4921
5194
|
}
|
|
@@ -4924,7 +5197,7 @@ function ComponentHandle(taco) {
|
|
|
4924
5197
|
this._actions = {};
|
|
4925
5198
|
if (o.actions) {
|
|
4926
5199
|
for (var k2 in o.actions) {
|
|
4927
|
-
if (
|
|
5200
|
+
if (_hop.call(o.actions, k2)) {
|
|
4928
5201
|
this._actions[k2] = o.actions[k2];
|
|
4929
5202
|
}
|
|
4930
5203
|
}
|
|
@@ -4934,7 +5207,7 @@ function ComponentHandle(taco) {
|
|
|
4934
5207
|
if (o.methods) {
|
|
4935
5208
|
var self = this;
|
|
4936
5209
|
for (var k3 in o.methods) {
|
|
4937
|
-
if (
|
|
5210
|
+
if (_hop.call(o.methods, k3)) {
|
|
4938
5211
|
this._methods[k3] = o.methods[k3];
|
|
4939
5212
|
(function(methodName, methodFn) {
|
|
4940
5213
|
self[methodName] = function() {
|
|
@@ -4967,14 +5240,23 @@ function ComponentHandle(taco) {
|
|
|
4967
5240
|
this._compile = !!o.compile;
|
|
4968
5241
|
this._bw_refs = {};
|
|
4969
5242
|
this._refCounter = 0;
|
|
5243
|
+
// Child component ownership (Bug #5)
|
|
5244
|
+
this._children = [];
|
|
5245
|
+
this._parent = null;
|
|
5246
|
+
// Factory metadata for BCCL rebuild (Bug #6)
|
|
5247
|
+
this._factory = taco._bwFactory || null;
|
|
4970
5248
|
}
|
|
4971
5249
|
|
|
5250
|
+
// Short alias for ComponentHandle.prototype (see alias block at top of file).
|
|
5251
|
+
// 28 method definitions × 25 chars = ~700B raw savings in minified output.
|
|
5252
|
+
var _chp = ComponentHandle.prototype;
|
|
5253
|
+
|
|
4972
5254
|
// ── State Methods ──
|
|
4973
5255
|
|
|
4974
5256
|
/**
|
|
4975
5257
|
* Get a state value. Dot-path supported: `get('user.name')`
|
|
4976
5258
|
*/
|
|
4977
|
-
|
|
5259
|
+
_chp.get = function(key) {
|
|
4978
5260
|
return bw._evaluatePath(this._state, key);
|
|
4979
5261
|
};
|
|
4980
5262
|
|
|
@@ -4984,12 +5266,13 @@ ComponentHandle.prototype.get = function(key) {
|
|
|
4984
5266
|
* @param {*} value - New value
|
|
4985
5267
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
4986
5268
|
*/
|
|
4987
|
-
|
|
5269
|
+
_chp.set = function(key, value, opts) {
|
|
4988
5270
|
// Dot-path set
|
|
4989
5271
|
var parts = key.split('.');
|
|
4990
5272
|
var obj = this._state;
|
|
4991
5273
|
for (var i = 0; i < parts.length - 1; i++) {
|
|
4992
|
-
if (obj[parts[i]]
|
|
5274
|
+
if (!_is(obj[parts[i]], 'object')) {
|
|
5275
|
+
if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
|
|
4993
5276
|
obj[parts[i]] = {};
|
|
4994
5277
|
}
|
|
4995
5278
|
obj = obj[parts[i]];
|
|
@@ -5009,10 +5292,10 @@ ComponentHandle.prototype.set = function(key, value, opts) {
|
|
|
5009
5292
|
/**
|
|
5010
5293
|
* Get a shallow clone of the full state.
|
|
5011
5294
|
*/
|
|
5012
|
-
|
|
5295
|
+
_chp.getState = function() {
|
|
5013
5296
|
var clone = {};
|
|
5014
5297
|
for (var k in this._state) {
|
|
5015
|
-
if (
|
|
5298
|
+
if (_hop.call(this._state, k)) {
|
|
5016
5299
|
clone[k] = this._state[k];
|
|
5017
5300
|
}
|
|
5018
5301
|
}
|
|
@@ -5024,9 +5307,9 @@ ComponentHandle.prototype.getState = function() {
|
|
|
5024
5307
|
* @param {Object} updates - Key-value pairs to merge
|
|
5025
5308
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
5026
5309
|
*/
|
|
5027
|
-
|
|
5310
|
+
_chp.setState = function(updates, opts) {
|
|
5028
5311
|
for (var k in updates) {
|
|
5029
|
-
if (
|
|
5312
|
+
if (_hop.call(updates, k)) {
|
|
5030
5313
|
this._state[k] = updates[k];
|
|
5031
5314
|
this._dirtyKeys[k] = true;
|
|
5032
5315
|
}
|
|
@@ -5043,9 +5326,9 @@ ComponentHandle.prototype.setState = function(updates, opts) {
|
|
|
5043
5326
|
/**
|
|
5044
5327
|
* Push a value onto an array in state. Clones the array.
|
|
5045
5328
|
*/
|
|
5046
|
-
|
|
5329
|
+
_chp.push = function(key, val) {
|
|
5047
5330
|
var arr = this.get(key);
|
|
5048
|
-
var newArr =
|
|
5331
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
5049
5332
|
newArr.push(val);
|
|
5050
5333
|
this.set(key, newArr);
|
|
5051
5334
|
};
|
|
@@ -5053,9 +5336,9 @@ ComponentHandle.prototype.push = function(key, val) {
|
|
|
5053
5336
|
/**
|
|
5054
5337
|
* Splice an array in state. Clones the array.
|
|
5055
5338
|
*/
|
|
5056
|
-
|
|
5339
|
+
_chp.splice = function(key, start, deleteCount) {
|
|
5057
5340
|
var arr = this.get(key);
|
|
5058
|
-
var newArr =
|
|
5341
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
5059
5342
|
var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
|
|
5060
5343
|
Array.prototype.splice.apply(newArr, args);
|
|
5061
5344
|
this.set(key, newArr);
|
|
@@ -5063,7 +5346,7 @@ ComponentHandle.prototype.splice = function(key, start, deleteCount) {
|
|
|
5063
5346
|
|
|
5064
5347
|
// ── Scheduling ──
|
|
5065
5348
|
|
|
5066
|
-
|
|
5349
|
+
_chp._scheduleDirty = function() {
|
|
5067
5350
|
if (!this._scheduled) {
|
|
5068
5351
|
this._scheduled = true;
|
|
5069
5352
|
bw._dirtyComponents.push(this);
|
|
@@ -5078,17 +5361,17 @@ ComponentHandle.prototype._scheduleDirty = function() {
|
|
|
5078
5361
|
* Creates binding descriptors with refIds for targeted DOM updates.
|
|
5079
5362
|
* @private
|
|
5080
5363
|
*/
|
|
5081
|
-
|
|
5364
|
+
_chp._compileBindings = function() {
|
|
5082
5365
|
this._bindings = [];
|
|
5083
5366
|
this._refCounter = 0;
|
|
5084
|
-
var stateKeys =
|
|
5367
|
+
var stateKeys = _keys(this._state);
|
|
5085
5368
|
var self = this;
|
|
5086
5369
|
|
|
5087
5370
|
function walkTaco(taco, path) {
|
|
5088
|
-
if (taco
|
|
5371
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5089
5372
|
|
|
5090
5373
|
// Check content for bindings
|
|
5091
|
-
if (
|
|
5374
|
+
if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
|
|
5092
5375
|
var refId = 'bw_ref_' + self._refCounter++;
|
|
5093
5376
|
var parsed = bw._parseBindings(taco.c);
|
|
5094
5377
|
var deps = [];
|
|
@@ -5110,10 +5393,10 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
5110
5393
|
// Check attributes for bindings
|
|
5111
5394
|
if (taco.a) {
|
|
5112
5395
|
for (var attrName in taco.a) {
|
|
5113
|
-
if (!
|
|
5396
|
+
if (!_hop.call(taco.a, attrName)) continue;
|
|
5114
5397
|
if (attrName === 'data-bw_ref') continue;
|
|
5115
5398
|
var attrVal = taco.a[attrName];
|
|
5116
|
-
if (
|
|
5399
|
+
if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
|
|
5117
5400
|
var refId2 = 'bw_ref_' + self._refCounter++;
|
|
5118
5401
|
var parsed2 = bw._parseBindings(attrVal);
|
|
5119
5402
|
var deps2 = [];
|
|
@@ -5139,9 +5422,27 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
5139
5422
|
}
|
|
5140
5423
|
|
|
5141
5424
|
// Recurse into children
|
|
5142
|
-
if (
|
|
5425
|
+
if (_isA(taco.c)) {
|
|
5143
5426
|
for (var i = 0; i < taco.c.length; i++) {
|
|
5144
|
-
|
|
5427
|
+
// Wrap string children with ${expr} in a span so patches target the span, not the parent
|
|
5428
|
+
if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
|
|
5429
|
+
var mixedRefId = 'bw_ref_' + self._refCounter++;
|
|
5430
|
+
var mixedParsed = bw._parseBindings(taco.c[i]);
|
|
5431
|
+
var mixedDeps = [];
|
|
5432
|
+
for (var mi = 0; mi < mixedParsed.length; mi++) {
|
|
5433
|
+
mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
|
|
5434
|
+
}
|
|
5435
|
+
self._bindings.push({
|
|
5436
|
+
expr: taco.c[i],
|
|
5437
|
+
type: 'content',
|
|
5438
|
+
refId: mixedRefId,
|
|
5439
|
+
deps: mixedDeps,
|
|
5440
|
+
template: taco.c[i]
|
|
5441
|
+
});
|
|
5442
|
+
// Replace string with a span wrapper so textContent targets the span only
|
|
5443
|
+
taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
|
|
5444
|
+
}
|
|
5445
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
5145
5446
|
walkTaco(taco.c[i], path.concat(i));
|
|
5146
5447
|
}
|
|
5147
5448
|
// Handle bw.when/bw.each markers
|
|
@@ -5176,7 +5477,7 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
5176
5477
|
taco.c[i]._refId = eachRefId;
|
|
5177
5478
|
}
|
|
5178
5479
|
}
|
|
5179
|
-
} else if (taco.c
|
|
5480
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5180
5481
|
walkTaco(taco.c, path.concat(0));
|
|
5181
5482
|
}
|
|
5182
5483
|
|
|
@@ -5192,7 +5493,7 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
5192
5493
|
* Build ref map from the live DOM after createDOM.
|
|
5193
5494
|
* @private
|
|
5194
5495
|
*/
|
|
5195
|
-
|
|
5496
|
+
_chp._collectRefs = function() {
|
|
5196
5497
|
this._bw_refs = {};
|
|
5197
5498
|
if (!this.element) return;
|
|
5198
5499
|
var els = this.element.querySelectorAll('[data-bw_ref]');
|
|
@@ -5213,7 +5514,7 @@ ComponentHandle.prototype._collectRefs = function() {
|
|
|
5213
5514
|
* Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
|
|
5214
5515
|
* @param {Element} parentEl - DOM element to mount into
|
|
5215
5516
|
*/
|
|
5216
|
-
|
|
5517
|
+
_chp.mount = function(parentEl) {
|
|
5217
5518
|
// willMount hook
|
|
5218
5519
|
if (this._hooks.willMount) this._hooks.willMount(this);
|
|
5219
5520
|
|
|
@@ -5235,7 +5536,7 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
5235
5536
|
// Register named actions in function registry
|
|
5236
5537
|
var self = this;
|
|
5237
5538
|
for (var actionName in this._actions) {
|
|
5238
|
-
if (
|
|
5539
|
+
if (_hop.call(this._actions, actionName)) {
|
|
5239
5540
|
var registeredName = this._bwId + '_' + actionName;
|
|
5240
5541
|
(function(aName) {
|
|
5241
5542
|
bw.funcRegister(function(evt) {
|
|
@@ -5254,6 +5555,11 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
5254
5555
|
this.element = bw.createDOM(tacoForDOM);
|
|
5255
5556
|
this.element._bwComponentHandle = this;
|
|
5256
5557
|
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
5558
|
+
|
|
5559
|
+
// Restore o.render from original TACO (stripped by _tacoForDOM)
|
|
5560
|
+
if (this.taco.o && this.taco.o.render) {
|
|
5561
|
+
this.element._bw_render = this.taco.o.render;
|
|
5562
|
+
}
|
|
5257
5563
|
if (this._userTag) {
|
|
5258
5564
|
this.element.classList.add(this._userTag);
|
|
5259
5565
|
}
|
|
@@ -5269,6 +5575,16 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
5269
5575
|
|
|
5270
5576
|
this.mounted = true;
|
|
5271
5577
|
|
|
5578
|
+
// Scan for child ComponentHandles and link parent/child (Bug #5)
|
|
5579
|
+
var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
|
|
5580
|
+
for (var ci = 0; ci < childEls.length; ci++) {
|
|
5581
|
+
var ch = childEls[ci]._bwComponentHandle;
|
|
5582
|
+
if (ch && ch !== this && !ch._parent) {
|
|
5583
|
+
ch._parent = this;
|
|
5584
|
+
this._children.push(ch);
|
|
5585
|
+
}
|
|
5586
|
+
}
|
|
5587
|
+
|
|
5272
5588
|
// mounted hook (backward compat: fn.length === 2 wraps (el, state))
|
|
5273
5589
|
if (this._hooks.mounted) {
|
|
5274
5590
|
if (this._hooks.mounted.length === 2) {
|
|
@@ -5277,16 +5593,21 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
5277
5593
|
this._hooks.mounted(this);
|
|
5278
5594
|
}
|
|
5279
5595
|
}
|
|
5596
|
+
|
|
5597
|
+
// Invoke o.render on initial mount (if present)
|
|
5598
|
+
if (this.element._bw_render) {
|
|
5599
|
+
this.element._bw_render(this.element, this._state);
|
|
5600
|
+
}
|
|
5280
5601
|
};
|
|
5281
5602
|
|
|
5282
5603
|
/**
|
|
5283
5604
|
* Prepare TACO for initial render: resolve when/each markers.
|
|
5284
5605
|
* @private
|
|
5285
5606
|
*/
|
|
5286
|
-
|
|
5287
|
-
if (!taco
|
|
5607
|
+
_chp._prepareTaco = function(taco) {
|
|
5608
|
+
if (!_is(taco, 'object')) return;
|
|
5288
5609
|
|
|
5289
|
-
if (
|
|
5610
|
+
if (_isA(taco.c)) {
|
|
5290
5611
|
for (var i = taco.c.length - 1; i >= 0; i--) {
|
|
5291
5612
|
var child = taco.c[i];
|
|
5292
5613
|
if (child && child._bwWhen) {
|
|
@@ -5311,18 +5632,18 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
|
|
|
5311
5632
|
var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
5312
5633
|
var arr = bw._evaluatePath(this._state, eachExprStr);
|
|
5313
5634
|
var items = [];
|
|
5314
|
-
if (
|
|
5635
|
+
if (_isA(arr)) {
|
|
5315
5636
|
for (var j = 0; j < arr.length; j++) {
|
|
5316
5637
|
items.push(child.factory(arr[j], j));
|
|
5317
5638
|
}
|
|
5318
5639
|
}
|
|
5319
5640
|
taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
|
|
5320
5641
|
}
|
|
5321
|
-
if (taco.c[i]
|
|
5642
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
5322
5643
|
this._prepareTaco(taco.c[i]);
|
|
5323
5644
|
}
|
|
5324
5645
|
}
|
|
5325
|
-
} else if (taco.c
|
|
5646
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5326
5647
|
this._prepareTaco(taco.c);
|
|
5327
5648
|
}
|
|
5328
5649
|
};
|
|
@@ -5331,12 +5652,12 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
|
|
|
5331
5652
|
* Wire action name strings (in onclick etc.) to dispatch function calls.
|
|
5332
5653
|
* @private
|
|
5333
5654
|
*/
|
|
5334
|
-
|
|
5335
|
-
if (!taco
|
|
5655
|
+
_chp._wireActions = function(taco) {
|
|
5656
|
+
if (!_is(taco, 'object') || !taco.t) return;
|
|
5336
5657
|
if (taco.a) {
|
|
5337
5658
|
for (var key in taco.a) {
|
|
5338
|
-
if (!
|
|
5339
|
-
if (key.startsWith('on') &&
|
|
5659
|
+
if (!_hop.call(taco.a, key)) continue;
|
|
5660
|
+
if (key.startsWith('on') && _is(taco.a[key], 'string')) {
|
|
5340
5661
|
var actionName = taco.a[key];
|
|
5341
5662
|
if (actionName in this._actions) {
|
|
5342
5663
|
var registeredName = this._bwId + '_' + actionName;
|
|
@@ -5350,11 +5671,11 @@ ComponentHandle.prototype._wireActions = function(taco) {
|
|
|
5350
5671
|
}
|
|
5351
5672
|
}
|
|
5352
5673
|
}
|
|
5353
|
-
if (
|
|
5674
|
+
if (_isA(taco.c)) {
|
|
5354
5675
|
for (var i = 0; i < taco.c.length; i++) {
|
|
5355
5676
|
this._wireActions(taco.c[i]);
|
|
5356
5677
|
}
|
|
5357
|
-
} else if (taco.c
|
|
5678
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5358
5679
|
this._wireActions(taco.c);
|
|
5359
5680
|
}
|
|
5360
5681
|
};
|
|
@@ -5363,7 +5684,7 @@ ComponentHandle.prototype._wireActions = function(taco) {
|
|
|
5363
5684
|
* Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
|
|
5364
5685
|
* @private
|
|
5365
5686
|
*/
|
|
5366
|
-
|
|
5687
|
+
_chp._deepCloneTaco = function(taco) {
|
|
5367
5688
|
if (taco == null) return taco;
|
|
5368
5689
|
// Preserve _bwWhen / _bwEach markers (contain functions)
|
|
5369
5690
|
if (taco._bwWhen) {
|
|
@@ -5375,18 +5696,18 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
|
|
|
5375
5696
|
if (taco._bwEach) {
|
|
5376
5697
|
return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
|
|
5377
5698
|
}
|
|
5378
|
-
if (
|
|
5699
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5379
5700
|
var result = { t: taco.t };
|
|
5380
5701
|
if (taco.a) {
|
|
5381
5702
|
result.a = {};
|
|
5382
5703
|
for (var k in taco.a) {
|
|
5383
|
-
if (
|
|
5704
|
+
if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
|
|
5384
5705
|
}
|
|
5385
5706
|
}
|
|
5386
5707
|
if (taco.c != null) {
|
|
5387
|
-
if (
|
|
5708
|
+
if (_isA(taco.c)) {
|
|
5388
5709
|
result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
|
|
5389
|
-
} else if (
|
|
5710
|
+
} else if (_is(taco.c, 'object')) {
|
|
5390
5711
|
result.c = this._deepCloneTaco(taco.c);
|
|
5391
5712
|
} else {
|
|
5392
5713
|
result.c = taco.c;
|
|
@@ -5400,27 +5721,31 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
|
|
|
5400
5721
|
* Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
|
|
5401
5722
|
* @private
|
|
5402
5723
|
*/
|
|
5403
|
-
|
|
5404
|
-
if (!taco
|
|
5724
|
+
_chp._tacoForDOM = function(taco) {
|
|
5725
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5405
5726
|
var result = { t: taco.t };
|
|
5406
5727
|
if (taco.a) result.a = taco.a;
|
|
5407
5728
|
if (taco.c != null) {
|
|
5408
|
-
if (
|
|
5729
|
+
if (_isA(taco.c)) {
|
|
5409
5730
|
result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
|
|
5410
|
-
} else if (
|
|
5731
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5411
5732
|
result.c = this._tacoForDOM(taco.c);
|
|
5412
5733
|
} else {
|
|
5413
5734
|
result.c = taco.c;
|
|
5414
5735
|
}
|
|
5415
5736
|
}
|
|
5416
5737
|
// Intentionally strip o (no mounted/unmount/state/render on sub-elements)
|
|
5738
|
+
if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
|
|
5739
|
+
_cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
|
|
5740
|
+
'>. Use onclick attribute or bw.component() for child interactivity.');
|
|
5741
|
+
}
|
|
5417
5742
|
return result;
|
|
5418
5743
|
};
|
|
5419
5744
|
|
|
5420
5745
|
/**
|
|
5421
5746
|
* Unmount: remove from DOM, deactivate, preserve state for re-mount.
|
|
5422
5747
|
*/
|
|
5423
|
-
|
|
5748
|
+
_chp.unmount = function() {
|
|
5424
5749
|
if (!this.mounted) return;
|
|
5425
5750
|
|
|
5426
5751
|
// unmount hook
|
|
@@ -5455,12 +5780,23 @@ ComponentHandle.prototype.unmount = function() {
|
|
|
5455
5780
|
/**
|
|
5456
5781
|
* Destroy: unmount + clear state + unregister actions.
|
|
5457
5782
|
*/
|
|
5458
|
-
|
|
5783
|
+
_chp.destroy = function() {
|
|
5459
5784
|
// willDestroy hook
|
|
5460
5785
|
if (this._hooks.willDestroy) {
|
|
5461
5786
|
this._hooks.willDestroy(this);
|
|
5462
5787
|
}
|
|
5463
5788
|
|
|
5789
|
+
// Cascade destroy to children depth-first (Bug #5)
|
|
5790
|
+
for (var ci = this._children.length - 1; ci >= 0; ci--) {
|
|
5791
|
+
this._children[ci].destroy();
|
|
5792
|
+
}
|
|
5793
|
+
this._children = [];
|
|
5794
|
+
if (this._parent) {
|
|
5795
|
+
var idx = this._parent._children.indexOf(this);
|
|
5796
|
+
if (idx >= 0) this._parent._children.splice(idx, 1);
|
|
5797
|
+
this._parent = null;
|
|
5798
|
+
}
|
|
5799
|
+
|
|
5464
5800
|
this.unmount();
|
|
5465
5801
|
|
|
5466
5802
|
// Unregister actions from function registry
|
|
@@ -5487,12 +5823,36 @@ ComponentHandle.prototype.destroy = function() {
|
|
|
5487
5823
|
* Flush dirty state: resolve changed bindings and apply to DOM.
|
|
5488
5824
|
* @private
|
|
5489
5825
|
*/
|
|
5490
|
-
|
|
5826
|
+
_chp._flush = function() {
|
|
5491
5827
|
this._scheduled = false;
|
|
5492
|
-
var changedKeys =
|
|
5828
|
+
var changedKeys = _keys(this._dirtyKeys);
|
|
5493
5829
|
this._dirtyKeys = {};
|
|
5494
5830
|
if (changedKeys.length === 0 || !this.mounted) return;
|
|
5495
5831
|
|
|
5832
|
+
// Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
|
|
5833
|
+
// rebuild the TACO from the factory with merged state (Bug #6)
|
|
5834
|
+
if (this._factory) {
|
|
5835
|
+
var rebuildNeeded = false;
|
|
5836
|
+
for (var fi = 0; fi < changedKeys.length; fi++) {
|
|
5837
|
+
if (_hop.call(this._factory.props, changedKeys[fi])) {
|
|
5838
|
+
rebuildNeeded = true; break;
|
|
5839
|
+
}
|
|
5840
|
+
}
|
|
5841
|
+
if (rebuildNeeded) {
|
|
5842
|
+
var merged = {};
|
|
5843
|
+
for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
|
|
5844
|
+
for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
|
|
5845
|
+
this._factory.props = merged;
|
|
5846
|
+
var newTaco = bw.make(this._factory.type, merged);
|
|
5847
|
+
newTaco._bwFactory = this._factory;
|
|
5848
|
+
this.taco = newTaco;
|
|
5849
|
+
this._originalTaco = this._deepCloneTaco(newTaco);
|
|
5850
|
+
this._render();
|
|
5851
|
+
if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
|
|
5852
|
+
return;
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
|
|
5496
5856
|
// willUpdate hook
|
|
5497
5857
|
if (this._hooks.willUpdate) {
|
|
5498
5858
|
this._hooks.willUpdate(this, changedKeys);
|
|
@@ -5531,7 +5891,7 @@ ComponentHandle.prototype._flush = function() {
|
|
|
5531
5891
|
* Returns list of patches to apply.
|
|
5532
5892
|
* @private
|
|
5533
5893
|
*/
|
|
5534
|
-
|
|
5894
|
+
_chp._resolveBindings = function(changedKeys) {
|
|
5535
5895
|
var patches = [];
|
|
5536
5896
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
5537
5897
|
var b = this._bindings[i];
|
|
@@ -5567,11 +5927,14 @@ ComponentHandle.prototype._resolveBindings = function(changedKeys) {
|
|
|
5567
5927
|
* Apply patches to DOM.
|
|
5568
5928
|
* @private
|
|
5569
5929
|
*/
|
|
5570
|
-
|
|
5930
|
+
_chp._applyPatches = function(patches) {
|
|
5571
5931
|
for (var i = 0; i < patches.length; i++) {
|
|
5572
5932
|
var p = patches[i];
|
|
5573
5933
|
var el = this._bw_refs[p.refId];
|
|
5574
|
-
if (!el)
|
|
5934
|
+
if (!el) {
|
|
5935
|
+
if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
|
|
5936
|
+
continue;
|
|
5937
|
+
}
|
|
5575
5938
|
if (p.type === 'content') {
|
|
5576
5939
|
el.textContent = p.value;
|
|
5577
5940
|
} else if (p.type === 'attribute') {
|
|
@@ -5588,7 +5951,7 @@ ComponentHandle.prototype._applyPatches = function(patches) {
|
|
|
5588
5951
|
* Resolve all bindings and apply (used for initial render).
|
|
5589
5952
|
* @private
|
|
5590
5953
|
*/
|
|
5591
|
-
|
|
5954
|
+
_chp._resolveAndApplyAll = function() {
|
|
5592
5955
|
var patches = [];
|
|
5593
5956
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
5594
5957
|
var b = this._bindings[i];
|
|
@@ -5611,7 +5974,7 @@ ComponentHandle.prototype._resolveAndApplyAll = function() {
|
|
|
5611
5974
|
* Full re-render for structural changes (when/each branch switches).
|
|
5612
5975
|
* @private
|
|
5613
5976
|
*/
|
|
5614
|
-
|
|
5977
|
+
_chp._render = function() {
|
|
5615
5978
|
if (!this.element || !this.element.parentNode) return;
|
|
5616
5979
|
var parent = this.element.parentNode;
|
|
5617
5980
|
var nextSibling = this.element.nextSibling;
|
|
@@ -5651,7 +6014,7 @@ ComponentHandle.prototype._render = function() {
|
|
|
5651
6014
|
* @param {string} event - Event name (e.g., 'click')
|
|
5652
6015
|
* @param {Function} handler - Event handler
|
|
5653
6016
|
*/
|
|
5654
|
-
|
|
6017
|
+
_chp.on = function(event, handler) {
|
|
5655
6018
|
if (this.element) {
|
|
5656
6019
|
this.element.addEventListener(event, handler);
|
|
5657
6020
|
}
|
|
@@ -5663,7 +6026,7 @@ ComponentHandle.prototype.on = function(event, handler) {
|
|
|
5663
6026
|
* @param {string} event - Event name
|
|
5664
6027
|
* @param {Function} handler - Handler to remove
|
|
5665
6028
|
*/
|
|
5666
|
-
|
|
6029
|
+
_chp.off = function(event, handler) {
|
|
5667
6030
|
if (this.element) {
|
|
5668
6031
|
this.element.removeEventListener(event, handler);
|
|
5669
6032
|
}
|
|
@@ -5678,7 +6041,7 @@ ComponentHandle.prototype.off = function(event, handler) {
|
|
|
5678
6041
|
* @param {Function} handler - Handler function
|
|
5679
6042
|
* @returns {Function} Unsubscribe function
|
|
5680
6043
|
*/
|
|
5681
|
-
|
|
6044
|
+
_chp.sub = function(topic, handler) {
|
|
5682
6045
|
var unsub = bw.sub(topic, handler);
|
|
5683
6046
|
this._subs.push(unsub);
|
|
5684
6047
|
return unsub;
|
|
@@ -5689,10 +6052,10 @@ ComponentHandle.prototype.sub = function(topic, handler) {
|
|
|
5689
6052
|
* @param {string} name - Action name
|
|
5690
6053
|
* @param {...*} args - Arguments passed after comp
|
|
5691
6054
|
*/
|
|
5692
|
-
|
|
6055
|
+
_chp.action = function(name) {
|
|
5693
6056
|
var fn = this._actions[name];
|
|
5694
6057
|
if (!fn) {
|
|
5695
|
-
|
|
6058
|
+
_cw('ComponentHandle.action: unknown action "' + name + '"');
|
|
5696
6059
|
return;
|
|
5697
6060
|
}
|
|
5698
6061
|
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
|
|
@@ -5704,7 +6067,7 @@ ComponentHandle.prototype.action = function(name) {
|
|
|
5704
6067
|
* @param {string} sel - CSS selector
|
|
5705
6068
|
* @returns {Element|null}
|
|
5706
6069
|
*/
|
|
5707
|
-
|
|
6070
|
+
_chp.select = function(sel) {
|
|
5708
6071
|
return this.element ? this.element.querySelector(sel) : null;
|
|
5709
6072
|
};
|
|
5710
6073
|
|
|
@@ -5713,7 +6076,7 @@ ComponentHandle.prototype.select = function(sel) {
|
|
|
5713
6076
|
* @param {string} sel - CSS selector
|
|
5714
6077
|
* @returns {Element[]}
|
|
5715
6078
|
*/
|
|
5716
|
-
|
|
6079
|
+
_chp.selectAll = function(sel) {
|
|
5717
6080
|
if (!this.element) return [];
|
|
5718
6081
|
return Array.prototype.slice.call(this.element.querySelectorAll(sel));
|
|
5719
6082
|
};
|
|
@@ -5724,7 +6087,7 @@ ComponentHandle.prototype.selectAll = function(sel) {
|
|
|
5724
6087
|
* @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
|
|
5725
6088
|
* @returns {ComponentHandle} this (for chaining)
|
|
5726
6089
|
*/
|
|
5727
|
-
|
|
6090
|
+
_chp.userTag = function(tag) {
|
|
5728
6091
|
this._userTag = tag;
|
|
5729
6092
|
if (this.element) {
|
|
5730
6093
|
this.element.classList.add(tag);
|
|
@@ -5825,14 +6188,399 @@ bw.message = function(target, action, data) {
|
|
|
5825
6188
|
}
|
|
5826
6189
|
if (!el || !el._bwComponentHandle) return false;
|
|
5827
6190
|
var comp = el._bwComponentHandle;
|
|
5828
|
-
if (
|
|
5829
|
-
|
|
6191
|
+
if (!_is(comp[action], 'function')) {
|
|
6192
|
+
_cw('bw.message: unknown action "' + action + '" on component ' + target);
|
|
5830
6193
|
return false;
|
|
5831
6194
|
}
|
|
5832
6195
|
comp[action](data);
|
|
5833
6196
|
return true;
|
|
5834
6197
|
};
|
|
5835
6198
|
|
|
6199
|
+
// ===================================================================================
|
|
6200
|
+
// bw.clientApply() / bw.clientConnect() — Server-driven UI protocol
|
|
6201
|
+
// ===================================================================================
|
|
6202
|
+
|
|
6203
|
+
/**
|
|
6204
|
+
* Registry of named functions sent via register messages.
|
|
6205
|
+
* Populated by clientApply({ type: 'register', name, body }).
|
|
6206
|
+
* Invoked by clientApply({ type: 'call', name, args }).
|
|
6207
|
+
* @private
|
|
6208
|
+
*/
|
|
6209
|
+
bw._clientFunctions = {};
|
|
6210
|
+
|
|
6211
|
+
/**
|
|
6212
|
+
* Whether exec messages are allowed. Set by clientConnect opts.allowExec.
|
|
6213
|
+
* Default false — exec messages are rejected unless explicitly opted in.
|
|
6214
|
+
* @private
|
|
6215
|
+
*/
|
|
6216
|
+
bw._allowExec = false;
|
|
6217
|
+
|
|
6218
|
+
/**
|
|
6219
|
+
* Built-in client functions available via call() without registration.
|
|
6220
|
+
* @private
|
|
6221
|
+
*/
|
|
6222
|
+
bw._builtinClientFunctions = {
|
|
6223
|
+
scrollTo: function(selector) {
|
|
6224
|
+
var el = bw._el(selector);
|
|
6225
|
+
if (el) el.scrollTop = el.scrollHeight;
|
|
6226
|
+
},
|
|
6227
|
+
focus: function(selector) {
|
|
6228
|
+
var el = bw._el(selector);
|
|
6229
|
+
if (el && _is(el.focus, 'function')) el.focus();
|
|
6230
|
+
},
|
|
6231
|
+
download: function(filename, content, mimeType) {
|
|
6232
|
+
if (typeof document === 'undefined') return;
|
|
6233
|
+
var blob = new Blob([content], { type: mimeType || 'text/plain' });
|
|
6234
|
+
var a = document.createElement('a');
|
|
6235
|
+
a.href = URL.createObjectURL(blob);
|
|
6236
|
+
a.download = filename;
|
|
6237
|
+
a.click();
|
|
6238
|
+
URL.revokeObjectURL(a.href);
|
|
6239
|
+
},
|
|
6240
|
+
clipboard: function(text) {
|
|
6241
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
6242
|
+
navigator.clipboard.writeText(text);
|
|
6243
|
+
}
|
|
6244
|
+
},
|
|
6245
|
+
redirect: function(url) {
|
|
6246
|
+
if (typeof window !== 'undefined') window.location.href = url;
|
|
6247
|
+
},
|
|
6248
|
+
log: function() {
|
|
6249
|
+
console.log.apply(console, arguments);
|
|
6250
|
+
}
|
|
6251
|
+
};
|
|
6252
|
+
|
|
6253
|
+
/**
|
|
6254
|
+
* Parse a bwserve protocol message string, supporting both strict JSON
|
|
6255
|
+
* and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
|
|
6256
|
+
*
|
|
6257
|
+
* The r-prefix format is designed for C/C++ string literals where
|
|
6258
|
+
* double-quote escaping is painful. The parser is a state machine
|
|
6259
|
+
* that walks character by character — not a regex replace.
|
|
6260
|
+
*
|
|
6261
|
+
* Escaping: apostrophes inside single-quoted values must be escaped
|
|
6262
|
+
* with backslash: r{'name':'Barry\'s room'}
|
|
6263
|
+
*
|
|
6264
|
+
* @param {string} str - JSON or r-prefixed relaxed JSON string
|
|
6265
|
+
* @returns {Object} Parsed message object
|
|
6266
|
+
* @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
|
|
6267
|
+
* @category Server
|
|
6268
|
+
*/
|
|
6269
|
+
bw.clientParse = function(str) {
|
|
6270
|
+
str = (str || '').trim();
|
|
6271
|
+
if (str.charAt(0) !== 'r') return JSON.parse(str);
|
|
6272
|
+
str = str.slice(1);
|
|
6273
|
+
|
|
6274
|
+
var out = [];
|
|
6275
|
+
var i = 0;
|
|
6276
|
+
var len = str.length;
|
|
6277
|
+
|
|
6278
|
+
while (i < len) {
|
|
6279
|
+
var ch = str[i];
|
|
6280
|
+
|
|
6281
|
+
if (ch === "'") {
|
|
6282
|
+
// Single-quoted string → emit as double-quoted
|
|
6283
|
+
out.push('"');
|
|
6284
|
+
i++;
|
|
6285
|
+
while (i < len) {
|
|
6286
|
+
var c = str[i];
|
|
6287
|
+
if (c === '\\' && i + 1 < len) {
|
|
6288
|
+
var next = str[i + 1];
|
|
6289
|
+
if (next === "'") {
|
|
6290
|
+
out.push("'"); // \' in input → ' in output
|
|
6291
|
+
} else {
|
|
6292
|
+
out.push('\\');
|
|
6293
|
+
out.push(next);
|
|
6294
|
+
}
|
|
6295
|
+
i += 2;
|
|
6296
|
+
} else if (c === '"') {
|
|
6297
|
+
out.push('\\"');
|
|
6298
|
+
i++;
|
|
6299
|
+
} else if (c === "'") {
|
|
6300
|
+
break;
|
|
6301
|
+
} else {
|
|
6302
|
+
out.push(c);
|
|
6303
|
+
i++;
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
out.push('"');
|
|
6307
|
+
i++; // skip closing '
|
|
6308
|
+
|
|
6309
|
+
} else if (ch === '"') {
|
|
6310
|
+
// Double-quoted string — pass through verbatim
|
|
6311
|
+
out.push(ch);
|
|
6312
|
+
i++;
|
|
6313
|
+
while (i < len) {
|
|
6314
|
+
var c2 = str[i];
|
|
6315
|
+
if (c2 === '\\' && i + 1 < len) {
|
|
6316
|
+
out.push(c2);
|
|
6317
|
+
out.push(str[i + 1]);
|
|
6318
|
+
i += 2;
|
|
6319
|
+
} else {
|
|
6320
|
+
out.push(c2);
|
|
6321
|
+
i++;
|
|
6322
|
+
if (c2 === '"') break;
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
|
|
6326
|
+
} else if (ch === ',') {
|
|
6327
|
+
// Trailing comma check: skip comma if next non-whitespace is } or ]
|
|
6328
|
+
var j = i + 1;
|
|
6329
|
+
while (j < len && (str[j] === ' ' || str[j] === '\t' || str[j] === '\n' || str[j] === '\r')) j++;
|
|
6330
|
+
if (j < len && (str[j] === '}' || str[j] === ']')) {
|
|
6331
|
+
i++; // skip trailing comma
|
|
6332
|
+
} else {
|
|
6333
|
+
out.push(ch);
|
|
6334
|
+
i++;
|
|
6335
|
+
}
|
|
6336
|
+
|
|
6337
|
+
} else {
|
|
6338
|
+
out.push(ch);
|
|
6339
|
+
i++;
|
|
6340
|
+
}
|
|
6341
|
+
}
|
|
6342
|
+
|
|
6343
|
+
return JSON.parse(out.join(''));
|
|
6344
|
+
};
|
|
6345
|
+
|
|
6346
|
+
/**
|
|
6347
|
+
* Apply a bwserve protocol message to the DOM.
|
|
6348
|
+
*
|
|
6349
|
+
* Dispatches one of 9 message types:
|
|
6350
|
+
* replace — bw.DOM(target, node)
|
|
6351
|
+
* append — target.appendChild(bw.createDOM(node))
|
|
6352
|
+
* remove — bw.cleanup(target); target.remove()
|
|
6353
|
+
* patch — bw.patch(target, content, attr)
|
|
6354
|
+
* batch — iterate ops, call clientApply for each
|
|
6355
|
+
* message — bw.message(target, action, data)
|
|
6356
|
+
* register — store a named function for later call()
|
|
6357
|
+
* call — invoke a registered or built-in function
|
|
6358
|
+
* exec — execute arbitrary JS (requires allowExec)
|
|
6359
|
+
*
|
|
6360
|
+
* Target resolution:
|
|
6361
|
+
* Starts with '#' or '.' → CSS selector (querySelector)
|
|
6362
|
+
* Otherwise → getElementById, then bw._el fallback
|
|
6363
|
+
*
|
|
6364
|
+
* @param {Object} msg - Protocol message
|
|
6365
|
+
* @returns {boolean} true if the message was applied successfully
|
|
6366
|
+
* @category Server
|
|
6367
|
+
*/
|
|
6368
|
+
bw.clientApply = function(msg) {
|
|
6369
|
+
if (!msg || !msg.type) return false;
|
|
6370
|
+
|
|
6371
|
+
var type = msg.type;
|
|
6372
|
+
var target = msg.target;
|
|
6373
|
+
|
|
6374
|
+
if (type === 'replace') {
|
|
6375
|
+
var el = bw._el(target);
|
|
6376
|
+
if (!el) return false;
|
|
6377
|
+
bw.DOM(el, msg.node);
|
|
6378
|
+
return true;
|
|
6379
|
+
|
|
6380
|
+
} else if (type === 'patch') {
|
|
6381
|
+
var patched = bw.patch(target, msg.content, msg.attr);
|
|
6382
|
+
return patched !== null;
|
|
6383
|
+
|
|
6384
|
+
} else if (type === 'append') {
|
|
6385
|
+
var parent = bw._el(target);
|
|
6386
|
+
if (!parent) return false;
|
|
6387
|
+
var child = bw.createDOM(msg.node);
|
|
6388
|
+
parent.appendChild(child);
|
|
6389
|
+
return true;
|
|
6390
|
+
|
|
6391
|
+
} else if (type === 'remove') {
|
|
6392
|
+
var toRemove = bw._el(target);
|
|
6393
|
+
if (!toRemove) return false;
|
|
6394
|
+
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
6395
|
+
toRemove.remove();
|
|
6396
|
+
return true;
|
|
6397
|
+
|
|
6398
|
+
} else if (type === 'batch') {
|
|
6399
|
+
if (!_isA(msg.ops)) return false;
|
|
6400
|
+
var allOk = true;
|
|
6401
|
+
msg.ops.forEach(function(op) {
|
|
6402
|
+
if (!bw.clientApply(op)) allOk = false;
|
|
6403
|
+
});
|
|
6404
|
+
return allOk;
|
|
6405
|
+
|
|
6406
|
+
} else if (type === 'message') {
|
|
6407
|
+
return bw.message(msg.target, msg.action, msg.data);
|
|
6408
|
+
|
|
6409
|
+
} else if (type === 'register') {
|
|
6410
|
+
if (!msg.name || !msg.body) return false;
|
|
6411
|
+
try {
|
|
6412
|
+
bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
|
|
6413
|
+
return true;
|
|
6414
|
+
} catch (e) {
|
|
6415
|
+
_ce('[bw] register error:', msg.name, e);
|
|
6416
|
+
return false;
|
|
6417
|
+
}
|
|
6418
|
+
|
|
6419
|
+
} else if (type === 'call') {
|
|
6420
|
+
if (!msg.name) return false;
|
|
6421
|
+
var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
|
|
6422
|
+
if (!_is(fn, 'function')) return false;
|
|
6423
|
+
try {
|
|
6424
|
+
var args = _isA(msg.args) ? msg.args : [];
|
|
6425
|
+
fn.apply(null, args);
|
|
6426
|
+
return true;
|
|
6427
|
+
} catch (e) {
|
|
6428
|
+
_ce('[bw] call error:', msg.name, e);
|
|
6429
|
+
return false;
|
|
6430
|
+
}
|
|
6431
|
+
|
|
6432
|
+
} else if (type === 'exec') {
|
|
6433
|
+
if (!bw._allowExec) {
|
|
6434
|
+
_cw('[bw] exec rejected: allowExec is not enabled');
|
|
6435
|
+
return false;
|
|
6436
|
+
}
|
|
6437
|
+
if (!msg.code) return false;
|
|
6438
|
+
try {
|
|
6439
|
+
new Function(msg.code)();
|
|
6440
|
+
return true;
|
|
6441
|
+
} catch (e) {
|
|
6442
|
+
_ce('[bw] exec error:', e);
|
|
6443
|
+
return false;
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
|
|
6447
|
+
return false;
|
|
6448
|
+
};
|
|
6449
|
+
|
|
6450
|
+
/**
|
|
6451
|
+
* Connect to a bwserve SSE endpoint and apply protocol messages automatically.
|
|
6452
|
+
*
|
|
6453
|
+
* Returns a connection object with sendAction(), on(), and close() methods.
|
|
6454
|
+
*
|
|
6455
|
+
* @param {string} url - SSE endpoint URL (e.g., '/__bw/events/client-1')
|
|
6456
|
+
* @param {Object} [opts] - Connection options
|
|
6457
|
+
* @param {string} [opts.transport='sse'] - Transport type: 'sse' (default) or 'poll'
|
|
6458
|
+
* @param {number} [opts.interval=2000] - Poll interval in ms (only for 'poll' transport)
|
|
6459
|
+
* @param {string} [opts.actionUrl] - POST endpoint for actions (default: derived from url)
|
|
6460
|
+
* @param {boolean} [opts.reconnect=true] - Auto-reconnect on disconnect
|
|
6461
|
+
* @param {boolean} [opts.allowExec=false] - Enable exec message type (arbitrary JS execution)
|
|
6462
|
+
* @param {Function} [opts.onStatus] - Status callback: 'connecting'|'connected'|'disconnected'
|
|
6463
|
+
* @param {Function} [opts.onMessage] - Raw message callback (before clientApply)
|
|
6464
|
+
* @returns {Object} Connection object { sendAction, on, close, status }
|
|
6465
|
+
* @category Server
|
|
6466
|
+
*/
|
|
6467
|
+
bw.clientConnect = function(url, opts) {
|
|
6468
|
+
opts = opts || {};
|
|
6469
|
+
var transport = opts.transport || 'sse';
|
|
6470
|
+
var actionUrl = opts.actionUrl || url.replace(/\/events\//, '/action/');
|
|
6471
|
+
var reconnect = opts.reconnect !== false;
|
|
6472
|
+
var onStatus = opts.onStatus || function() {};
|
|
6473
|
+
var onMessage = opts.onMessage || null;
|
|
6474
|
+
var handlers = {};
|
|
6475
|
+
// Set the global allowExec flag from connection options
|
|
6476
|
+
bw._allowExec = !!opts.allowExec;
|
|
6477
|
+
var conn = {
|
|
6478
|
+
status: 'connecting',
|
|
6479
|
+
_es: null,
|
|
6480
|
+
_pollTimer: null
|
|
6481
|
+
};
|
|
6482
|
+
|
|
6483
|
+
function setStatus(s) {
|
|
6484
|
+
conn.status = s;
|
|
6485
|
+
onStatus(s);
|
|
6486
|
+
}
|
|
6487
|
+
|
|
6488
|
+
function handleMessage(data) {
|
|
6489
|
+
try {
|
|
6490
|
+
var msg = _is(data, 'string') ? bw.clientParse(data) : data;
|
|
6491
|
+
if (onMessage) onMessage(msg);
|
|
6492
|
+
if (handlers.message) handlers.message(msg);
|
|
6493
|
+
bw.clientApply(msg);
|
|
6494
|
+
} catch (e) {
|
|
6495
|
+
if (handlers.error) handlers.error(e);
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
|
|
6499
|
+
if (transport === 'sse' && typeof EventSource !== 'undefined') {
|
|
6500
|
+
setStatus('connecting');
|
|
6501
|
+
var es = new EventSource(url);
|
|
6502
|
+
conn._es = es;
|
|
6503
|
+
|
|
6504
|
+
es.onopen = function() {
|
|
6505
|
+
setStatus('connected');
|
|
6506
|
+
if (handlers.open) handlers.open();
|
|
6507
|
+
};
|
|
6508
|
+
|
|
6509
|
+
es.onmessage = function(e) {
|
|
6510
|
+
handleMessage(e.data);
|
|
6511
|
+
};
|
|
6512
|
+
|
|
6513
|
+
es.onerror = function() {
|
|
6514
|
+
if (conn.status === 'connected') {
|
|
6515
|
+
setStatus('disconnected');
|
|
6516
|
+
}
|
|
6517
|
+
if (handlers.error) handlers.error(new Error('SSE connection error'));
|
|
6518
|
+
if (!reconnect) {
|
|
6519
|
+
es.close();
|
|
6520
|
+
}
|
|
6521
|
+
// EventSource auto-reconnects by default when reconnect=true
|
|
6522
|
+
};
|
|
6523
|
+
} else if (transport === 'poll') {
|
|
6524
|
+
var interval = opts.interval || 2000;
|
|
6525
|
+
setStatus('connected');
|
|
6526
|
+
conn._pollTimer = setInterval(function() {
|
|
6527
|
+
fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
|
|
6528
|
+
if (_isA(msgs)) {
|
|
6529
|
+
msgs.forEach(handleMessage);
|
|
6530
|
+
} else if (msgs && msgs.type) {
|
|
6531
|
+
handleMessage(msgs);
|
|
6532
|
+
}
|
|
6533
|
+
}).catch(function(e) {
|
|
6534
|
+
if (handlers.error) handlers.error(e);
|
|
6535
|
+
});
|
|
6536
|
+
}, interval);
|
|
6537
|
+
}
|
|
6538
|
+
|
|
6539
|
+
/**
|
|
6540
|
+
* Send an action to the server via POST.
|
|
6541
|
+
* @param {string} action - Action name
|
|
6542
|
+
* @param {Object} [data] - Action payload
|
|
6543
|
+
*/
|
|
6544
|
+
conn.sendAction = function(action, data) {
|
|
6545
|
+
var body = JSON.stringify({ type: 'action', action: action, data: data || {} });
|
|
6546
|
+
fetch(actionUrl, {
|
|
6547
|
+
method: 'POST',
|
|
6548
|
+
headers: { 'Content-Type': 'application/json' },
|
|
6549
|
+
body: body
|
|
6550
|
+
}).catch(function(e) {
|
|
6551
|
+
if (handlers.error) handlers.error(e);
|
|
6552
|
+
});
|
|
6553
|
+
};
|
|
6554
|
+
|
|
6555
|
+
/**
|
|
6556
|
+
* Register an event handler.
|
|
6557
|
+
* @param {string} event - 'open'|'message'|'error'|'close'
|
|
6558
|
+
* @param {Function} handler
|
|
6559
|
+
*/
|
|
6560
|
+
conn.on = function(event, handler) {
|
|
6561
|
+
handlers[event] = handler;
|
|
6562
|
+
return conn;
|
|
6563
|
+
};
|
|
6564
|
+
|
|
6565
|
+
/**
|
|
6566
|
+
* Close the connection.
|
|
6567
|
+
*/
|
|
6568
|
+
conn.close = function() {
|
|
6569
|
+
if (conn._es) {
|
|
6570
|
+
conn._es.close();
|
|
6571
|
+
conn._es = null;
|
|
6572
|
+
}
|
|
6573
|
+
if (conn._pollTimer) {
|
|
6574
|
+
clearInterval(conn._pollTimer);
|
|
6575
|
+
conn._pollTimer = null;
|
|
6576
|
+
}
|
|
6577
|
+
setStatus('disconnected');
|
|
6578
|
+
if (handlers.close) handlers.close();
|
|
6579
|
+
};
|
|
6580
|
+
|
|
6581
|
+
return conn;
|
|
6582
|
+
};
|
|
6583
|
+
|
|
5836
6584
|
// ===================================================================================
|
|
5837
6585
|
// bw.inspect() — Debug utility
|
|
5838
6586
|
// ===================================================================================
|
|
@@ -5859,33 +6607,33 @@ bw.inspect = function(target) {
|
|
|
5859
6607
|
el = target.element;
|
|
5860
6608
|
comp = target;
|
|
5861
6609
|
} else {
|
|
5862
|
-
if (
|
|
6610
|
+
if (_is(target, 'string')) {
|
|
5863
6611
|
el = bw.$(target)[0];
|
|
5864
6612
|
}
|
|
5865
6613
|
if (!el) {
|
|
5866
|
-
|
|
6614
|
+
_cw('bw.inspect: element not found');
|
|
5867
6615
|
return null;
|
|
5868
6616
|
}
|
|
5869
6617
|
comp = el._bwComponentHandle;
|
|
5870
6618
|
}
|
|
5871
6619
|
if (!comp) {
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
6620
|
+
_cl('bw.inspect: no ComponentHandle on this element');
|
|
6621
|
+
_cl(' Tag:', el.tagName);
|
|
6622
|
+
_cl(' Classes:', el.className);
|
|
6623
|
+
_cl(' _bw_state:', el._bw_state || '(none)');
|
|
5876
6624
|
return null;
|
|
5877
6625
|
}
|
|
5878
6626
|
var deps = comp._bindings.reduce(function(s, b) {
|
|
5879
6627
|
return s.concat(b.deps || []);
|
|
5880
6628
|
}, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
|
|
5881
6629
|
console.group('Component: ' + comp._bwId);
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
6630
|
+
_cl('State:', comp._state);
|
|
6631
|
+
_cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
|
|
6632
|
+
_cl('Methods:', _keys(comp._methods));
|
|
6633
|
+
_cl('Actions:', _keys(comp._actions));
|
|
6634
|
+
_cl('User tag:', comp._userTag || '(none)');
|
|
6635
|
+
_cl('Mounted:', comp.mounted);
|
|
6636
|
+
_cl('Element:', comp.element);
|
|
5889
6637
|
console.groupEnd();
|
|
5890
6638
|
return comp;
|
|
5891
6639
|
};
|
|
@@ -5908,8 +6656,8 @@ bw.compile = function(taco) {
|
|
|
5908
6656
|
// Pre-extract all binding expressions
|
|
5909
6657
|
var precompiled = [];
|
|
5910
6658
|
function walkExpressions(node) {
|
|
5911
|
-
if (!node
|
|
5912
|
-
if (
|
|
6659
|
+
if (!_is(node, 'object')) return;
|
|
6660
|
+
if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
|
|
5913
6661
|
var parsed = bw._parseBindings(node.c);
|
|
5914
6662
|
for (var i = 0; i < parsed.length; i++) {
|
|
5915
6663
|
try {
|
|
@@ -5924,9 +6672,9 @@ bw.compile = function(taco) {
|
|
|
5924
6672
|
}
|
|
5925
6673
|
if (node.a) {
|
|
5926
6674
|
for (var key in node.a) {
|
|
5927
|
-
if (
|
|
6675
|
+
if (_hop.call(node.a, key)) {
|
|
5928
6676
|
var v = node.a[key];
|
|
5929
|
-
if (
|
|
6677
|
+
if (_is(v, 'string') && v.indexOf('${') >= 0) {
|
|
5930
6678
|
var parsed2 = bw._parseBindings(v);
|
|
5931
6679
|
for (var j = 0; j < parsed2.length; j++) {
|
|
5932
6680
|
try {
|
|
@@ -5942,9 +6690,9 @@ bw.compile = function(taco) {
|
|
|
5942
6690
|
}
|
|
5943
6691
|
}
|
|
5944
6692
|
}
|
|
5945
|
-
if (
|
|
6693
|
+
if (_isA(node.c)) {
|
|
5946
6694
|
for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
|
|
5947
|
-
} else if (node.c
|
|
6695
|
+
} else if (_is(node.c, 'object') && node.c.t) {
|
|
5948
6696
|
walkExpressions(node.c);
|
|
5949
6697
|
}
|
|
5950
6698
|
}
|
|
@@ -5956,7 +6704,7 @@ bw.compile = function(taco) {
|
|
|
5956
6704
|
handle._precompiledBindings = precompiled;
|
|
5957
6705
|
if (initialState) {
|
|
5958
6706
|
for (var k in initialState) {
|
|
5959
|
-
if (
|
|
6707
|
+
if (_hop.call(initialState, k)) {
|
|
5960
6708
|
handle._state[k] = initialState[k];
|
|
5961
6709
|
}
|
|
5962
6710
|
}
|
|
@@ -5987,18 +6735,18 @@ bw.compile = function(taco) {
|
|
|
5987
6735
|
bw.css = function(rules, options = {}) {
|
|
5988
6736
|
const { minify = false, pretty = !minify } = options;
|
|
5989
6737
|
|
|
5990
|
-
if (
|
|
6738
|
+
if (_is(rules, 'string')) return rules;
|
|
5991
6739
|
|
|
5992
6740
|
let css = '';
|
|
5993
6741
|
const indent = pretty ? ' ' : '';
|
|
5994
6742
|
const newline = pretty ? '\n' : '';
|
|
5995
6743
|
const space = pretty ? ' ' : '';
|
|
5996
6744
|
|
|
5997
|
-
if (
|
|
6745
|
+
if (_isA(rules)) {
|
|
5998
6746
|
css = rules.map(rule => bw.css(rule, options)).join(newline);
|
|
5999
|
-
} else if (
|
|
6747
|
+
} else if (_is(rules, 'object')) {
|
|
6000
6748
|
Object.entries(rules).forEach(([selector, styles]) => {
|
|
6001
|
-
if (
|
|
6749
|
+
if (_is(styles, 'object')) {
|
|
6002
6750
|
// Handle @media, @keyframes, @supports — recurse into nested block
|
|
6003
6751
|
if (selector.charAt(0) === '@') {
|
|
6004
6752
|
const inner = bw.css(styles, options);
|
|
@@ -6047,7 +6795,7 @@ bw.css = function(rules, options = {}) {
|
|
|
6047
6795
|
*/
|
|
6048
6796
|
bw.injectCSS = function(css, options = {}) {
|
|
6049
6797
|
if (!bw._isBrowser) {
|
|
6050
|
-
|
|
6798
|
+
_cw('bw.injectCSS requires a DOM environment');
|
|
6051
6799
|
return null;
|
|
6052
6800
|
}
|
|
6053
6801
|
|
|
@@ -6064,7 +6812,7 @@ bw.injectCSS = function(css, options = {}) {
|
|
|
6064
6812
|
}
|
|
6065
6813
|
|
|
6066
6814
|
// Convert CSS if needed
|
|
6067
|
-
const cssStr =
|
|
6815
|
+
const cssStr = _is(css, 'string') ? css : bw.css(css, options);
|
|
6068
6816
|
|
|
6069
6817
|
// Set or append CSS
|
|
6070
6818
|
if (append && styleEl.textContent) {
|
|
@@ -6094,7 +6842,7 @@ bw.s = function() {
|
|
|
6094
6842
|
var result = {};
|
|
6095
6843
|
for (var i = 0; i < arguments.length; i++) {
|
|
6096
6844
|
var arg = arguments[i];
|
|
6097
|
-
if (arg
|
|
6845
|
+
if (_is(arg, 'object')) Object.assign(result, arg);
|
|
6098
6846
|
}
|
|
6099
6847
|
return result;
|
|
6100
6848
|
};
|
|
@@ -6217,7 +6965,7 @@ bw.u = {
|
|
|
6217
6965
|
bw.responsive = function(selector, breakpoints) {
|
|
6218
6966
|
var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
|
|
6219
6967
|
var parts = [];
|
|
6220
|
-
|
|
6968
|
+
_keys(breakpoints).forEach(function(key) {
|
|
6221
6969
|
var rules = {};
|
|
6222
6970
|
if (key === 'base') {
|
|
6223
6971
|
rules[selector] = breakpoints[key];
|
|
@@ -6289,18 +7037,18 @@ if (bw._isBrowser) {
|
|
|
6289
7037
|
if (!selector) return [];
|
|
6290
7038
|
|
|
6291
7039
|
// Already an array
|
|
6292
|
-
if (
|
|
7040
|
+
if (_isA(selector)) return selector;
|
|
6293
7041
|
|
|
6294
7042
|
// Single element
|
|
6295
7043
|
if (selector.nodeType) return [selector];
|
|
6296
7044
|
|
|
6297
7045
|
// NodeList or HTMLCollection
|
|
6298
|
-
if (selector.length !== undefined &&
|
|
7046
|
+
if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
6299
7047
|
return Array.from(selector);
|
|
6300
7048
|
}
|
|
6301
7049
|
|
|
6302
7050
|
// CSS selector string
|
|
6303
|
-
if (
|
|
7051
|
+
if (_is(selector, 'string')) {
|
|
6304
7052
|
return Array.from(document.querySelectorAll(selector));
|
|
6305
7053
|
}
|
|
6306
7054
|
|
|
@@ -6804,7 +7552,7 @@ bw.makeTable = function(config) {
|
|
|
6804
7552
|
|
|
6805
7553
|
// Auto-detect columns if not provided
|
|
6806
7554
|
const cols = columns || (data.length > 0
|
|
6807
|
-
?
|
|
7555
|
+
? _keys(data[0]).map(key => ({ key, label: key }))
|
|
6808
7556
|
: []);
|
|
6809
7557
|
|
|
6810
7558
|
// Current sort state
|
|
@@ -6819,7 +7567,7 @@ bw.makeTable = function(config) {
|
|
|
6819
7567
|
const bVal = b[currentSortColumn];
|
|
6820
7568
|
|
|
6821
7569
|
// Handle different types
|
|
6822
|
-
if (
|
|
7570
|
+
if (_is(aVal, 'number') && _is(bVal, 'number')) {
|
|
6823
7571
|
return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
6824
7572
|
}
|
|
6825
7573
|
|
|
@@ -6929,7 +7677,7 @@ bw.makeTable = function(config) {
|
|
|
6929
7677
|
bw.makeTableFromArray = function(config) {
|
|
6930
7678
|
const { data = [], headerRow = true, columns, ...rest } = config;
|
|
6931
7679
|
|
|
6932
|
-
if (!
|
|
7680
|
+
if (!_isA(data) || data.length === 0) {
|
|
6933
7681
|
return bw.makeTable({ data: [], columns: columns || [], ...rest });
|
|
6934
7682
|
}
|
|
6935
7683
|
|
|
@@ -7011,7 +7759,7 @@ bw.makeBarChart = function(config) {
|
|
|
7011
7759
|
className = ''
|
|
7012
7760
|
} = config;
|
|
7013
7761
|
|
|
7014
|
-
if (!
|
|
7762
|
+
if (!_isA(data) || data.length === 0) {
|
|
7015
7763
|
return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
|
|
7016
7764
|
}
|
|
7017
7765
|
|
|
@@ -7160,7 +7908,7 @@ bw._componentRegistry = new Map();
|
|
|
7160
7908
|
*/
|
|
7161
7909
|
bw.render = function(element, position, taco) {
|
|
7162
7910
|
// Get target element
|
|
7163
|
-
const targetEl =
|
|
7911
|
+
const targetEl = _is(element, 'string')
|
|
7164
7912
|
? document.querySelector(element)
|
|
7165
7913
|
: element;
|
|
7166
7914
|
|
|
@@ -7310,7 +8058,7 @@ bw.render = function(element, position, taco) {
|
|
|
7310
8058
|
setContent(content) {
|
|
7311
8059
|
this._taco.c = content;
|
|
7312
8060
|
if (this.element) {
|
|
7313
|
-
if (
|
|
8061
|
+
if (_is(content, 'string')) {
|
|
7314
8062
|
this.element.textContent = content;
|
|
7315
8063
|
} else {
|
|
7316
8064
|
// Re-render for complex content
|