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.umd.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
/*! bitwrench v2.0.
|
|
1
|
+
/*! bitwrench v2.0.17 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
5
5
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bw = factory());
|
|
6
6
|
})(this, (function () { 'use strict';
|
|
7
7
|
|
|
8
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
8
9
|
/**
|
|
9
10
|
* Auto-generated version file from package.json
|
|
10
11
|
* DO NOT EDIT DIRECTLY - Use npm run generate-version
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
const VERSION_INFO = {
|
|
14
|
-
version: '2.0.
|
|
15
|
+
version: '2.0.17',
|
|
15
16
|
name: 'bitwrench',
|
|
16
17
|
description: 'A library for javascript UI functions.',
|
|
17
18
|
license: 'BSD-2-Clause',
|
|
18
19
|
homepage: 'https://deftio.github.com/bitwrench/pages',
|
|
19
20
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
20
21
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
21
|
-
buildDate: '2026-03-
|
|
22
|
+
buildDate: '2026-03-13T23:15:10.823Z'
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -436,12 +437,11 @@
|
|
|
436
437
|
var lightBase = config.light || hslToHex([h, 8, 97]);
|
|
437
438
|
var darkBase = config.dark || hslToHex([h, 10, 13]);
|
|
438
439
|
|
|
439
|
-
// Background & surface tokens —
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
var
|
|
444
|
-
var surfBase = config.surface || '#f8f9fa';
|
|
440
|
+
// Background & surface tokens — tinted with primary hue for theme personality.
|
|
441
|
+
// Very subtle: bg at L=98/S=6, surface at L=96/S=8.
|
|
442
|
+
// User can override with config.background / config.surface.
|
|
443
|
+
var bgBase = config.background || hslToHex([h, 6, 98]);
|
|
444
|
+
var surfBase = config.surface || hslToHex([h, 8, 96]);
|
|
445
445
|
|
|
446
446
|
var palette = {
|
|
447
447
|
primary: deriveShades(config.primary),
|
|
@@ -1574,7 +1574,7 @@
|
|
|
1574
1574
|
'@media (min-width: 992px)': { '.bw_container': { 'max-width': '960px' } },
|
|
1575
1575
|
'@media (min-width: 1200px)': { '.bw_container': { 'max-width': '1140px' } },
|
|
1576
1576
|
'.bw_container_fluid': {
|
|
1577
|
-
'width': '100%', 'padding-right': '
|
|
1577
|
+
'width': '100%', 'padding-right': '0.75rem', 'padding-left': '0.75rem',
|
|
1578
1578
|
'margin-right': 'auto', 'margin-left': 'auto'
|
|
1579
1579
|
},
|
|
1580
1580
|
'.bw_row': {
|
|
@@ -1735,7 +1735,8 @@
|
|
|
1735
1735
|
'.bw_badge': {
|
|
1736
1736
|
'display': 'inline-block', 'font-size': '0.875rem',
|
|
1737
1737
|
'font-weight': '600', 'line-height': '1.3', 'text-align': 'center',
|
|
1738
|
-
'white-space': 'nowrap', 'vertical-align': 'baseline'
|
|
1738
|
+
'white-space': 'nowrap', 'vertical-align': 'baseline',
|
|
1739
|
+
'padding': '0.35rem 0.65rem', 'border-radius': '0.25rem'
|
|
1739
1740
|
},
|
|
1740
1741
|
'.bw_badge:empty': { 'display': 'none' },
|
|
1741
1742
|
'.bw_badge_sm': { 'font-size': '0.75rem', 'padding': '0.25rem 0.5rem' },
|
|
@@ -1920,7 +1921,7 @@
|
|
|
1920
1921
|
// ---- Code demo ----
|
|
1921
1922
|
codeDemo: {
|
|
1922
1923
|
'.bw_code_demo': { 'margin-bottom': '2rem' },
|
|
1923
|
-
'.bw_code_pre': { 'margin': '0', 'border': 'none', 'overflow-x': 'auto' },
|
|
1924
|
+
'.bw_code_pre': { 'margin': '0', 'border': 'none', 'overflow-x': 'auto', 'max-width': '100%' },
|
|
1924
1925
|
'.bw_code_block': {
|
|
1925
1926
|
'display': 'block', 'padding': '1.25rem',
|
|
1926
1927
|
'font-family': '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace',
|
|
@@ -2017,7 +2018,7 @@
|
|
|
2017
2018
|
},
|
|
2018
2019
|
'.bw_modal.bw_modal_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
|
|
2019
2020
|
'.bw_modal_dialog': {
|
|
2020
|
-
'position': 'relative', 'width': '100%', 'max-width': '500px', 'margin': '1.75rem auto',
|
|
2021
|
+
'position': 'relative', 'width': 'calc(100% - 1rem)', 'max-width': '500px', 'margin': '1.75rem auto',
|
|
2021
2022
|
'pointer-events': 'none'
|
|
2022
2023
|
},
|
|
2023
2024
|
'.bw_modal.bw_modal_show .bw_modal_dialog': { 'transform': 'translateY(0)' },
|
|
@@ -2047,7 +2048,7 @@
|
|
|
2047
2048
|
'.bw_toast_container.bw_toast_top_center': { 'top': '0', 'left': '50%', 'transform': 'translateX(-50%)' },
|
|
2048
2049
|
'.bw_toast_container.bw_toast_bottom_center': { 'bottom': '0', 'left': '50%', 'transform': 'translateX(-50%)' },
|
|
2049
2050
|
'.bw_toast': {
|
|
2050
|
-
'pointer-events': 'auto', 'width': '350px', 'max-width': '
|
|
2051
|
+
'pointer-events': 'auto', 'width': '350px', 'max-width': 'calc(100vw - 2rem)', 'background-clip': 'padding-box',
|
|
2051
2052
|
'opacity': '0'
|
|
2052
2053
|
},
|
|
2053
2054
|
'.bw_toast.bw_toast_show': { 'opacity': '1', 'transform': 'translateY(0)' },
|
|
@@ -2133,7 +2134,7 @@
|
|
|
2133
2134
|
'.bw_tooltip_wrapper': { 'position': 'relative', 'display': 'inline-block' },
|
|
2134
2135
|
'.bw_tooltip': {
|
|
2135
2136
|
'position': 'absolute', 'z-index': '999',
|
|
2136
|
-
'font-size': '0.875rem', 'white-space': 'nowrap', 'pointer-events': 'none',
|
|
2137
|
+
'font-size': '0.875rem', 'white-space': 'nowrap', 'max-width': 'min(300px, calc(100vw - 1rem))', 'pointer-events': 'none',
|
|
2137
2138
|
'opacity': '0', 'visibility': 'hidden'
|
|
2138
2139
|
},
|
|
2139
2140
|
'.bw_tooltip.bw_tooltip_show': { 'opacity': '1', 'visibility': 'visible' },
|
|
@@ -2153,7 +2154,7 @@
|
|
|
2153
2154
|
'.bw_popover_trigger': { 'cursor': 'pointer' },
|
|
2154
2155
|
'.bw_popover': {
|
|
2155
2156
|
'position': 'absolute', 'z-index': '1000',
|
|
2156
|
-
'min-width': '200px', 'max-width': '320px',
|
|
2157
|
+
'min-width': '200px', 'max-width': 'min(320px, calc(100vw - 2rem))',
|
|
2157
2158
|
'pointer-events': 'none', 'opacity': '0', 'visibility': 'hidden'
|
|
2158
2159
|
},
|
|
2159
2160
|
'.bw_popover.bw_popover_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
|
|
@@ -2336,7 +2337,18 @@
|
|
|
2336
2337
|
'.bw_hero, .bw_hero': { 'padding': '2rem 1rem' },
|
|
2337
2338
|
'.bw_cta_actions, .bw_cta-actions': { 'flex-direction': 'column' },
|
|
2338
2339
|
'.bw_hstack, .bw_hstack': { 'flex-direction': 'column' },
|
|
2339
|
-
'.bw_feature_grid, .bw_feature-grid': { 'grid-template-columns': '1fr' }
|
|
2340
|
+
'.bw_feature_grid, .bw_feature-grid': { 'grid-template-columns': '1fr' },
|
|
2341
|
+
'.bw_modal_dialog': { 'margin': '0.5rem auto' },
|
|
2342
|
+
'.bw_modal_lg': { 'max-width': 'calc(100% - 1rem)' },
|
|
2343
|
+
'.bw_modal_xl': { 'max-width': 'calc(100% - 1rem)' },
|
|
2344
|
+
'.bw_navbar': { 'padding': '0.5rem 0.75rem' },
|
|
2345
|
+
'.bw_navbar_brand': { 'margin-right': '0.5rem', 'font-size': '1rem' },
|
|
2346
|
+
'.bw_navbar_nav': { 'flex-wrap': 'wrap' },
|
|
2347
|
+
'.bw_tooltip': { 'white-space': 'normal' },
|
|
2348
|
+
'.bw_table': { 'display': 'block', 'overflow-x': 'auto', '-webkit-overflow-scrolling': 'touch' },
|
|
2349
|
+
'.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%' },
|
|
2350
|
+
'.bw_container': { 'padding-right': '0.5rem', 'padding-left': '0.5rem' },
|
|
2351
|
+
'.bw_container_fluid': { 'padding-right': '0.5rem', 'padding-left': '0.5rem' }
|
|
2340
2352
|
}
|
|
2341
2353
|
}
|
|
2342
2354
|
};
|
|
@@ -6869,7 +6881,11 @@
|
|
|
6869
6881
|
function make(type, props) {
|
|
6870
6882
|
var def = BCCL[type];
|
|
6871
6883
|
if (!def) throw new Error('bw.make: unknown component type "' + type + '". Available: ' + Object.keys(BCCL).join(', '));
|
|
6872
|
-
|
|
6884
|
+
var taco = def.make(props || {});
|
|
6885
|
+
if (taco && typeof taco === 'object') {
|
|
6886
|
+
taco._bwFactory = { type: type, props: props || {} };
|
|
6887
|
+
}
|
|
6888
|
+
return taco;
|
|
6873
6889
|
}
|
|
6874
6890
|
|
|
6875
6891
|
var components = /*#__PURE__*/Object.freeze({
|
|
@@ -6990,7 +7006,7 @@
|
|
|
6990
7006
|
__monkey_patch_is_nodejs__: {
|
|
6991
7007
|
_value: 'ignore',
|
|
6992
7008
|
set: function(x) {
|
|
6993
|
-
this._value = (
|
|
7009
|
+
this._value = _is(x, 'boolean') ? x : 'ignore';
|
|
6994
7010
|
},
|
|
6995
7011
|
get: function() {
|
|
6996
7012
|
return this._value;
|
|
@@ -7038,6 +7054,67 @@
|
|
|
7038
7054
|
configurable: true
|
|
7039
7055
|
});
|
|
7040
7056
|
|
|
7057
|
+
// ── Internal aliases ─────────────────────────────────────────────────────
|
|
7058
|
+
// Short names for frequently-used builtins and internal methods.
|
|
7059
|
+
// Same pattern as v1 (_to = bw.typeOf, etc.).
|
|
7060
|
+
//
|
|
7061
|
+
// Why: Terser can't shorten global property chains (console.warn,
|
|
7062
|
+
// Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
|
|
7063
|
+
// because it can't prove they're side-effect-free. We can, so we alias
|
|
7064
|
+
// them here. Each alias saves bytes in the minified output, and the short
|
|
7065
|
+
// names also reduce visual noise in the hot paths (binding pipeline,
|
|
7066
|
+
// createDOM, etc.).
|
|
7067
|
+
//
|
|
7068
|
+
// Alias Target Sites
|
|
7069
|
+
// ───────── ────────────────────────────────────── ─────
|
|
7070
|
+
// _hop Object.prototype.hasOwnProperty 15
|
|
7071
|
+
// _isA Array.isArray 25
|
|
7072
|
+
// _keys Object.keys 7
|
|
7073
|
+
// _to bw.typeOf (type string) 26
|
|
7074
|
+
// _is type check boolean: _is(x,'string') ~50
|
|
7075
|
+
// _cw console.warn 8
|
|
7076
|
+
// _cl console.log 11
|
|
7077
|
+
// _ce console.error 4
|
|
7078
|
+
// _chp ComponentHandle.prototype 28 (defined after constructor)
|
|
7079
|
+
//
|
|
7080
|
+
// Note: document.createElement etc. are NOT aliased because they require
|
|
7081
|
+
// `this === document` and .bind() would add overhead on every call.
|
|
7082
|
+
// Console aliases use thin wrappers (not direct refs) so test monkey-
|
|
7083
|
+
// patching of console.warn/log/error continues to work.
|
|
7084
|
+
//
|
|
7085
|
+
// `typeof x` for UNDECLARED globals (window, document, process, require,
|
|
7086
|
+
// EventSource, navigator, Promise, __filename, import.meta) MUST stay as
|
|
7087
|
+
// raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
|
|
7088
|
+
//
|
|
7089
|
+
// ── v1 functional type helpers (kept for reference, not currently used) ──
|
|
7090
|
+
// _toa(x, type, trueVal, falseVal) — bw.typeAssign:
|
|
7091
|
+
// returns trueVal if _to(x)===type, else falseVal.
|
|
7092
|
+
// Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
|
|
7093
|
+
// _toc(x, type, trueVal, falseVal) — bw.typeConvert:
|
|
7094
|
+
// same as _toa but if trueVal/falseVal are functions, calls them with x.
|
|
7095
|
+
// Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
|
|
7096
|
+
// Uncomment if pattern frequency justifies them:
|
|
7097
|
+
// var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
|
|
7098
|
+
// 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); };
|
|
7099
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7100
|
+
var _hop = Object.prototype.hasOwnProperty;
|
|
7101
|
+
var _isA = Array.isArray;
|
|
7102
|
+
var _keys = Object.keys;
|
|
7103
|
+
var _to = typeOf; // imported from bitwrench-utils.js
|
|
7104
|
+
var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
|
|
7105
|
+
// Console aliases use thin wrappers (not direct references) so that test
|
|
7106
|
+
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
7107
|
+
var _cw = function() { console.warn.apply(console, arguments); };
|
|
7108
|
+
var _cl = function() { console.log.apply(console, arguments); };
|
|
7109
|
+
var _ce = function() { console.error.apply(console, arguments); };
|
|
7110
|
+
|
|
7111
|
+
/**
|
|
7112
|
+
* Debug flag. When true, emits console.warn for silent binding failures
|
|
7113
|
+
* (missing paths, null refs, auto-created intermediate objects).
|
|
7114
|
+
* @type {boolean}
|
|
7115
|
+
*/
|
|
7116
|
+
bw.debug = false;
|
|
7117
|
+
|
|
7041
7118
|
/**
|
|
7042
7119
|
* Lazy-resolve Node.js `fs` module.
|
|
7043
7120
|
* Tries require('fs') first (available in CJS/UMD Node.js builds),
|
|
@@ -7185,7 +7262,7 @@
|
|
|
7185
7262
|
*/
|
|
7186
7263
|
bw._el = function(id) {
|
|
7187
7264
|
// Pass-through for DOM elements
|
|
7188
|
-
if (
|
|
7265
|
+
if (!_is(id, 'string')) return id || null;
|
|
7189
7266
|
if (!id) return null;
|
|
7190
7267
|
if (!bw._isBrowser) return null;
|
|
7191
7268
|
|
|
@@ -7281,7 +7358,7 @@
|
|
|
7281
7358
|
* // => '<b>Hello</b> & "world"'
|
|
7282
7359
|
*/
|
|
7283
7360
|
bw.escapeHTML = function(str) {
|
|
7284
|
-
if (
|
|
7361
|
+
if (!_is(str, 'string')) return '';
|
|
7285
7362
|
|
|
7286
7363
|
const escapeMap = {
|
|
7287
7364
|
'&': '&',
|
|
@@ -7354,7 +7431,7 @@
|
|
|
7354
7431
|
}
|
|
7355
7432
|
|
|
7356
7433
|
// Handle arrays of TACOs
|
|
7357
|
-
if (
|
|
7434
|
+
if (_isA(taco)) {
|
|
7358
7435
|
return taco.map(t => bw.html(t, options)).join('');
|
|
7359
7436
|
}
|
|
7360
7437
|
|
|
@@ -7377,15 +7454,15 @@
|
|
|
7377
7454
|
if (taco && taco._bwEach && options.state) {
|
|
7378
7455
|
var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
7379
7456
|
var arr = bw._evaluatePath(options.state, eachExpr);
|
|
7380
|
-
if (!
|
|
7457
|
+
if (!_isA(arr)) return '';
|
|
7381
7458
|
return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
|
|
7382
7459
|
}
|
|
7383
7460
|
|
|
7384
7461
|
// Handle primitives and non-TACO objects
|
|
7385
|
-
if (
|
|
7462
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
7386
7463
|
var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
|
|
7387
7464
|
// Resolve template bindings if state provided
|
|
7388
|
-
if (options.state &&
|
|
7465
|
+
if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
|
|
7389
7466
|
str = bw._resolveTemplate(str, options.state, !!options.compile);
|
|
7390
7467
|
}
|
|
7391
7468
|
return str;
|
|
@@ -7405,10 +7482,18 @@
|
|
|
7405
7482
|
// Skip null, undefined, false
|
|
7406
7483
|
if (value == null || value === false) continue;
|
|
7407
7484
|
|
|
7408
|
-
//
|
|
7409
|
-
if (key.startsWith('on'))
|
|
7485
|
+
// Serialize event handlers via funcRegister
|
|
7486
|
+
if (key.startsWith('on')) {
|
|
7487
|
+
if (_is(value, 'function')) {
|
|
7488
|
+
var fnId = bw.funcRegister(value);
|
|
7489
|
+
attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
|
|
7490
|
+
} else if (_is(value, 'string')) {
|
|
7491
|
+
attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
|
|
7492
|
+
}
|
|
7493
|
+
continue;
|
|
7494
|
+
}
|
|
7410
7495
|
|
|
7411
|
-
if (key === 'style' &&
|
|
7496
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
7412
7497
|
// Convert style object to string
|
|
7413
7498
|
const styleStr = Object.entries(value)
|
|
7414
7499
|
.filter(([, v]) => v != null)
|
|
@@ -7419,7 +7504,7 @@
|
|
|
7419
7504
|
}
|
|
7420
7505
|
} else if (key === 'class') {
|
|
7421
7506
|
// Handle class as array or string
|
|
7422
|
-
const classStr =
|
|
7507
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
7423
7508
|
if (classStr) {
|
|
7424
7509
|
attrStr += ` class="${bw.escapeHTML(classStr)}"`;
|
|
7425
7510
|
}
|
|
@@ -7455,13 +7540,184 @@
|
|
|
7455
7540
|
// Process content recursively
|
|
7456
7541
|
let contentStr = content != null ? bw.html(content, options) : '';
|
|
7457
7542
|
// Resolve template bindings in content if state provided
|
|
7458
|
-
if (options.state &&
|
|
7543
|
+
if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
|
|
7459
7544
|
contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
|
|
7460
7545
|
}
|
|
7461
7546
|
|
|
7462
7547
|
return `<${tag}${attrStr}>${contentStr}</${tag}>`;
|
|
7463
7548
|
};
|
|
7464
7549
|
|
|
7550
|
+
/**
|
|
7551
|
+
* Generate a complete, self-contained HTML document from TACO content.
|
|
7552
|
+
*
|
|
7553
|
+
* Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
|
|
7554
|
+
* func registry emission (so serialized event handlers work), optional theme,
|
|
7555
|
+
* and extra head elements. Designed for static site generation, offline/airgapped
|
|
7556
|
+
* use, and the "static site that isn't static" workflow.
|
|
7557
|
+
*
|
|
7558
|
+
* @param {Object} [opts={}] - Page options
|
|
7559
|
+
* @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
|
|
7560
|
+
* @param {string} [opts.title='bitwrench'] - Page title
|
|
7561
|
+
* @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
|
|
7562
|
+
* @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
|
|
7563
|
+
* @param {string} [opts.css=''] - Additional CSS for <style> block
|
|
7564
|
+
* @param {string|Object} [opts.theme=null] - Theme preset name or config object
|
|
7565
|
+
* @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
|
|
7566
|
+
* @param {string} [opts.favicon=''] - Favicon URL
|
|
7567
|
+
* @param {string} [opts.lang='en'] - HTML lang attribute
|
|
7568
|
+
* @returns {string} Complete HTML document string
|
|
7569
|
+
* @category DOM Generation
|
|
7570
|
+
* @see bw.html
|
|
7571
|
+
* @example
|
|
7572
|
+
* bw.htmlPage({
|
|
7573
|
+
* title: 'My App',
|
|
7574
|
+
* body: { t: 'h1', c: 'Hello World' },
|
|
7575
|
+
* runtime: 'shim'
|
|
7576
|
+
* })
|
|
7577
|
+
*/
|
|
7578
|
+
bw.htmlPage = function(opts) {
|
|
7579
|
+
opts = opts || {};
|
|
7580
|
+
var title = opts.title || 'bitwrench';
|
|
7581
|
+
var body = opts.body || '';
|
|
7582
|
+
var state = opts.state || undefined;
|
|
7583
|
+
var runtime = opts.runtime || 'shim';
|
|
7584
|
+
var css = opts.css || '';
|
|
7585
|
+
var theme = opts.theme || null;
|
|
7586
|
+
var headExtra = opts.head || [];
|
|
7587
|
+
var favicon = opts.favicon || '';
|
|
7588
|
+
var lang = opts.lang || 'en';
|
|
7589
|
+
|
|
7590
|
+
// Snapshot funcRegistry counter before rendering
|
|
7591
|
+
var fnCounterBefore = bw._fnIDCounter;
|
|
7592
|
+
|
|
7593
|
+
// Render body content
|
|
7594
|
+
var bodyHTML = '';
|
|
7595
|
+
if (_is(body, 'string')) {
|
|
7596
|
+
bodyHTML = body;
|
|
7597
|
+
} else {
|
|
7598
|
+
var htmlOpts = {};
|
|
7599
|
+
if (state) htmlOpts.state = state;
|
|
7600
|
+
bodyHTML = bw.html(body, htmlOpts);
|
|
7601
|
+
}
|
|
7602
|
+
|
|
7603
|
+
// Collect functions registered during this render
|
|
7604
|
+
var fnCounterAfter = bw._fnIDCounter;
|
|
7605
|
+
var registryEntries = '';
|
|
7606
|
+
for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
|
|
7607
|
+
var fnKey = 'bw_fn_' + i;
|
|
7608
|
+
if (bw._fnRegistry[fnKey]) {
|
|
7609
|
+
registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
|
|
7610
|
+
bw._fnRegistry[fnKey].toString() + ';\n';
|
|
7611
|
+
}
|
|
7612
|
+
}
|
|
7613
|
+
|
|
7614
|
+
// Build runtime script for <head>
|
|
7615
|
+
var runtimeHead = '';
|
|
7616
|
+
if (runtime === 'inline') {
|
|
7617
|
+
// Read UMD bundle synchronously if in Node.js
|
|
7618
|
+
var umdSource = null;
|
|
7619
|
+
if (bw._isNode) {
|
|
7620
|
+
try {
|
|
7621
|
+
var fs = (typeof require === 'function') ? require('fs') : null;
|
|
7622
|
+
var pathMod = (typeof require === 'function') ? require('path') : null;
|
|
7623
|
+
if (fs && pathMod) {
|
|
7624
|
+
// Resolve dist/ relative to this source file
|
|
7625
|
+
var srcDir = '';
|
|
7626
|
+
try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
|
|
7627
|
+
catch(e2) { /* ESM: __filename not available */ }
|
|
7628
|
+
if (!srcDir && typeof ({ url: (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench.umd.js', document.baseURI).href)) }) !== 'undefined' && (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench.umd.js', document.baseURI).href))) {
|
|
7629
|
+
var url = (typeof require === 'function') ? require('url') : null;
|
|
7630
|
+
if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath((typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench.umd.js', document.baseURI).href))));
|
|
7631
|
+
}
|
|
7632
|
+
if (srcDir) {
|
|
7633
|
+
var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
|
|
7634
|
+
umdSource = fs.readFileSync(distPath, 'utf8');
|
|
7635
|
+
}
|
|
7636
|
+
}
|
|
7637
|
+
} catch(e) { /* fall through */ }
|
|
7638
|
+
}
|
|
7639
|
+
if (umdSource) {
|
|
7640
|
+
runtimeHead = '<script>' + umdSource + '</script>';
|
|
7641
|
+
} else {
|
|
7642
|
+
// Fallback to shim in browser or if dist not available
|
|
7643
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
7644
|
+
}
|
|
7645
|
+
} else if (runtime === 'cdn') {
|
|
7646
|
+
runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
|
|
7647
|
+
} else if (runtime === 'shim') {
|
|
7648
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
7649
|
+
}
|
|
7650
|
+
// runtime === 'none' → empty
|
|
7651
|
+
|
|
7652
|
+
// Theme CSS
|
|
7653
|
+
var themeCSS = '';
|
|
7654
|
+
if (theme) {
|
|
7655
|
+
var themeConfig = _is(theme, 'string')
|
|
7656
|
+
? (THEME_PRESETS[theme.toLowerCase()] || null)
|
|
7657
|
+
: theme;
|
|
7658
|
+
if (themeConfig) {
|
|
7659
|
+
var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, { inject: false }));
|
|
7660
|
+
themeCSS = themeResult.css;
|
|
7661
|
+
}
|
|
7662
|
+
}
|
|
7663
|
+
|
|
7664
|
+
// Extra <head> elements
|
|
7665
|
+
var headHTML = '';
|
|
7666
|
+
if (_isA(headExtra) && headExtra.length > 0) {
|
|
7667
|
+
headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
|
|
7668
|
+
}
|
|
7669
|
+
|
|
7670
|
+
// Favicon
|
|
7671
|
+
var faviconTag = '';
|
|
7672
|
+
if (favicon) {
|
|
7673
|
+
var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
|
|
7674
|
+
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c];
|
|
7675
|
+
});
|
|
7676
|
+
faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
|
|
7677
|
+
}
|
|
7678
|
+
|
|
7679
|
+
// Escaped title
|
|
7680
|
+
var safeTitle = bw.escapeHTML(title);
|
|
7681
|
+
|
|
7682
|
+
// Combine all CSS
|
|
7683
|
+
var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
|
|
7684
|
+
|
|
7685
|
+
// Body-end script: registry entries + optional loadDefaultStyles
|
|
7686
|
+
var bodyEndScript = '';
|
|
7687
|
+
var bodyEndParts = [];
|
|
7688
|
+
if (registryEntries) {
|
|
7689
|
+
bodyEndParts.push(registryEntries);
|
|
7690
|
+
}
|
|
7691
|
+
if (runtime === 'inline' || runtime === 'cdn') {
|
|
7692
|
+
bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
|
|
7693
|
+
}
|
|
7694
|
+
if (bodyEndParts.length > 0) {
|
|
7695
|
+
bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
|
|
7696
|
+
}
|
|
7697
|
+
|
|
7698
|
+
// Assemble document
|
|
7699
|
+
var parts = [
|
|
7700
|
+
'<!DOCTYPE html>',
|
|
7701
|
+
'<html lang="' + lang + '">',
|
|
7702
|
+
'<head>',
|
|
7703
|
+
'<meta charset="UTF-8">',
|
|
7704
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
7705
|
+
];
|
|
7706
|
+
parts.push('<title>' + safeTitle + '</title>');
|
|
7707
|
+
if (faviconTag) parts.push(faviconTag);
|
|
7708
|
+
if (runtimeHead) parts.push(runtimeHead);
|
|
7709
|
+
if (headHTML) parts.push(headHTML);
|
|
7710
|
+
if (allCSS) parts.push('<style>' + allCSS + '</style>');
|
|
7711
|
+
parts.push('</head>');
|
|
7712
|
+
parts.push('<body>');
|
|
7713
|
+
parts.push(bodyHTML);
|
|
7714
|
+
if (bodyEndScript) parts.push(bodyEndScript);
|
|
7715
|
+
parts.push('</body>');
|
|
7716
|
+
parts.push('</html>');
|
|
7717
|
+
|
|
7718
|
+
return parts.join('\n');
|
|
7719
|
+
};
|
|
7720
|
+
|
|
7465
7721
|
/**
|
|
7466
7722
|
* Create a live DOM element from a TACO object (browser only).
|
|
7467
7723
|
*
|
|
@@ -7506,7 +7762,7 @@
|
|
|
7506
7762
|
}
|
|
7507
7763
|
|
|
7508
7764
|
// Handle text nodes
|
|
7509
|
-
if (
|
|
7765
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
7510
7766
|
return document.createTextNode(String(taco));
|
|
7511
7767
|
}
|
|
7512
7768
|
|
|
@@ -7519,16 +7775,16 @@
|
|
|
7519
7775
|
for (const [key, value] of Object.entries(attrs)) {
|
|
7520
7776
|
if (value == null || value === false) continue;
|
|
7521
7777
|
|
|
7522
|
-
if (key === 'style' &&
|
|
7778
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
7523
7779
|
// Apply styles directly
|
|
7524
7780
|
Object.assign(el.style, value);
|
|
7525
7781
|
} else if (key === 'class') {
|
|
7526
7782
|
// Handle class as array or string
|
|
7527
|
-
const classStr =
|
|
7783
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
7528
7784
|
if (classStr) {
|
|
7529
7785
|
el.className = classStr;
|
|
7530
7786
|
}
|
|
7531
|
-
} else if (key.startsWith('on') &&
|
|
7787
|
+
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
7532
7788
|
// Event handlers
|
|
7533
7789
|
const eventName = key.slice(2).toLowerCase();
|
|
7534
7790
|
el.addEventListener(eventName, value);
|
|
@@ -7548,7 +7804,7 @@
|
|
|
7548
7804
|
// Children with data-bw_id or id attributes get local refs on the parent,
|
|
7549
7805
|
// so o.render functions can access them without any DOM lookup.
|
|
7550
7806
|
if (content != null) {
|
|
7551
|
-
if (
|
|
7807
|
+
if (_isA(content)) {
|
|
7552
7808
|
content.forEach(child => {
|
|
7553
7809
|
if (child != null) {
|
|
7554
7810
|
// Handle ComponentHandle in content arrays (Level 2 children)
|
|
@@ -7568,20 +7824,20 @@
|
|
|
7568
7824
|
if (childEl._bw_refs) {
|
|
7569
7825
|
if (!el._bw_refs) el._bw_refs = {};
|
|
7570
7826
|
for (var rk in childEl._bw_refs) {
|
|
7571
|
-
if (
|
|
7827
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
7572
7828
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
7573
7829
|
}
|
|
7574
7830
|
}
|
|
7575
7831
|
}
|
|
7576
7832
|
}
|
|
7577
7833
|
});
|
|
7578
|
-
} else if (
|
|
7834
|
+
} else if (_is(content, 'object') && content.__bw_raw) {
|
|
7579
7835
|
// Raw HTML content — inject via innerHTML
|
|
7580
7836
|
el.innerHTML = content.v;
|
|
7581
7837
|
} else if (content._bwComponent === true) {
|
|
7582
7838
|
// Single ComponentHandle as content
|
|
7583
7839
|
content.mount(el);
|
|
7584
|
-
} else if (
|
|
7840
|
+
} else if (_is(content, 'object') && content.t) {
|
|
7585
7841
|
var childEl = bw.createDOM(content, options);
|
|
7586
7842
|
el.appendChild(childEl);
|
|
7587
7843
|
var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
|
|
@@ -7592,7 +7848,7 @@
|
|
|
7592
7848
|
if (childEl._bw_refs) {
|
|
7593
7849
|
if (!el._bw_refs) el._bw_refs = {};
|
|
7594
7850
|
for (var rk in childEl._bw_refs) {
|
|
7595
|
-
if (
|
|
7851
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
7596
7852
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
7597
7853
|
}
|
|
7598
7854
|
}
|
|
@@ -7625,7 +7881,7 @@
|
|
|
7625
7881
|
el._bw_render = opts.render;
|
|
7626
7882
|
|
|
7627
7883
|
if (opts.mounted) {
|
|
7628
|
-
|
|
7884
|
+
_cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
|
|
7629
7885
|
}
|
|
7630
7886
|
|
|
7631
7887
|
// Queue initial render (same timing as mounted)
|
|
@@ -7698,7 +7954,7 @@
|
|
|
7698
7954
|
const targetEl = bw._el(target);
|
|
7699
7955
|
|
|
7700
7956
|
if (!targetEl) {
|
|
7701
|
-
|
|
7957
|
+
_ce('bw.DOM: Target element not found:', target);
|
|
7702
7958
|
return null;
|
|
7703
7959
|
}
|
|
7704
7960
|
|
|
@@ -7738,7 +7994,7 @@
|
|
|
7738
7994
|
targetEl.appendChild(taco.element);
|
|
7739
7995
|
}
|
|
7740
7996
|
// Handle arrays
|
|
7741
|
-
else if (
|
|
7997
|
+
else if (_isA(taco)) {
|
|
7742
7998
|
taco.forEach(t => {
|
|
7743
7999
|
if (t != null) {
|
|
7744
8000
|
if (t._bwComponent === true) {
|
|
@@ -7774,7 +8030,7 @@
|
|
|
7774
8030
|
bw.compileProps = function(handle, props = {}) {
|
|
7775
8031
|
const compiledProps = {};
|
|
7776
8032
|
|
|
7777
|
-
|
|
8033
|
+
_keys(props).forEach(key => {
|
|
7778
8034
|
// Create getter/setter for each prop
|
|
7779
8035
|
Object.defineProperty(compiledProps, key, {
|
|
7780
8036
|
get() {
|
|
@@ -8092,17 +8348,17 @@
|
|
|
8092
8348
|
if (attr) {
|
|
8093
8349
|
// Patch an attribute
|
|
8094
8350
|
el.setAttribute(attr, String(content));
|
|
8095
|
-
} else if (
|
|
8351
|
+
} else if (_isA(content)) {
|
|
8096
8352
|
// Patch with array of children (strings and/or TACOs)
|
|
8097
8353
|
el.innerHTML = '';
|
|
8098
8354
|
content.forEach(function(item) {
|
|
8099
|
-
if (
|
|
8355
|
+
if (_is(item, 'string') || _is(item, 'number')) {
|
|
8100
8356
|
el.appendChild(document.createTextNode(String(item)));
|
|
8101
8357
|
} else if (item && item.t) {
|
|
8102
8358
|
el.appendChild(bw.createDOM(item));
|
|
8103
8359
|
}
|
|
8104
8360
|
});
|
|
8105
|
-
} else if (
|
|
8361
|
+
} else if (_is(content, 'object') && content.t) {
|
|
8106
8362
|
// Patch with a TACO — replace children
|
|
8107
8363
|
el.innerHTML = '';
|
|
8108
8364
|
el.appendChild(bw.createDOM(content));
|
|
@@ -8133,7 +8389,7 @@
|
|
|
8133
8389
|
bw.patchAll = function(patches) {
|
|
8134
8390
|
var results = {};
|
|
8135
8391
|
for (var id in patches) {
|
|
8136
|
-
if (
|
|
8392
|
+
if (_hop.call(patches, id)) {
|
|
8137
8393
|
results[id] = bw.patch(id, patches[id]);
|
|
8138
8394
|
}
|
|
8139
8395
|
}
|
|
@@ -8230,7 +8486,7 @@
|
|
|
8230
8486
|
snapshot[i].handler(detail);
|
|
8231
8487
|
called++;
|
|
8232
8488
|
} catch (err) {
|
|
8233
|
-
|
|
8489
|
+
_cw('bw.pub: subscriber error on topic "' + topic + '":', err);
|
|
8234
8490
|
}
|
|
8235
8491
|
}
|
|
8236
8492
|
return called;
|
|
@@ -8326,8 +8582,8 @@
|
|
|
8326
8582
|
* @see bw.funcGetDispatchStr
|
|
8327
8583
|
*/
|
|
8328
8584
|
bw.funcRegister = function(fn, name) {
|
|
8329
|
-
if (
|
|
8330
|
-
var fnID = (
|
|
8585
|
+
if (!_is(fn, 'function')) return '';
|
|
8586
|
+
var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
|
|
8331
8587
|
bw._fnRegistry[fnID] = fn;
|
|
8332
8588
|
return fnID;
|
|
8333
8589
|
};
|
|
@@ -8346,7 +8602,7 @@
|
|
|
8346
8602
|
bw.funcGetById = function(name, errFn) {
|
|
8347
8603
|
name = String(name);
|
|
8348
8604
|
if (name in bw._fnRegistry) return bw._fnRegistry[name];
|
|
8349
|
-
return (
|
|
8605
|
+
return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
|
|
8350
8606
|
};
|
|
8351
8607
|
|
|
8352
8608
|
/**
|
|
@@ -8387,13 +8643,30 @@
|
|
|
8387
8643
|
bw.funcGetRegistry = function() {
|
|
8388
8644
|
var copy = {};
|
|
8389
8645
|
for (var k in bw._fnRegistry) {
|
|
8390
|
-
if (
|
|
8646
|
+
if (_hop.call(bw._fnRegistry, k)) {
|
|
8391
8647
|
copy[k] = bw._fnRegistry[k];
|
|
8392
8648
|
}
|
|
8393
8649
|
}
|
|
8394
8650
|
return copy;
|
|
8395
8651
|
};
|
|
8396
8652
|
|
|
8653
|
+
/**
|
|
8654
|
+
* Minimal runtime shim for funcRegister dispatch in static HTML.
|
|
8655
|
+
* When embedded in a `<script>` tag, provides just enough infrastructure
|
|
8656
|
+
* for `bw.funcGetById()` calls to resolve. The actual function bodies
|
|
8657
|
+
* are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
|
|
8658
|
+
* @type {string}
|
|
8659
|
+
* @category Function Registry
|
|
8660
|
+
*/
|
|
8661
|
+
bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
|
|
8662
|
+
'if(!bw._fnRegistry)bw._fnRegistry={};' +
|
|
8663
|
+
'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
|
|
8664
|
+
'console.warn("bw: unregistered fn "+n)};};' +
|
|
8665
|
+
'bw.funcRegister=function(fn,name){' +
|
|
8666
|
+
'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
|
|
8667
|
+
'bw._fnRegistry[id]=fn;return id;};' +
|
|
8668
|
+
'window.bw=bw;})();';
|
|
8669
|
+
|
|
8397
8670
|
// ===================================================================================
|
|
8398
8671
|
// Template Binding Utilities
|
|
8399
8672
|
// ===================================================================================
|
|
@@ -8421,7 +8694,10 @@
|
|
|
8421
8694
|
var parts = path.split('.');
|
|
8422
8695
|
var val = state;
|
|
8423
8696
|
for (var i = 0; i < parts.length; i++) {
|
|
8424
|
-
if (val == null)
|
|
8697
|
+
if (val == null) {
|
|
8698
|
+
if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
|
|
8699
|
+
return '';
|
|
8700
|
+
}
|
|
8425
8701
|
val = val[parts[i]];
|
|
8426
8702
|
}
|
|
8427
8703
|
return (val == null) ? '' : val;
|
|
@@ -8441,7 +8717,7 @@
|
|
|
8441
8717
|
*/
|
|
8442
8718
|
bw._compiledExprs = {};
|
|
8443
8719
|
bw._resolveTemplate = function(str, state, compile) {
|
|
8444
|
-
if (
|
|
8720
|
+
if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
|
|
8445
8721
|
var bindings = bw._parseBindings(str);
|
|
8446
8722
|
if (bindings.length === 0) return str;
|
|
8447
8723
|
|
|
@@ -8463,6 +8739,7 @@
|
|
|
8463
8739
|
try {
|
|
8464
8740
|
val = bw._compiledExprs[b.expr](state);
|
|
8465
8741
|
} catch (e) {
|
|
8742
|
+
if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
|
|
8466
8743
|
val = '';
|
|
8467
8744
|
}
|
|
8468
8745
|
} else {
|
|
@@ -8571,7 +8848,7 @@
|
|
|
8571
8848
|
this._state = {};
|
|
8572
8849
|
if (o.state) {
|
|
8573
8850
|
for (var k in o.state) {
|
|
8574
|
-
if (
|
|
8851
|
+
if (_hop.call(o.state, k)) {
|
|
8575
8852
|
this._state[k] = o.state[k];
|
|
8576
8853
|
}
|
|
8577
8854
|
}
|
|
@@ -8580,7 +8857,7 @@
|
|
|
8580
8857
|
this._actions = {};
|
|
8581
8858
|
if (o.actions) {
|
|
8582
8859
|
for (var k2 in o.actions) {
|
|
8583
|
-
if (
|
|
8860
|
+
if (_hop.call(o.actions, k2)) {
|
|
8584
8861
|
this._actions[k2] = o.actions[k2];
|
|
8585
8862
|
}
|
|
8586
8863
|
}
|
|
@@ -8590,7 +8867,7 @@
|
|
|
8590
8867
|
if (o.methods) {
|
|
8591
8868
|
var self = this;
|
|
8592
8869
|
for (var k3 in o.methods) {
|
|
8593
|
-
if (
|
|
8870
|
+
if (_hop.call(o.methods, k3)) {
|
|
8594
8871
|
this._methods[k3] = o.methods[k3];
|
|
8595
8872
|
(function(methodName, methodFn) {
|
|
8596
8873
|
self[methodName] = function() {
|
|
@@ -8623,14 +8900,23 @@
|
|
|
8623
8900
|
this._compile = !!o.compile;
|
|
8624
8901
|
this._bw_refs = {};
|
|
8625
8902
|
this._refCounter = 0;
|
|
8903
|
+
// Child component ownership (Bug #5)
|
|
8904
|
+
this._children = [];
|
|
8905
|
+
this._parent = null;
|
|
8906
|
+
// Factory metadata for BCCL rebuild (Bug #6)
|
|
8907
|
+
this._factory = taco._bwFactory || null;
|
|
8626
8908
|
}
|
|
8627
8909
|
|
|
8910
|
+
// Short alias for ComponentHandle.prototype (see alias block at top of file).
|
|
8911
|
+
// 28 method definitions × 25 chars = ~700B raw savings in minified output.
|
|
8912
|
+
var _chp = ComponentHandle.prototype;
|
|
8913
|
+
|
|
8628
8914
|
// ── State Methods ──
|
|
8629
8915
|
|
|
8630
8916
|
/**
|
|
8631
8917
|
* Get a state value. Dot-path supported: `get('user.name')`
|
|
8632
8918
|
*/
|
|
8633
|
-
|
|
8919
|
+
_chp.get = function(key) {
|
|
8634
8920
|
return bw._evaluatePath(this._state, key);
|
|
8635
8921
|
};
|
|
8636
8922
|
|
|
@@ -8640,12 +8926,13 @@
|
|
|
8640
8926
|
* @param {*} value - New value
|
|
8641
8927
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
8642
8928
|
*/
|
|
8643
|
-
|
|
8929
|
+
_chp.set = function(key, value, opts) {
|
|
8644
8930
|
// Dot-path set
|
|
8645
8931
|
var parts = key.split('.');
|
|
8646
8932
|
var obj = this._state;
|
|
8647
8933
|
for (var i = 0; i < parts.length - 1; i++) {
|
|
8648
|
-
if (obj[parts[i]]
|
|
8934
|
+
if (!_is(obj[parts[i]], 'object')) {
|
|
8935
|
+
if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
|
|
8649
8936
|
obj[parts[i]] = {};
|
|
8650
8937
|
}
|
|
8651
8938
|
obj = obj[parts[i]];
|
|
@@ -8665,10 +8952,10 @@
|
|
|
8665
8952
|
/**
|
|
8666
8953
|
* Get a shallow clone of the full state.
|
|
8667
8954
|
*/
|
|
8668
|
-
|
|
8955
|
+
_chp.getState = function() {
|
|
8669
8956
|
var clone = {};
|
|
8670
8957
|
for (var k in this._state) {
|
|
8671
|
-
if (
|
|
8958
|
+
if (_hop.call(this._state, k)) {
|
|
8672
8959
|
clone[k] = this._state[k];
|
|
8673
8960
|
}
|
|
8674
8961
|
}
|
|
@@ -8680,9 +8967,9 @@
|
|
|
8680
8967
|
* @param {Object} updates - Key-value pairs to merge
|
|
8681
8968
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
8682
8969
|
*/
|
|
8683
|
-
|
|
8970
|
+
_chp.setState = function(updates, opts) {
|
|
8684
8971
|
for (var k in updates) {
|
|
8685
|
-
if (
|
|
8972
|
+
if (_hop.call(updates, k)) {
|
|
8686
8973
|
this._state[k] = updates[k];
|
|
8687
8974
|
this._dirtyKeys[k] = true;
|
|
8688
8975
|
}
|
|
@@ -8699,9 +8986,9 @@
|
|
|
8699
8986
|
/**
|
|
8700
8987
|
* Push a value onto an array in state. Clones the array.
|
|
8701
8988
|
*/
|
|
8702
|
-
|
|
8989
|
+
_chp.push = function(key, val) {
|
|
8703
8990
|
var arr = this.get(key);
|
|
8704
|
-
var newArr =
|
|
8991
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
8705
8992
|
newArr.push(val);
|
|
8706
8993
|
this.set(key, newArr);
|
|
8707
8994
|
};
|
|
@@ -8709,9 +8996,9 @@
|
|
|
8709
8996
|
/**
|
|
8710
8997
|
* Splice an array in state. Clones the array.
|
|
8711
8998
|
*/
|
|
8712
|
-
|
|
8999
|
+
_chp.splice = function(key, start, deleteCount) {
|
|
8713
9000
|
var arr = this.get(key);
|
|
8714
|
-
var newArr =
|
|
9001
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
8715
9002
|
var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
|
|
8716
9003
|
Array.prototype.splice.apply(newArr, args);
|
|
8717
9004
|
this.set(key, newArr);
|
|
@@ -8719,7 +9006,7 @@
|
|
|
8719
9006
|
|
|
8720
9007
|
// ── Scheduling ──
|
|
8721
9008
|
|
|
8722
|
-
|
|
9009
|
+
_chp._scheduleDirty = function() {
|
|
8723
9010
|
if (!this._scheduled) {
|
|
8724
9011
|
this._scheduled = true;
|
|
8725
9012
|
bw._dirtyComponents.push(this);
|
|
@@ -8734,17 +9021,17 @@
|
|
|
8734
9021
|
* Creates binding descriptors with refIds for targeted DOM updates.
|
|
8735
9022
|
* @private
|
|
8736
9023
|
*/
|
|
8737
|
-
|
|
9024
|
+
_chp._compileBindings = function() {
|
|
8738
9025
|
this._bindings = [];
|
|
8739
9026
|
this._refCounter = 0;
|
|
8740
|
-
var stateKeys =
|
|
9027
|
+
var stateKeys = _keys(this._state);
|
|
8741
9028
|
var self = this;
|
|
8742
9029
|
|
|
8743
9030
|
function walkTaco(taco, path) {
|
|
8744
|
-
if (taco
|
|
9031
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
8745
9032
|
|
|
8746
9033
|
// Check content for bindings
|
|
8747
|
-
if (
|
|
9034
|
+
if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
|
|
8748
9035
|
var refId = 'bw_ref_' + self._refCounter++;
|
|
8749
9036
|
var parsed = bw._parseBindings(taco.c);
|
|
8750
9037
|
var deps = [];
|
|
@@ -8766,10 +9053,10 @@
|
|
|
8766
9053
|
// Check attributes for bindings
|
|
8767
9054
|
if (taco.a) {
|
|
8768
9055
|
for (var attrName in taco.a) {
|
|
8769
|
-
if (!
|
|
9056
|
+
if (!_hop.call(taco.a, attrName)) continue;
|
|
8770
9057
|
if (attrName === 'data-bw_ref') continue;
|
|
8771
9058
|
var attrVal = taco.a[attrName];
|
|
8772
|
-
if (
|
|
9059
|
+
if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
|
|
8773
9060
|
var refId2 = 'bw_ref_' + self._refCounter++;
|
|
8774
9061
|
var parsed2 = bw._parseBindings(attrVal);
|
|
8775
9062
|
var deps2 = [];
|
|
@@ -8795,9 +9082,27 @@
|
|
|
8795
9082
|
}
|
|
8796
9083
|
|
|
8797
9084
|
// Recurse into children
|
|
8798
|
-
if (
|
|
9085
|
+
if (_isA(taco.c)) {
|
|
8799
9086
|
for (var i = 0; i < taco.c.length; i++) {
|
|
8800
|
-
|
|
9087
|
+
// Wrap string children with ${expr} in a span so patches target the span, not the parent
|
|
9088
|
+
if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
|
|
9089
|
+
var mixedRefId = 'bw_ref_' + self._refCounter++;
|
|
9090
|
+
var mixedParsed = bw._parseBindings(taco.c[i]);
|
|
9091
|
+
var mixedDeps = [];
|
|
9092
|
+
for (var mi = 0; mi < mixedParsed.length; mi++) {
|
|
9093
|
+
mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
|
|
9094
|
+
}
|
|
9095
|
+
self._bindings.push({
|
|
9096
|
+
expr: taco.c[i],
|
|
9097
|
+
type: 'content',
|
|
9098
|
+
refId: mixedRefId,
|
|
9099
|
+
deps: mixedDeps,
|
|
9100
|
+
template: taco.c[i]
|
|
9101
|
+
});
|
|
9102
|
+
// Replace string with a span wrapper so textContent targets the span only
|
|
9103
|
+
taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
|
|
9104
|
+
}
|
|
9105
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
8801
9106
|
walkTaco(taco.c[i], path.concat(i));
|
|
8802
9107
|
}
|
|
8803
9108
|
// Handle bw.when/bw.each markers
|
|
@@ -8832,7 +9137,7 @@
|
|
|
8832
9137
|
taco.c[i]._refId = eachRefId;
|
|
8833
9138
|
}
|
|
8834
9139
|
}
|
|
8835
|
-
} else if (taco.c
|
|
9140
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
8836
9141
|
walkTaco(taco.c, path.concat(0));
|
|
8837
9142
|
}
|
|
8838
9143
|
|
|
@@ -8848,7 +9153,7 @@
|
|
|
8848
9153
|
* Build ref map from the live DOM after createDOM.
|
|
8849
9154
|
* @private
|
|
8850
9155
|
*/
|
|
8851
|
-
|
|
9156
|
+
_chp._collectRefs = function() {
|
|
8852
9157
|
this._bw_refs = {};
|
|
8853
9158
|
if (!this.element) return;
|
|
8854
9159
|
var els = this.element.querySelectorAll('[data-bw_ref]');
|
|
@@ -8869,7 +9174,7 @@
|
|
|
8869
9174
|
* Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
|
|
8870
9175
|
* @param {Element} parentEl - DOM element to mount into
|
|
8871
9176
|
*/
|
|
8872
|
-
|
|
9177
|
+
_chp.mount = function(parentEl) {
|
|
8873
9178
|
// willMount hook
|
|
8874
9179
|
if (this._hooks.willMount) this._hooks.willMount(this);
|
|
8875
9180
|
|
|
@@ -8891,7 +9196,7 @@
|
|
|
8891
9196
|
// Register named actions in function registry
|
|
8892
9197
|
var self = this;
|
|
8893
9198
|
for (var actionName in this._actions) {
|
|
8894
|
-
if (
|
|
9199
|
+
if (_hop.call(this._actions, actionName)) {
|
|
8895
9200
|
var registeredName = this._bwId + '_' + actionName;
|
|
8896
9201
|
(function(aName) {
|
|
8897
9202
|
bw.funcRegister(function(evt) {
|
|
@@ -8910,6 +9215,11 @@
|
|
|
8910
9215
|
this.element = bw.createDOM(tacoForDOM);
|
|
8911
9216
|
this.element._bwComponentHandle = this;
|
|
8912
9217
|
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
9218
|
+
|
|
9219
|
+
// Restore o.render from original TACO (stripped by _tacoForDOM)
|
|
9220
|
+
if (this.taco.o && this.taco.o.render) {
|
|
9221
|
+
this.element._bw_render = this.taco.o.render;
|
|
9222
|
+
}
|
|
8913
9223
|
if (this._userTag) {
|
|
8914
9224
|
this.element.classList.add(this._userTag);
|
|
8915
9225
|
}
|
|
@@ -8925,6 +9235,16 @@
|
|
|
8925
9235
|
|
|
8926
9236
|
this.mounted = true;
|
|
8927
9237
|
|
|
9238
|
+
// Scan for child ComponentHandles and link parent/child (Bug #5)
|
|
9239
|
+
var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
|
|
9240
|
+
for (var ci = 0; ci < childEls.length; ci++) {
|
|
9241
|
+
var ch = childEls[ci]._bwComponentHandle;
|
|
9242
|
+
if (ch && ch !== this && !ch._parent) {
|
|
9243
|
+
ch._parent = this;
|
|
9244
|
+
this._children.push(ch);
|
|
9245
|
+
}
|
|
9246
|
+
}
|
|
9247
|
+
|
|
8928
9248
|
// mounted hook (backward compat: fn.length === 2 wraps (el, state))
|
|
8929
9249
|
if (this._hooks.mounted) {
|
|
8930
9250
|
if (this._hooks.mounted.length === 2) {
|
|
@@ -8933,16 +9253,21 @@
|
|
|
8933
9253
|
this._hooks.mounted(this);
|
|
8934
9254
|
}
|
|
8935
9255
|
}
|
|
9256
|
+
|
|
9257
|
+
// Invoke o.render on initial mount (if present)
|
|
9258
|
+
if (this.element._bw_render) {
|
|
9259
|
+
this.element._bw_render(this.element, this._state);
|
|
9260
|
+
}
|
|
8936
9261
|
};
|
|
8937
9262
|
|
|
8938
9263
|
/**
|
|
8939
9264
|
* Prepare TACO for initial render: resolve when/each markers.
|
|
8940
9265
|
* @private
|
|
8941
9266
|
*/
|
|
8942
|
-
|
|
8943
|
-
if (!taco
|
|
9267
|
+
_chp._prepareTaco = function(taco) {
|
|
9268
|
+
if (!_is(taco, 'object')) return;
|
|
8944
9269
|
|
|
8945
|
-
if (
|
|
9270
|
+
if (_isA(taco.c)) {
|
|
8946
9271
|
for (var i = taco.c.length - 1; i >= 0; i--) {
|
|
8947
9272
|
var child = taco.c[i];
|
|
8948
9273
|
if (child && child._bwWhen) {
|
|
@@ -8967,18 +9292,18 @@
|
|
|
8967
9292
|
var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
8968
9293
|
var arr = bw._evaluatePath(this._state, eachExprStr);
|
|
8969
9294
|
var items = [];
|
|
8970
|
-
if (
|
|
9295
|
+
if (_isA(arr)) {
|
|
8971
9296
|
for (var j = 0; j < arr.length; j++) {
|
|
8972
9297
|
items.push(child.factory(arr[j], j));
|
|
8973
9298
|
}
|
|
8974
9299
|
}
|
|
8975
9300
|
taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
|
|
8976
9301
|
}
|
|
8977
|
-
if (taco.c[i]
|
|
9302
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
8978
9303
|
this._prepareTaco(taco.c[i]);
|
|
8979
9304
|
}
|
|
8980
9305
|
}
|
|
8981
|
-
} else if (taco.c
|
|
9306
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
8982
9307
|
this._prepareTaco(taco.c);
|
|
8983
9308
|
}
|
|
8984
9309
|
};
|
|
@@ -8987,12 +9312,12 @@
|
|
|
8987
9312
|
* Wire action name strings (in onclick etc.) to dispatch function calls.
|
|
8988
9313
|
* @private
|
|
8989
9314
|
*/
|
|
8990
|
-
|
|
8991
|
-
if (!taco
|
|
9315
|
+
_chp._wireActions = function(taco) {
|
|
9316
|
+
if (!_is(taco, 'object') || !taco.t) return;
|
|
8992
9317
|
if (taco.a) {
|
|
8993
9318
|
for (var key in taco.a) {
|
|
8994
|
-
if (!
|
|
8995
|
-
if (key.startsWith('on') &&
|
|
9319
|
+
if (!_hop.call(taco.a, key)) continue;
|
|
9320
|
+
if (key.startsWith('on') && _is(taco.a[key], 'string')) {
|
|
8996
9321
|
var actionName = taco.a[key];
|
|
8997
9322
|
if (actionName in this._actions) {
|
|
8998
9323
|
var registeredName = this._bwId + '_' + actionName;
|
|
@@ -9006,11 +9331,11 @@
|
|
|
9006
9331
|
}
|
|
9007
9332
|
}
|
|
9008
9333
|
}
|
|
9009
|
-
if (
|
|
9334
|
+
if (_isA(taco.c)) {
|
|
9010
9335
|
for (var i = 0; i < taco.c.length; i++) {
|
|
9011
9336
|
this._wireActions(taco.c[i]);
|
|
9012
9337
|
}
|
|
9013
|
-
} else if (taco.c
|
|
9338
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
9014
9339
|
this._wireActions(taco.c);
|
|
9015
9340
|
}
|
|
9016
9341
|
};
|
|
@@ -9019,7 +9344,7 @@
|
|
|
9019
9344
|
* Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
|
|
9020
9345
|
* @private
|
|
9021
9346
|
*/
|
|
9022
|
-
|
|
9347
|
+
_chp._deepCloneTaco = function(taco) {
|
|
9023
9348
|
if (taco == null) return taco;
|
|
9024
9349
|
// Preserve _bwWhen / _bwEach markers (contain functions)
|
|
9025
9350
|
if (taco._bwWhen) {
|
|
@@ -9031,18 +9356,18 @@
|
|
|
9031
9356
|
if (taco._bwEach) {
|
|
9032
9357
|
return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
|
|
9033
9358
|
}
|
|
9034
|
-
if (
|
|
9359
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
9035
9360
|
var result = { t: taco.t };
|
|
9036
9361
|
if (taco.a) {
|
|
9037
9362
|
result.a = {};
|
|
9038
9363
|
for (var k in taco.a) {
|
|
9039
|
-
if (
|
|
9364
|
+
if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
|
|
9040
9365
|
}
|
|
9041
9366
|
}
|
|
9042
9367
|
if (taco.c != null) {
|
|
9043
|
-
if (
|
|
9368
|
+
if (_isA(taco.c)) {
|
|
9044
9369
|
result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
|
|
9045
|
-
} else if (
|
|
9370
|
+
} else if (_is(taco.c, 'object')) {
|
|
9046
9371
|
result.c = this._deepCloneTaco(taco.c);
|
|
9047
9372
|
} else {
|
|
9048
9373
|
result.c = taco.c;
|
|
@@ -9056,27 +9381,31 @@
|
|
|
9056
9381
|
* Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
|
|
9057
9382
|
* @private
|
|
9058
9383
|
*/
|
|
9059
|
-
|
|
9060
|
-
if (!taco
|
|
9384
|
+
_chp._tacoForDOM = function(taco) {
|
|
9385
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
9061
9386
|
var result = { t: taco.t };
|
|
9062
9387
|
if (taco.a) result.a = taco.a;
|
|
9063
9388
|
if (taco.c != null) {
|
|
9064
|
-
if (
|
|
9389
|
+
if (_isA(taco.c)) {
|
|
9065
9390
|
result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
|
|
9066
|
-
} else if (
|
|
9391
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
9067
9392
|
result.c = this._tacoForDOM(taco.c);
|
|
9068
9393
|
} else {
|
|
9069
9394
|
result.c = taco.c;
|
|
9070
9395
|
}
|
|
9071
9396
|
}
|
|
9072
9397
|
// Intentionally strip o (no mounted/unmount/state/render on sub-elements)
|
|
9398
|
+
if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
|
|
9399
|
+
_cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
|
|
9400
|
+
'>. Use onclick attribute or bw.component() for child interactivity.');
|
|
9401
|
+
}
|
|
9073
9402
|
return result;
|
|
9074
9403
|
};
|
|
9075
9404
|
|
|
9076
9405
|
/**
|
|
9077
9406
|
* Unmount: remove from DOM, deactivate, preserve state for re-mount.
|
|
9078
9407
|
*/
|
|
9079
|
-
|
|
9408
|
+
_chp.unmount = function() {
|
|
9080
9409
|
if (!this.mounted) return;
|
|
9081
9410
|
|
|
9082
9411
|
// unmount hook
|
|
@@ -9111,12 +9440,23 @@
|
|
|
9111
9440
|
/**
|
|
9112
9441
|
* Destroy: unmount + clear state + unregister actions.
|
|
9113
9442
|
*/
|
|
9114
|
-
|
|
9443
|
+
_chp.destroy = function() {
|
|
9115
9444
|
// willDestroy hook
|
|
9116
9445
|
if (this._hooks.willDestroy) {
|
|
9117
9446
|
this._hooks.willDestroy(this);
|
|
9118
9447
|
}
|
|
9119
9448
|
|
|
9449
|
+
// Cascade destroy to children depth-first (Bug #5)
|
|
9450
|
+
for (var ci = this._children.length - 1; ci >= 0; ci--) {
|
|
9451
|
+
this._children[ci].destroy();
|
|
9452
|
+
}
|
|
9453
|
+
this._children = [];
|
|
9454
|
+
if (this._parent) {
|
|
9455
|
+
var idx = this._parent._children.indexOf(this);
|
|
9456
|
+
if (idx >= 0) this._parent._children.splice(idx, 1);
|
|
9457
|
+
this._parent = null;
|
|
9458
|
+
}
|
|
9459
|
+
|
|
9120
9460
|
this.unmount();
|
|
9121
9461
|
|
|
9122
9462
|
// Unregister actions from function registry
|
|
@@ -9143,12 +9483,36 @@
|
|
|
9143
9483
|
* Flush dirty state: resolve changed bindings and apply to DOM.
|
|
9144
9484
|
* @private
|
|
9145
9485
|
*/
|
|
9146
|
-
|
|
9486
|
+
_chp._flush = function() {
|
|
9147
9487
|
this._scheduled = false;
|
|
9148
|
-
var changedKeys =
|
|
9488
|
+
var changedKeys = _keys(this._dirtyKeys);
|
|
9149
9489
|
this._dirtyKeys = {};
|
|
9150
9490
|
if (changedKeys.length === 0 || !this.mounted) return;
|
|
9151
9491
|
|
|
9492
|
+
// Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
|
|
9493
|
+
// rebuild the TACO from the factory with merged state (Bug #6)
|
|
9494
|
+
if (this._factory) {
|
|
9495
|
+
var rebuildNeeded = false;
|
|
9496
|
+
for (var fi = 0; fi < changedKeys.length; fi++) {
|
|
9497
|
+
if (_hop.call(this._factory.props, changedKeys[fi])) {
|
|
9498
|
+
rebuildNeeded = true; break;
|
|
9499
|
+
}
|
|
9500
|
+
}
|
|
9501
|
+
if (rebuildNeeded) {
|
|
9502
|
+
var merged = {};
|
|
9503
|
+
for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
|
|
9504
|
+
for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
|
|
9505
|
+
this._factory.props = merged;
|
|
9506
|
+
var newTaco = bw.make(this._factory.type, merged);
|
|
9507
|
+
newTaco._bwFactory = this._factory;
|
|
9508
|
+
this.taco = newTaco;
|
|
9509
|
+
this._originalTaco = this._deepCloneTaco(newTaco);
|
|
9510
|
+
this._render();
|
|
9511
|
+
if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
|
|
9512
|
+
return;
|
|
9513
|
+
}
|
|
9514
|
+
}
|
|
9515
|
+
|
|
9152
9516
|
// willUpdate hook
|
|
9153
9517
|
if (this._hooks.willUpdate) {
|
|
9154
9518
|
this._hooks.willUpdate(this, changedKeys);
|
|
@@ -9187,7 +9551,7 @@
|
|
|
9187
9551
|
* Returns list of patches to apply.
|
|
9188
9552
|
* @private
|
|
9189
9553
|
*/
|
|
9190
|
-
|
|
9554
|
+
_chp._resolveBindings = function(changedKeys) {
|
|
9191
9555
|
var patches = [];
|
|
9192
9556
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
9193
9557
|
var b = this._bindings[i];
|
|
@@ -9223,11 +9587,14 @@
|
|
|
9223
9587
|
* Apply patches to DOM.
|
|
9224
9588
|
* @private
|
|
9225
9589
|
*/
|
|
9226
|
-
|
|
9590
|
+
_chp._applyPatches = function(patches) {
|
|
9227
9591
|
for (var i = 0; i < patches.length; i++) {
|
|
9228
9592
|
var p = patches[i];
|
|
9229
9593
|
var el = this._bw_refs[p.refId];
|
|
9230
|
-
if (!el)
|
|
9594
|
+
if (!el) {
|
|
9595
|
+
if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
|
|
9596
|
+
continue;
|
|
9597
|
+
}
|
|
9231
9598
|
if (p.type === 'content') {
|
|
9232
9599
|
el.textContent = p.value;
|
|
9233
9600
|
} else if (p.type === 'attribute') {
|
|
@@ -9244,7 +9611,7 @@
|
|
|
9244
9611
|
* Resolve all bindings and apply (used for initial render).
|
|
9245
9612
|
* @private
|
|
9246
9613
|
*/
|
|
9247
|
-
|
|
9614
|
+
_chp._resolveAndApplyAll = function() {
|
|
9248
9615
|
var patches = [];
|
|
9249
9616
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
9250
9617
|
var b = this._bindings[i];
|
|
@@ -9267,7 +9634,7 @@
|
|
|
9267
9634
|
* Full re-render for structural changes (when/each branch switches).
|
|
9268
9635
|
* @private
|
|
9269
9636
|
*/
|
|
9270
|
-
|
|
9637
|
+
_chp._render = function() {
|
|
9271
9638
|
if (!this.element || !this.element.parentNode) return;
|
|
9272
9639
|
var parent = this.element.parentNode;
|
|
9273
9640
|
var nextSibling = this.element.nextSibling;
|
|
@@ -9307,7 +9674,7 @@
|
|
|
9307
9674
|
* @param {string} event - Event name (e.g., 'click')
|
|
9308
9675
|
* @param {Function} handler - Event handler
|
|
9309
9676
|
*/
|
|
9310
|
-
|
|
9677
|
+
_chp.on = function(event, handler) {
|
|
9311
9678
|
if (this.element) {
|
|
9312
9679
|
this.element.addEventListener(event, handler);
|
|
9313
9680
|
}
|
|
@@ -9319,7 +9686,7 @@
|
|
|
9319
9686
|
* @param {string} event - Event name
|
|
9320
9687
|
* @param {Function} handler - Handler to remove
|
|
9321
9688
|
*/
|
|
9322
|
-
|
|
9689
|
+
_chp.off = function(event, handler) {
|
|
9323
9690
|
if (this.element) {
|
|
9324
9691
|
this.element.removeEventListener(event, handler);
|
|
9325
9692
|
}
|
|
@@ -9334,7 +9701,7 @@
|
|
|
9334
9701
|
* @param {Function} handler - Handler function
|
|
9335
9702
|
* @returns {Function} Unsubscribe function
|
|
9336
9703
|
*/
|
|
9337
|
-
|
|
9704
|
+
_chp.sub = function(topic, handler) {
|
|
9338
9705
|
var unsub = bw.sub(topic, handler);
|
|
9339
9706
|
this._subs.push(unsub);
|
|
9340
9707
|
return unsub;
|
|
@@ -9345,10 +9712,10 @@
|
|
|
9345
9712
|
* @param {string} name - Action name
|
|
9346
9713
|
* @param {...*} args - Arguments passed after comp
|
|
9347
9714
|
*/
|
|
9348
|
-
|
|
9715
|
+
_chp.action = function(name) {
|
|
9349
9716
|
var fn = this._actions[name];
|
|
9350
9717
|
if (!fn) {
|
|
9351
|
-
|
|
9718
|
+
_cw('ComponentHandle.action: unknown action "' + name + '"');
|
|
9352
9719
|
return;
|
|
9353
9720
|
}
|
|
9354
9721
|
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
|
|
@@ -9360,7 +9727,7 @@
|
|
|
9360
9727
|
* @param {string} sel - CSS selector
|
|
9361
9728
|
* @returns {Element|null}
|
|
9362
9729
|
*/
|
|
9363
|
-
|
|
9730
|
+
_chp.select = function(sel) {
|
|
9364
9731
|
return this.element ? this.element.querySelector(sel) : null;
|
|
9365
9732
|
};
|
|
9366
9733
|
|
|
@@ -9369,7 +9736,7 @@
|
|
|
9369
9736
|
* @param {string} sel - CSS selector
|
|
9370
9737
|
* @returns {Element[]}
|
|
9371
9738
|
*/
|
|
9372
|
-
|
|
9739
|
+
_chp.selectAll = function(sel) {
|
|
9373
9740
|
if (!this.element) return [];
|
|
9374
9741
|
return Array.prototype.slice.call(this.element.querySelectorAll(sel));
|
|
9375
9742
|
};
|
|
@@ -9380,7 +9747,7 @@
|
|
|
9380
9747
|
* @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
|
|
9381
9748
|
* @returns {ComponentHandle} this (for chaining)
|
|
9382
9749
|
*/
|
|
9383
|
-
|
|
9750
|
+
_chp.userTag = function(tag) {
|
|
9384
9751
|
this._userTag = tag;
|
|
9385
9752
|
if (this.element) {
|
|
9386
9753
|
this.element.classList.add(tag);
|
|
@@ -9481,14 +9848,399 @@
|
|
|
9481
9848
|
}
|
|
9482
9849
|
if (!el || !el._bwComponentHandle) return false;
|
|
9483
9850
|
var comp = el._bwComponentHandle;
|
|
9484
|
-
if (
|
|
9485
|
-
|
|
9851
|
+
if (!_is(comp[action], 'function')) {
|
|
9852
|
+
_cw('bw.message: unknown action "' + action + '" on component ' + target);
|
|
9486
9853
|
return false;
|
|
9487
9854
|
}
|
|
9488
9855
|
comp[action](data);
|
|
9489
9856
|
return true;
|
|
9490
9857
|
};
|
|
9491
9858
|
|
|
9859
|
+
// ===================================================================================
|
|
9860
|
+
// bw.clientApply() / bw.clientConnect() — Server-driven UI protocol
|
|
9861
|
+
// ===================================================================================
|
|
9862
|
+
|
|
9863
|
+
/**
|
|
9864
|
+
* Registry of named functions sent via register messages.
|
|
9865
|
+
* Populated by clientApply({ type: 'register', name, body }).
|
|
9866
|
+
* Invoked by clientApply({ type: 'call', name, args }).
|
|
9867
|
+
* @private
|
|
9868
|
+
*/
|
|
9869
|
+
bw._clientFunctions = {};
|
|
9870
|
+
|
|
9871
|
+
/**
|
|
9872
|
+
* Whether exec messages are allowed. Set by clientConnect opts.allowExec.
|
|
9873
|
+
* Default false — exec messages are rejected unless explicitly opted in.
|
|
9874
|
+
* @private
|
|
9875
|
+
*/
|
|
9876
|
+
bw._allowExec = false;
|
|
9877
|
+
|
|
9878
|
+
/**
|
|
9879
|
+
* Built-in client functions available via call() without registration.
|
|
9880
|
+
* @private
|
|
9881
|
+
*/
|
|
9882
|
+
bw._builtinClientFunctions = {
|
|
9883
|
+
scrollTo: function(selector) {
|
|
9884
|
+
var el = bw._el(selector);
|
|
9885
|
+
if (el) el.scrollTop = el.scrollHeight;
|
|
9886
|
+
},
|
|
9887
|
+
focus: function(selector) {
|
|
9888
|
+
var el = bw._el(selector);
|
|
9889
|
+
if (el && _is(el.focus, 'function')) el.focus();
|
|
9890
|
+
},
|
|
9891
|
+
download: function(filename, content, mimeType) {
|
|
9892
|
+
if (typeof document === 'undefined') return;
|
|
9893
|
+
var blob = new Blob([content], { type: mimeType || 'text/plain' });
|
|
9894
|
+
var a = document.createElement('a');
|
|
9895
|
+
a.href = URL.createObjectURL(blob);
|
|
9896
|
+
a.download = filename;
|
|
9897
|
+
a.click();
|
|
9898
|
+
URL.revokeObjectURL(a.href);
|
|
9899
|
+
},
|
|
9900
|
+
clipboard: function(text) {
|
|
9901
|
+
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
9902
|
+
navigator.clipboard.writeText(text);
|
|
9903
|
+
}
|
|
9904
|
+
},
|
|
9905
|
+
redirect: function(url) {
|
|
9906
|
+
if (typeof window !== 'undefined') window.location.href = url;
|
|
9907
|
+
},
|
|
9908
|
+
log: function() {
|
|
9909
|
+
console.log.apply(console, arguments);
|
|
9910
|
+
}
|
|
9911
|
+
};
|
|
9912
|
+
|
|
9913
|
+
/**
|
|
9914
|
+
* Parse a bwserve protocol message string, supporting both strict JSON
|
|
9915
|
+
* and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
|
|
9916
|
+
*
|
|
9917
|
+
* The r-prefix format is designed for C/C++ string literals where
|
|
9918
|
+
* double-quote escaping is painful. The parser is a state machine
|
|
9919
|
+
* that walks character by character — not a regex replace.
|
|
9920
|
+
*
|
|
9921
|
+
* Escaping: apostrophes inside single-quoted values must be escaped
|
|
9922
|
+
* with backslash: r{'name':'Barry\'s room'}
|
|
9923
|
+
*
|
|
9924
|
+
* @param {string} str - JSON or r-prefixed relaxed JSON string
|
|
9925
|
+
* @returns {Object} Parsed message object
|
|
9926
|
+
* @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
|
|
9927
|
+
* @category Server
|
|
9928
|
+
*/
|
|
9929
|
+
bw.clientParse = function(str) {
|
|
9930
|
+
str = (str || '').trim();
|
|
9931
|
+
if (str.charAt(0) !== 'r') return JSON.parse(str);
|
|
9932
|
+
str = str.slice(1);
|
|
9933
|
+
|
|
9934
|
+
var out = [];
|
|
9935
|
+
var i = 0;
|
|
9936
|
+
var len = str.length;
|
|
9937
|
+
|
|
9938
|
+
while (i < len) {
|
|
9939
|
+
var ch = str[i];
|
|
9940
|
+
|
|
9941
|
+
if (ch === "'") {
|
|
9942
|
+
// Single-quoted string → emit as double-quoted
|
|
9943
|
+
out.push('"');
|
|
9944
|
+
i++;
|
|
9945
|
+
while (i < len) {
|
|
9946
|
+
var c = str[i];
|
|
9947
|
+
if (c === '\\' && i + 1 < len) {
|
|
9948
|
+
var next = str[i + 1];
|
|
9949
|
+
if (next === "'") {
|
|
9950
|
+
out.push("'"); // \' in input → ' in output
|
|
9951
|
+
} else {
|
|
9952
|
+
out.push('\\');
|
|
9953
|
+
out.push(next);
|
|
9954
|
+
}
|
|
9955
|
+
i += 2;
|
|
9956
|
+
} else if (c === '"') {
|
|
9957
|
+
out.push('\\"');
|
|
9958
|
+
i++;
|
|
9959
|
+
} else if (c === "'") {
|
|
9960
|
+
break;
|
|
9961
|
+
} else {
|
|
9962
|
+
out.push(c);
|
|
9963
|
+
i++;
|
|
9964
|
+
}
|
|
9965
|
+
}
|
|
9966
|
+
out.push('"');
|
|
9967
|
+
i++; // skip closing '
|
|
9968
|
+
|
|
9969
|
+
} else if (ch === '"') {
|
|
9970
|
+
// Double-quoted string — pass through verbatim
|
|
9971
|
+
out.push(ch);
|
|
9972
|
+
i++;
|
|
9973
|
+
while (i < len) {
|
|
9974
|
+
var c2 = str[i];
|
|
9975
|
+
if (c2 === '\\' && i + 1 < len) {
|
|
9976
|
+
out.push(c2);
|
|
9977
|
+
out.push(str[i + 1]);
|
|
9978
|
+
i += 2;
|
|
9979
|
+
} else {
|
|
9980
|
+
out.push(c2);
|
|
9981
|
+
i++;
|
|
9982
|
+
if (c2 === '"') break;
|
|
9983
|
+
}
|
|
9984
|
+
}
|
|
9985
|
+
|
|
9986
|
+
} else if (ch === ',') {
|
|
9987
|
+
// Trailing comma check: skip comma if next non-whitespace is } or ]
|
|
9988
|
+
var j = i + 1;
|
|
9989
|
+
while (j < len && (str[j] === ' ' || str[j] === '\t' || str[j] === '\n' || str[j] === '\r')) j++;
|
|
9990
|
+
if (j < len && (str[j] === '}' || str[j] === ']')) {
|
|
9991
|
+
i++; // skip trailing comma
|
|
9992
|
+
} else {
|
|
9993
|
+
out.push(ch);
|
|
9994
|
+
i++;
|
|
9995
|
+
}
|
|
9996
|
+
|
|
9997
|
+
} else {
|
|
9998
|
+
out.push(ch);
|
|
9999
|
+
i++;
|
|
10000
|
+
}
|
|
10001
|
+
}
|
|
10002
|
+
|
|
10003
|
+
return JSON.parse(out.join(''));
|
|
10004
|
+
};
|
|
10005
|
+
|
|
10006
|
+
/**
|
|
10007
|
+
* Apply a bwserve protocol message to the DOM.
|
|
10008
|
+
*
|
|
10009
|
+
* Dispatches one of 9 message types:
|
|
10010
|
+
* replace — bw.DOM(target, node)
|
|
10011
|
+
* append — target.appendChild(bw.createDOM(node))
|
|
10012
|
+
* remove — bw.cleanup(target); target.remove()
|
|
10013
|
+
* patch — bw.patch(target, content, attr)
|
|
10014
|
+
* batch — iterate ops, call clientApply for each
|
|
10015
|
+
* message — bw.message(target, action, data)
|
|
10016
|
+
* register — store a named function for later call()
|
|
10017
|
+
* call — invoke a registered or built-in function
|
|
10018
|
+
* exec — execute arbitrary JS (requires allowExec)
|
|
10019
|
+
*
|
|
10020
|
+
* Target resolution:
|
|
10021
|
+
* Starts with '#' or '.' → CSS selector (querySelector)
|
|
10022
|
+
* Otherwise → getElementById, then bw._el fallback
|
|
10023
|
+
*
|
|
10024
|
+
* @param {Object} msg - Protocol message
|
|
10025
|
+
* @returns {boolean} true if the message was applied successfully
|
|
10026
|
+
* @category Server
|
|
10027
|
+
*/
|
|
10028
|
+
bw.clientApply = function(msg) {
|
|
10029
|
+
if (!msg || !msg.type) return false;
|
|
10030
|
+
|
|
10031
|
+
var type = msg.type;
|
|
10032
|
+
var target = msg.target;
|
|
10033
|
+
|
|
10034
|
+
if (type === 'replace') {
|
|
10035
|
+
var el = bw._el(target);
|
|
10036
|
+
if (!el) return false;
|
|
10037
|
+
bw.DOM(el, msg.node);
|
|
10038
|
+
return true;
|
|
10039
|
+
|
|
10040
|
+
} else if (type === 'patch') {
|
|
10041
|
+
var patched = bw.patch(target, msg.content, msg.attr);
|
|
10042
|
+
return patched !== null;
|
|
10043
|
+
|
|
10044
|
+
} else if (type === 'append') {
|
|
10045
|
+
var parent = bw._el(target);
|
|
10046
|
+
if (!parent) return false;
|
|
10047
|
+
var child = bw.createDOM(msg.node);
|
|
10048
|
+
parent.appendChild(child);
|
|
10049
|
+
return true;
|
|
10050
|
+
|
|
10051
|
+
} else if (type === 'remove') {
|
|
10052
|
+
var toRemove = bw._el(target);
|
|
10053
|
+
if (!toRemove) return false;
|
|
10054
|
+
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
10055
|
+
toRemove.remove();
|
|
10056
|
+
return true;
|
|
10057
|
+
|
|
10058
|
+
} else if (type === 'batch') {
|
|
10059
|
+
if (!_isA(msg.ops)) return false;
|
|
10060
|
+
var allOk = true;
|
|
10061
|
+
msg.ops.forEach(function(op) {
|
|
10062
|
+
if (!bw.clientApply(op)) allOk = false;
|
|
10063
|
+
});
|
|
10064
|
+
return allOk;
|
|
10065
|
+
|
|
10066
|
+
} else if (type === 'message') {
|
|
10067
|
+
return bw.message(msg.target, msg.action, msg.data);
|
|
10068
|
+
|
|
10069
|
+
} else if (type === 'register') {
|
|
10070
|
+
if (!msg.name || !msg.body) return false;
|
|
10071
|
+
try {
|
|
10072
|
+
bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
|
|
10073
|
+
return true;
|
|
10074
|
+
} catch (e) {
|
|
10075
|
+
_ce('[bw] register error:', msg.name, e);
|
|
10076
|
+
return false;
|
|
10077
|
+
}
|
|
10078
|
+
|
|
10079
|
+
} else if (type === 'call') {
|
|
10080
|
+
if (!msg.name) return false;
|
|
10081
|
+
var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
|
|
10082
|
+
if (!_is(fn, 'function')) return false;
|
|
10083
|
+
try {
|
|
10084
|
+
var args = _isA(msg.args) ? msg.args : [];
|
|
10085
|
+
fn.apply(null, args);
|
|
10086
|
+
return true;
|
|
10087
|
+
} catch (e) {
|
|
10088
|
+
_ce('[bw] call error:', msg.name, e);
|
|
10089
|
+
return false;
|
|
10090
|
+
}
|
|
10091
|
+
|
|
10092
|
+
} else if (type === 'exec') {
|
|
10093
|
+
if (!bw._allowExec) {
|
|
10094
|
+
_cw('[bw] exec rejected: allowExec is not enabled');
|
|
10095
|
+
return false;
|
|
10096
|
+
}
|
|
10097
|
+
if (!msg.code) return false;
|
|
10098
|
+
try {
|
|
10099
|
+
new Function(msg.code)();
|
|
10100
|
+
return true;
|
|
10101
|
+
} catch (e) {
|
|
10102
|
+
_ce('[bw] exec error:', e);
|
|
10103
|
+
return false;
|
|
10104
|
+
}
|
|
10105
|
+
}
|
|
10106
|
+
|
|
10107
|
+
return false;
|
|
10108
|
+
};
|
|
10109
|
+
|
|
10110
|
+
/**
|
|
10111
|
+
* Connect to a bwserve SSE endpoint and apply protocol messages automatically.
|
|
10112
|
+
*
|
|
10113
|
+
* Returns a connection object with sendAction(), on(), and close() methods.
|
|
10114
|
+
*
|
|
10115
|
+
* @param {string} url - SSE endpoint URL (e.g., '/__bw/events/client-1')
|
|
10116
|
+
* @param {Object} [opts] - Connection options
|
|
10117
|
+
* @param {string} [opts.transport='sse'] - Transport type: 'sse' (default) or 'poll'
|
|
10118
|
+
* @param {number} [opts.interval=2000] - Poll interval in ms (only for 'poll' transport)
|
|
10119
|
+
* @param {string} [opts.actionUrl] - POST endpoint for actions (default: derived from url)
|
|
10120
|
+
* @param {boolean} [opts.reconnect=true] - Auto-reconnect on disconnect
|
|
10121
|
+
* @param {boolean} [opts.allowExec=false] - Enable exec message type (arbitrary JS execution)
|
|
10122
|
+
* @param {Function} [opts.onStatus] - Status callback: 'connecting'|'connected'|'disconnected'
|
|
10123
|
+
* @param {Function} [opts.onMessage] - Raw message callback (before clientApply)
|
|
10124
|
+
* @returns {Object} Connection object { sendAction, on, close, status }
|
|
10125
|
+
* @category Server
|
|
10126
|
+
*/
|
|
10127
|
+
bw.clientConnect = function(url, opts) {
|
|
10128
|
+
opts = opts || {};
|
|
10129
|
+
var transport = opts.transport || 'sse';
|
|
10130
|
+
var actionUrl = opts.actionUrl || url.replace(/\/events\//, '/action/');
|
|
10131
|
+
var reconnect = opts.reconnect !== false;
|
|
10132
|
+
var onStatus = opts.onStatus || function() {};
|
|
10133
|
+
var onMessage = opts.onMessage || null;
|
|
10134
|
+
var handlers = {};
|
|
10135
|
+
// Set the global allowExec flag from connection options
|
|
10136
|
+
bw._allowExec = !!opts.allowExec;
|
|
10137
|
+
var conn = {
|
|
10138
|
+
status: 'connecting',
|
|
10139
|
+
_es: null,
|
|
10140
|
+
_pollTimer: null
|
|
10141
|
+
};
|
|
10142
|
+
|
|
10143
|
+
function setStatus(s) {
|
|
10144
|
+
conn.status = s;
|
|
10145
|
+
onStatus(s);
|
|
10146
|
+
}
|
|
10147
|
+
|
|
10148
|
+
function handleMessage(data) {
|
|
10149
|
+
try {
|
|
10150
|
+
var msg = _is(data, 'string') ? bw.clientParse(data) : data;
|
|
10151
|
+
if (onMessage) onMessage(msg);
|
|
10152
|
+
if (handlers.message) handlers.message(msg);
|
|
10153
|
+
bw.clientApply(msg);
|
|
10154
|
+
} catch (e) {
|
|
10155
|
+
if (handlers.error) handlers.error(e);
|
|
10156
|
+
}
|
|
10157
|
+
}
|
|
10158
|
+
|
|
10159
|
+
if (transport === 'sse' && typeof EventSource !== 'undefined') {
|
|
10160
|
+
setStatus('connecting');
|
|
10161
|
+
var es = new EventSource(url);
|
|
10162
|
+
conn._es = es;
|
|
10163
|
+
|
|
10164
|
+
es.onopen = function() {
|
|
10165
|
+
setStatus('connected');
|
|
10166
|
+
if (handlers.open) handlers.open();
|
|
10167
|
+
};
|
|
10168
|
+
|
|
10169
|
+
es.onmessage = function(e) {
|
|
10170
|
+
handleMessage(e.data);
|
|
10171
|
+
};
|
|
10172
|
+
|
|
10173
|
+
es.onerror = function() {
|
|
10174
|
+
if (conn.status === 'connected') {
|
|
10175
|
+
setStatus('disconnected');
|
|
10176
|
+
}
|
|
10177
|
+
if (handlers.error) handlers.error(new Error('SSE connection error'));
|
|
10178
|
+
if (!reconnect) {
|
|
10179
|
+
es.close();
|
|
10180
|
+
}
|
|
10181
|
+
// EventSource auto-reconnects by default when reconnect=true
|
|
10182
|
+
};
|
|
10183
|
+
} else if (transport === 'poll') {
|
|
10184
|
+
var interval = opts.interval || 2000;
|
|
10185
|
+
setStatus('connected');
|
|
10186
|
+
conn._pollTimer = setInterval(function() {
|
|
10187
|
+
fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
|
|
10188
|
+
if (_isA(msgs)) {
|
|
10189
|
+
msgs.forEach(handleMessage);
|
|
10190
|
+
} else if (msgs && msgs.type) {
|
|
10191
|
+
handleMessage(msgs);
|
|
10192
|
+
}
|
|
10193
|
+
}).catch(function(e) {
|
|
10194
|
+
if (handlers.error) handlers.error(e);
|
|
10195
|
+
});
|
|
10196
|
+
}, interval);
|
|
10197
|
+
}
|
|
10198
|
+
|
|
10199
|
+
/**
|
|
10200
|
+
* Send an action to the server via POST.
|
|
10201
|
+
* @param {string} action - Action name
|
|
10202
|
+
* @param {Object} [data] - Action payload
|
|
10203
|
+
*/
|
|
10204
|
+
conn.sendAction = function(action, data) {
|
|
10205
|
+
var body = JSON.stringify({ type: 'action', action: action, data: data || {} });
|
|
10206
|
+
fetch(actionUrl, {
|
|
10207
|
+
method: 'POST',
|
|
10208
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10209
|
+
body: body
|
|
10210
|
+
}).catch(function(e) {
|
|
10211
|
+
if (handlers.error) handlers.error(e);
|
|
10212
|
+
});
|
|
10213
|
+
};
|
|
10214
|
+
|
|
10215
|
+
/**
|
|
10216
|
+
* Register an event handler.
|
|
10217
|
+
* @param {string} event - 'open'|'message'|'error'|'close'
|
|
10218
|
+
* @param {Function} handler
|
|
10219
|
+
*/
|
|
10220
|
+
conn.on = function(event, handler) {
|
|
10221
|
+
handlers[event] = handler;
|
|
10222
|
+
return conn;
|
|
10223
|
+
};
|
|
10224
|
+
|
|
10225
|
+
/**
|
|
10226
|
+
* Close the connection.
|
|
10227
|
+
*/
|
|
10228
|
+
conn.close = function() {
|
|
10229
|
+
if (conn._es) {
|
|
10230
|
+
conn._es.close();
|
|
10231
|
+
conn._es = null;
|
|
10232
|
+
}
|
|
10233
|
+
if (conn._pollTimer) {
|
|
10234
|
+
clearInterval(conn._pollTimer);
|
|
10235
|
+
conn._pollTimer = null;
|
|
10236
|
+
}
|
|
10237
|
+
setStatus('disconnected');
|
|
10238
|
+
if (handlers.close) handlers.close();
|
|
10239
|
+
};
|
|
10240
|
+
|
|
10241
|
+
return conn;
|
|
10242
|
+
};
|
|
10243
|
+
|
|
9492
10244
|
// ===================================================================================
|
|
9493
10245
|
// bw.inspect() — Debug utility
|
|
9494
10246
|
// ===================================================================================
|
|
@@ -9515,33 +10267,33 @@
|
|
|
9515
10267
|
el = target.element;
|
|
9516
10268
|
comp = target;
|
|
9517
10269
|
} else {
|
|
9518
|
-
if (
|
|
10270
|
+
if (_is(target, 'string')) {
|
|
9519
10271
|
el = bw.$(target)[0];
|
|
9520
10272
|
}
|
|
9521
10273
|
if (!el) {
|
|
9522
|
-
|
|
10274
|
+
_cw('bw.inspect: element not found');
|
|
9523
10275
|
return null;
|
|
9524
10276
|
}
|
|
9525
10277
|
comp = el._bwComponentHandle;
|
|
9526
10278
|
}
|
|
9527
10279
|
if (!comp) {
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
10280
|
+
_cl('bw.inspect: no ComponentHandle on this element');
|
|
10281
|
+
_cl(' Tag:', el.tagName);
|
|
10282
|
+
_cl(' Classes:', el.className);
|
|
10283
|
+
_cl(' _bw_state:', el._bw_state || '(none)');
|
|
9532
10284
|
return null;
|
|
9533
10285
|
}
|
|
9534
10286
|
var deps = comp._bindings.reduce(function(s, b) {
|
|
9535
10287
|
return s.concat(b.deps || []);
|
|
9536
10288
|
}, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
|
|
9537
10289
|
console.group('Component: ' + comp._bwId);
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
10290
|
+
_cl('State:', comp._state);
|
|
10291
|
+
_cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
|
|
10292
|
+
_cl('Methods:', _keys(comp._methods));
|
|
10293
|
+
_cl('Actions:', _keys(comp._actions));
|
|
10294
|
+
_cl('User tag:', comp._userTag || '(none)');
|
|
10295
|
+
_cl('Mounted:', comp.mounted);
|
|
10296
|
+
_cl('Element:', comp.element);
|
|
9545
10297
|
console.groupEnd();
|
|
9546
10298
|
return comp;
|
|
9547
10299
|
};
|
|
@@ -9564,8 +10316,8 @@
|
|
|
9564
10316
|
// Pre-extract all binding expressions
|
|
9565
10317
|
var precompiled = [];
|
|
9566
10318
|
function walkExpressions(node) {
|
|
9567
|
-
if (!node
|
|
9568
|
-
if (
|
|
10319
|
+
if (!_is(node, 'object')) return;
|
|
10320
|
+
if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
|
|
9569
10321
|
var parsed = bw._parseBindings(node.c);
|
|
9570
10322
|
for (var i = 0; i < parsed.length; i++) {
|
|
9571
10323
|
try {
|
|
@@ -9580,9 +10332,9 @@
|
|
|
9580
10332
|
}
|
|
9581
10333
|
if (node.a) {
|
|
9582
10334
|
for (var key in node.a) {
|
|
9583
|
-
if (
|
|
10335
|
+
if (_hop.call(node.a, key)) {
|
|
9584
10336
|
var v = node.a[key];
|
|
9585
|
-
if (
|
|
10337
|
+
if (_is(v, 'string') && v.indexOf('${') >= 0) {
|
|
9586
10338
|
var parsed2 = bw._parseBindings(v);
|
|
9587
10339
|
for (var j = 0; j < parsed2.length; j++) {
|
|
9588
10340
|
try {
|
|
@@ -9598,9 +10350,9 @@
|
|
|
9598
10350
|
}
|
|
9599
10351
|
}
|
|
9600
10352
|
}
|
|
9601
|
-
if (
|
|
10353
|
+
if (_isA(node.c)) {
|
|
9602
10354
|
for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
|
|
9603
|
-
} else if (node.c
|
|
10355
|
+
} else if (_is(node.c, 'object') && node.c.t) {
|
|
9604
10356
|
walkExpressions(node.c);
|
|
9605
10357
|
}
|
|
9606
10358
|
}
|
|
@@ -9612,7 +10364,7 @@
|
|
|
9612
10364
|
handle._precompiledBindings = precompiled;
|
|
9613
10365
|
if (initialState) {
|
|
9614
10366
|
for (var k in initialState) {
|
|
9615
|
-
if (
|
|
10367
|
+
if (_hop.call(initialState, k)) {
|
|
9616
10368
|
handle._state[k] = initialState[k];
|
|
9617
10369
|
}
|
|
9618
10370
|
}
|
|
@@ -9643,18 +10395,18 @@
|
|
|
9643
10395
|
bw.css = function(rules, options = {}) {
|
|
9644
10396
|
const { minify = false, pretty = !minify } = options;
|
|
9645
10397
|
|
|
9646
|
-
if (
|
|
10398
|
+
if (_is(rules, 'string')) return rules;
|
|
9647
10399
|
|
|
9648
10400
|
let css = '';
|
|
9649
10401
|
const indent = pretty ? ' ' : '';
|
|
9650
10402
|
const newline = pretty ? '\n' : '';
|
|
9651
10403
|
const space = pretty ? ' ' : '';
|
|
9652
10404
|
|
|
9653
|
-
if (
|
|
10405
|
+
if (_isA(rules)) {
|
|
9654
10406
|
css = rules.map(rule => bw.css(rule, options)).join(newline);
|
|
9655
|
-
} else if (
|
|
10407
|
+
} else if (_is(rules, 'object')) {
|
|
9656
10408
|
Object.entries(rules).forEach(([selector, styles]) => {
|
|
9657
|
-
if (
|
|
10409
|
+
if (_is(styles, 'object')) {
|
|
9658
10410
|
// Handle @media, @keyframes, @supports — recurse into nested block
|
|
9659
10411
|
if (selector.charAt(0) === '@') {
|
|
9660
10412
|
const inner = bw.css(styles, options);
|
|
@@ -9703,7 +10455,7 @@
|
|
|
9703
10455
|
*/
|
|
9704
10456
|
bw.injectCSS = function(css, options = {}) {
|
|
9705
10457
|
if (!bw._isBrowser) {
|
|
9706
|
-
|
|
10458
|
+
_cw('bw.injectCSS requires a DOM environment');
|
|
9707
10459
|
return null;
|
|
9708
10460
|
}
|
|
9709
10461
|
|
|
@@ -9720,7 +10472,7 @@
|
|
|
9720
10472
|
}
|
|
9721
10473
|
|
|
9722
10474
|
// Convert CSS if needed
|
|
9723
|
-
const cssStr =
|
|
10475
|
+
const cssStr = _is(css, 'string') ? css : bw.css(css, options);
|
|
9724
10476
|
|
|
9725
10477
|
// Set or append CSS
|
|
9726
10478
|
if (append && styleEl.textContent) {
|
|
@@ -9750,7 +10502,7 @@
|
|
|
9750
10502
|
var result = {};
|
|
9751
10503
|
for (var i = 0; i < arguments.length; i++) {
|
|
9752
10504
|
var arg = arguments[i];
|
|
9753
|
-
if (arg
|
|
10505
|
+
if (_is(arg, 'object')) Object.assign(result, arg);
|
|
9754
10506
|
}
|
|
9755
10507
|
return result;
|
|
9756
10508
|
};
|
|
@@ -9873,7 +10625,7 @@
|
|
|
9873
10625
|
bw.responsive = function(selector, breakpoints) {
|
|
9874
10626
|
var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
|
|
9875
10627
|
var parts = [];
|
|
9876
|
-
|
|
10628
|
+
_keys(breakpoints).forEach(function(key) {
|
|
9877
10629
|
var rules = {};
|
|
9878
10630
|
if (key === 'base') {
|
|
9879
10631
|
rules[selector] = breakpoints[key];
|
|
@@ -9945,18 +10697,18 @@
|
|
|
9945
10697
|
if (!selector) return [];
|
|
9946
10698
|
|
|
9947
10699
|
// Already an array
|
|
9948
|
-
if (
|
|
10700
|
+
if (_isA(selector)) return selector;
|
|
9949
10701
|
|
|
9950
10702
|
// Single element
|
|
9951
10703
|
if (selector.nodeType) return [selector];
|
|
9952
10704
|
|
|
9953
10705
|
// NodeList or HTMLCollection
|
|
9954
|
-
if (selector.length !== undefined &&
|
|
10706
|
+
if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
9955
10707
|
return Array.from(selector);
|
|
9956
10708
|
}
|
|
9957
10709
|
|
|
9958
10710
|
// CSS selector string
|
|
9959
|
-
if (
|
|
10711
|
+
if (_is(selector, 'string')) {
|
|
9960
10712
|
return Array.from(document.querySelectorAll(selector));
|
|
9961
10713
|
}
|
|
9962
10714
|
|
|
@@ -10460,7 +11212,7 @@
|
|
|
10460
11212
|
|
|
10461
11213
|
// Auto-detect columns if not provided
|
|
10462
11214
|
const cols = columns || (data.length > 0
|
|
10463
|
-
?
|
|
11215
|
+
? _keys(data[0]).map(key => ({ key, label: key }))
|
|
10464
11216
|
: []);
|
|
10465
11217
|
|
|
10466
11218
|
// Current sort state
|
|
@@ -10475,7 +11227,7 @@
|
|
|
10475
11227
|
const bVal = b[currentSortColumn];
|
|
10476
11228
|
|
|
10477
11229
|
// Handle different types
|
|
10478
|
-
if (
|
|
11230
|
+
if (_is(aVal, 'number') && _is(bVal, 'number')) {
|
|
10479
11231
|
return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
10480
11232
|
}
|
|
10481
11233
|
|
|
@@ -10585,7 +11337,7 @@
|
|
|
10585
11337
|
bw.makeTableFromArray = function(config) {
|
|
10586
11338
|
const { data = [], headerRow = true, columns, ...rest } = config;
|
|
10587
11339
|
|
|
10588
|
-
if (!
|
|
11340
|
+
if (!_isA(data) || data.length === 0) {
|
|
10589
11341
|
return bw.makeTable({ data: [], columns: columns || [], ...rest });
|
|
10590
11342
|
}
|
|
10591
11343
|
|
|
@@ -10667,7 +11419,7 @@
|
|
|
10667
11419
|
className = ''
|
|
10668
11420
|
} = config;
|
|
10669
11421
|
|
|
10670
|
-
if (!
|
|
11422
|
+
if (!_isA(data) || data.length === 0) {
|
|
10671
11423
|
return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
|
|
10672
11424
|
}
|
|
10673
11425
|
|
|
@@ -10816,7 +11568,7 @@
|
|
|
10816
11568
|
*/
|
|
10817
11569
|
bw.render = function(element, position, taco) {
|
|
10818
11570
|
// Get target element
|
|
10819
|
-
const targetEl =
|
|
11571
|
+
const targetEl = _is(element, 'string')
|
|
10820
11572
|
? document.querySelector(element)
|
|
10821
11573
|
: element;
|
|
10822
11574
|
|
|
@@ -10966,7 +11718,7 @@
|
|
|
10966
11718
|
setContent(content) {
|
|
10967
11719
|
this._taco.c = content;
|
|
10968
11720
|
if (this.element) {
|
|
10969
|
-
if (
|
|
11721
|
+
if (_is(content, 'string')) {
|
|
10970
11722
|
this.element.textContent = content;
|
|
10971
11723
|
} else {
|
|
10972
11724
|
// Re-render for complex content
|