bitwrench 2.0.16 → 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/dist/bitwrench-bccl.cjs.js +6 -2
- package/dist/bitwrench-bccl.cjs.min.js +3 -3
- package/dist/bitwrench-bccl.esm.js +6 -2
- package/dist/bitwrench-bccl.esm.min.js +3 -3
- package/dist/bitwrench-bccl.umd.js +6 -2
- package/dist/bitwrench-bccl.umd.min.js +2 -2
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.cjs.min.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +506 -154
- package/dist/bitwrench-lean.cjs.min.js +7 -7
- package/dist/bitwrench-lean.es5.js +517 -155
- package/dist/bitwrench-lean.es5.min.js +5 -5
- package/dist/bitwrench-lean.esm.js +505 -154
- package/dist/bitwrench-lean.esm.min.js +6 -6
- package/dist/bitwrench-lean.umd.js +506 -154
- package/dist/bitwrench-lean.umd.min.js +7 -7
- package/dist/bitwrench.cjs.js +511 -155
- package/dist/bitwrench.cjs.min.js +8 -8
- package/dist/bitwrench.es5.js +525 -156
- package/dist/bitwrench.es5.min.js +6 -6
- package/dist/bitwrench.esm.js +510 -155
- package/dist/bitwrench.esm.min.js +8 -8
- package/dist/bitwrench.umd.js +511 -155
- package/dist/bitwrench.umd.min.js +8 -8
- package/dist/builds.json +82 -82
- package/dist/bwserve.cjs.js +16 -2
- package/dist/bwserve.esm.js +16 -2
- package/dist/sri.json +34 -34
- package/package.json +4 -2
- package/readme.html +1 -1
- package/src/bitwrench-bccl.js +5 -1
- package/src/bitwrench.js +502 -151
- package/src/bwserve/index.js +12 -1
- package/src/bwserve/shell.js +3 -0
- package/src/cli/layout-default.js +47 -32
- package/src/version.js +3 -3
package/src/bitwrench.js
CHANGED
|
@@ -80,7 +80,7 @@ const bw = {
|
|
|
80
80
|
__monkey_patch_is_nodejs__: {
|
|
81
81
|
_value: 'ignore',
|
|
82
82
|
set: function(x) {
|
|
83
|
-
this._value = (
|
|
83
|
+
this._value = _is(x, 'boolean') ? x : 'ignore';
|
|
84
84
|
},
|
|
85
85
|
get: function() {
|
|
86
86
|
return this._value;
|
|
@@ -128,6 +128,67 @@ Object.defineProperty(bw, '_isBrowser', {
|
|
|
128
128
|
configurable: true
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
+
// ── Internal aliases ─────────────────────────────────────────────────────
|
|
132
|
+
// Short names for frequently-used builtins and internal methods.
|
|
133
|
+
// Same pattern as v1 (_to = bw.typeOf, etc.).
|
|
134
|
+
//
|
|
135
|
+
// Why: Terser can't shorten global property chains (console.warn,
|
|
136
|
+
// Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
|
|
137
|
+
// because it can't prove they're side-effect-free. We can, so we alias
|
|
138
|
+
// them here. Each alias saves bytes in the minified output, and the short
|
|
139
|
+
// names also reduce visual noise in the hot paths (binding pipeline,
|
|
140
|
+
// createDOM, etc.).
|
|
141
|
+
//
|
|
142
|
+
// Alias Target Sites
|
|
143
|
+
// ───────── ────────────────────────────────────── ─────
|
|
144
|
+
// _hop Object.prototype.hasOwnProperty 15
|
|
145
|
+
// _isA Array.isArray 25
|
|
146
|
+
// _keys Object.keys 7
|
|
147
|
+
// _to bw.typeOf (type string) 26
|
|
148
|
+
// _is type check boolean: _is(x,'string') ~50
|
|
149
|
+
// _cw console.warn 8
|
|
150
|
+
// _cl console.log 11
|
|
151
|
+
// _ce console.error 4
|
|
152
|
+
// _chp ComponentHandle.prototype 28 (defined after constructor)
|
|
153
|
+
//
|
|
154
|
+
// Note: document.createElement etc. are NOT aliased because they require
|
|
155
|
+
// `this === document` and .bind() would add overhead on every call.
|
|
156
|
+
// Console aliases use thin wrappers (not direct refs) so test monkey-
|
|
157
|
+
// patching of console.warn/log/error continues to work.
|
|
158
|
+
//
|
|
159
|
+
// `typeof x` for UNDECLARED globals (window, document, process, require,
|
|
160
|
+
// EventSource, navigator, Promise, __filename, import.meta) MUST stay as
|
|
161
|
+
// raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
|
|
162
|
+
//
|
|
163
|
+
// ── v1 functional type helpers (kept for reference, not currently used) ──
|
|
164
|
+
// _toa(x, type, trueVal, falseVal) — bw.typeAssign:
|
|
165
|
+
// returns trueVal if _to(x)===type, else falseVal.
|
|
166
|
+
// Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
|
|
167
|
+
// _toc(x, type, trueVal, falseVal) — bw.typeConvert:
|
|
168
|
+
// same as _toa but if trueVal/falseVal are functions, calls them with x.
|
|
169
|
+
// Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
|
|
170
|
+
// Uncomment if pattern frequency justifies them:
|
|
171
|
+
// var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
|
|
172
|
+
// 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); };
|
|
173
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
174
|
+
var _hop = Object.prototype.hasOwnProperty;
|
|
175
|
+
var _isA = Array.isArray;
|
|
176
|
+
var _keys = Object.keys;
|
|
177
|
+
var _to = _typeOf; // imported from bitwrench-utils.js
|
|
178
|
+
var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
|
|
179
|
+
// Console aliases use thin wrappers (not direct references) so that test
|
|
180
|
+
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
181
|
+
var _cw = function() { console.warn.apply(console, arguments); };
|
|
182
|
+
var _cl = function() { console.log.apply(console, arguments); };
|
|
183
|
+
var _ce = function() { console.error.apply(console, arguments); };
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Debug flag. When true, emits console.warn for silent binding failures
|
|
187
|
+
* (missing paths, null refs, auto-created intermediate objects).
|
|
188
|
+
* @type {boolean}
|
|
189
|
+
*/
|
|
190
|
+
bw.debug = false;
|
|
191
|
+
|
|
131
192
|
/**
|
|
132
193
|
* Lazy-resolve Node.js `fs` module.
|
|
133
194
|
* Tries require('fs') first (available in CJS/UMD Node.js builds),
|
|
@@ -275,7 +336,7 @@ bw.uuid = function(prefix) {
|
|
|
275
336
|
*/
|
|
276
337
|
bw._el = function(id) {
|
|
277
338
|
// Pass-through for DOM elements
|
|
278
|
-
if (
|
|
339
|
+
if (!_is(id, 'string')) return id || null;
|
|
279
340
|
if (!id) return null;
|
|
280
341
|
if (!bw._isBrowser) return null;
|
|
281
342
|
|
|
@@ -371,7 +432,7 @@ bw._deregisterNode = function(el, bwId) {
|
|
|
371
432
|
* // => '<b>Hello</b> & "world"'
|
|
372
433
|
*/
|
|
373
434
|
bw.escapeHTML = function(str) {
|
|
374
|
-
if (
|
|
435
|
+
if (!_is(str, 'string')) return '';
|
|
375
436
|
|
|
376
437
|
const escapeMap = {
|
|
377
438
|
'&': '&',
|
|
@@ -444,7 +505,7 @@ bw.html = function(taco, options = {}) {
|
|
|
444
505
|
}
|
|
445
506
|
|
|
446
507
|
// Handle arrays of TACOs
|
|
447
|
-
if (
|
|
508
|
+
if (_isA(taco)) {
|
|
448
509
|
return taco.map(t => bw.html(t, options)).join('');
|
|
449
510
|
}
|
|
450
511
|
|
|
@@ -467,15 +528,15 @@ bw.html = function(taco, options = {}) {
|
|
|
467
528
|
if (taco && taco._bwEach && options.state) {
|
|
468
529
|
var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
469
530
|
var arr = bw._evaluatePath(options.state, eachExpr);
|
|
470
|
-
if (!
|
|
531
|
+
if (!_isA(arr)) return '';
|
|
471
532
|
return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
|
|
472
533
|
}
|
|
473
534
|
|
|
474
535
|
// Handle primitives and non-TACO objects
|
|
475
|
-
if (
|
|
536
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
476
537
|
var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
|
|
477
538
|
// Resolve template bindings if state provided
|
|
478
|
-
if (options.state &&
|
|
539
|
+
if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
|
|
479
540
|
str = bw._resolveTemplate(str, options.state, !!options.compile);
|
|
480
541
|
}
|
|
481
542
|
return str;
|
|
@@ -495,10 +556,18 @@ bw.html = function(taco, options = {}) {
|
|
|
495
556
|
// Skip null, undefined, false
|
|
496
557
|
if (value == null || value === false) continue;
|
|
497
558
|
|
|
498
|
-
//
|
|
499
|
-
if (key.startsWith('on'))
|
|
559
|
+
// Serialize event handlers via funcRegister
|
|
560
|
+
if (key.startsWith('on')) {
|
|
561
|
+
if (_is(value, 'function')) {
|
|
562
|
+
var fnId = bw.funcRegister(value);
|
|
563
|
+
attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
|
|
564
|
+
} else if (_is(value, 'string')) {
|
|
565
|
+
attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
|
|
566
|
+
}
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
500
569
|
|
|
501
|
-
if (key === 'style' &&
|
|
570
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
502
571
|
// Convert style object to string
|
|
503
572
|
const styleStr = Object.entries(value)
|
|
504
573
|
.filter(([, v]) => v != null)
|
|
@@ -509,7 +578,7 @@ bw.html = function(taco, options = {}) {
|
|
|
509
578
|
}
|
|
510
579
|
} else if (key === 'class') {
|
|
511
580
|
// Handle class as array or string
|
|
512
|
-
const classStr =
|
|
581
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
513
582
|
if (classStr) {
|
|
514
583
|
attrStr += ` class="${bw.escapeHTML(classStr)}"`;
|
|
515
584
|
}
|
|
@@ -545,13 +614,184 @@ bw.html = function(taco, options = {}) {
|
|
|
545
614
|
// Process content recursively
|
|
546
615
|
let contentStr = content != null ? bw.html(content, options) : '';
|
|
547
616
|
// Resolve template bindings in content if state provided
|
|
548
|
-
if (options.state &&
|
|
617
|
+
if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
|
|
549
618
|
contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
|
|
550
619
|
}
|
|
551
620
|
|
|
552
621
|
return `<${tag}${attrStr}>${contentStr}</${tag}>`;
|
|
553
622
|
};
|
|
554
623
|
|
|
624
|
+
/**
|
|
625
|
+
* Generate a complete, self-contained HTML document from TACO content.
|
|
626
|
+
*
|
|
627
|
+
* Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
|
|
628
|
+
* func registry emission (so serialized event handlers work), optional theme,
|
|
629
|
+
* and extra head elements. Designed for static site generation, offline/airgapped
|
|
630
|
+
* use, and the "static site that isn't static" workflow.
|
|
631
|
+
*
|
|
632
|
+
* @param {Object} [opts={}] - Page options
|
|
633
|
+
* @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
|
|
634
|
+
* @param {string} [opts.title='bitwrench'] - Page title
|
|
635
|
+
* @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
|
|
636
|
+
* @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
|
|
637
|
+
* @param {string} [opts.css=''] - Additional CSS for <style> block
|
|
638
|
+
* @param {string|Object} [opts.theme=null] - Theme preset name or config object
|
|
639
|
+
* @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
|
|
640
|
+
* @param {string} [opts.favicon=''] - Favicon URL
|
|
641
|
+
* @param {string} [opts.lang='en'] - HTML lang attribute
|
|
642
|
+
* @returns {string} Complete HTML document string
|
|
643
|
+
* @category DOM Generation
|
|
644
|
+
* @see bw.html
|
|
645
|
+
* @example
|
|
646
|
+
* bw.htmlPage({
|
|
647
|
+
* title: 'My App',
|
|
648
|
+
* body: { t: 'h1', c: 'Hello World' },
|
|
649
|
+
* runtime: 'shim'
|
|
650
|
+
* })
|
|
651
|
+
*/
|
|
652
|
+
bw.htmlPage = function(opts) {
|
|
653
|
+
opts = opts || {};
|
|
654
|
+
var title = opts.title || 'bitwrench';
|
|
655
|
+
var body = opts.body || '';
|
|
656
|
+
var state = opts.state || undefined;
|
|
657
|
+
var runtime = opts.runtime || 'shim';
|
|
658
|
+
var css = opts.css || '';
|
|
659
|
+
var theme = opts.theme || null;
|
|
660
|
+
var headExtra = opts.head || [];
|
|
661
|
+
var favicon = opts.favicon || '';
|
|
662
|
+
var lang = opts.lang || 'en';
|
|
663
|
+
|
|
664
|
+
// Snapshot funcRegistry counter before rendering
|
|
665
|
+
var fnCounterBefore = bw._fnIDCounter;
|
|
666
|
+
|
|
667
|
+
// Render body content
|
|
668
|
+
var bodyHTML = '';
|
|
669
|
+
if (_is(body, 'string')) {
|
|
670
|
+
bodyHTML = body;
|
|
671
|
+
} else {
|
|
672
|
+
var htmlOpts = {};
|
|
673
|
+
if (state) htmlOpts.state = state;
|
|
674
|
+
bodyHTML = bw.html(body, htmlOpts);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Collect functions registered during this render
|
|
678
|
+
var fnCounterAfter = bw._fnIDCounter;
|
|
679
|
+
var registryEntries = '';
|
|
680
|
+
for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
|
|
681
|
+
var fnKey = 'bw_fn_' + i;
|
|
682
|
+
if (bw._fnRegistry[fnKey]) {
|
|
683
|
+
registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
|
|
684
|
+
bw._fnRegistry[fnKey].toString() + ';\n';
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Build runtime script for <head>
|
|
689
|
+
var runtimeHead = '';
|
|
690
|
+
if (runtime === 'inline') {
|
|
691
|
+
// Read UMD bundle synchronously if in Node.js
|
|
692
|
+
var umdSource = null;
|
|
693
|
+
if (bw._isNode) {
|
|
694
|
+
try {
|
|
695
|
+
var fs = (typeof require === 'function') ? require('fs') : null;
|
|
696
|
+
var pathMod = (typeof require === 'function') ? require('path') : null;
|
|
697
|
+
if (fs && pathMod) {
|
|
698
|
+
// Resolve dist/ relative to this source file
|
|
699
|
+
var srcDir = '';
|
|
700
|
+
try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
|
|
701
|
+
catch(e2) { /* ESM: __filename not available */ }
|
|
702
|
+
if (!srcDir && typeof import.meta !== 'undefined' && import.meta.url) {
|
|
703
|
+
var url = (typeof require === 'function') ? require('url') : null;
|
|
704
|
+
if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath(import.meta.url));
|
|
705
|
+
}
|
|
706
|
+
if (srcDir) {
|
|
707
|
+
var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
|
|
708
|
+
umdSource = fs.readFileSync(distPath, 'utf8');
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
} catch(e) { /* fall through */ }
|
|
712
|
+
}
|
|
713
|
+
if (umdSource) {
|
|
714
|
+
runtimeHead = '<script>' + umdSource + '</script>';
|
|
715
|
+
} else {
|
|
716
|
+
// Fallback to shim in browser or if dist not available
|
|
717
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
718
|
+
}
|
|
719
|
+
} else if (runtime === 'cdn') {
|
|
720
|
+
runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
|
|
721
|
+
} else if (runtime === 'shim') {
|
|
722
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
723
|
+
}
|
|
724
|
+
// runtime === 'none' → empty
|
|
725
|
+
|
|
726
|
+
// Theme CSS
|
|
727
|
+
var themeCSS = '';
|
|
728
|
+
if (theme) {
|
|
729
|
+
var themeConfig = _is(theme, 'string')
|
|
730
|
+
? (THEME_PRESETS[theme.toLowerCase()] || null)
|
|
731
|
+
: theme;
|
|
732
|
+
if (themeConfig) {
|
|
733
|
+
var themeResult = bw.generateTheme('', Object.assign({}, themeConfig, { inject: false }));
|
|
734
|
+
themeCSS = themeResult.css;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Extra <head> elements
|
|
739
|
+
var headHTML = '';
|
|
740
|
+
if (_isA(headExtra) && headExtra.length > 0) {
|
|
741
|
+
headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Favicon
|
|
745
|
+
var faviconTag = '';
|
|
746
|
+
if (favicon) {
|
|
747
|
+
var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
|
|
748
|
+
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c];
|
|
749
|
+
});
|
|
750
|
+
faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Escaped title
|
|
754
|
+
var safeTitle = bw.escapeHTML(title);
|
|
755
|
+
|
|
756
|
+
// Combine all CSS
|
|
757
|
+
var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
|
|
758
|
+
|
|
759
|
+
// Body-end script: registry entries + optional loadDefaultStyles
|
|
760
|
+
var bodyEndScript = '';
|
|
761
|
+
var bodyEndParts = [];
|
|
762
|
+
if (registryEntries) {
|
|
763
|
+
bodyEndParts.push(registryEntries);
|
|
764
|
+
}
|
|
765
|
+
if (runtime === 'inline' || runtime === 'cdn') {
|
|
766
|
+
bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadDefaultStyles();}');
|
|
767
|
+
}
|
|
768
|
+
if (bodyEndParts.length > 0) {
|
|
769
|
+
bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Assemble document
|
|
773
|
+
var parts = [
|
|
774
|
+
'<!DOCTYPE html>',
|
|
775
|
+
'<html lang="' + lang + '">',
|
|
776
|
+
'<head>',
|
|
777
|
+
'<meta charset="UTF-8">',
|
|
778
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
779
|
+
];
|
|
780
|
+
parts.push('<title>' + safeTitle + '</title>');
|
|
781
|
+
if (faviconTag) parts.push(faviconTag);
|
|
782
|
+
if (runtimeHead) parts.push(runtimeHead);
|
|
783
|
+
if (headHTML) parts.push(headHTML);
|
|
784
|
+
if (allCSS) parts.push('<style>' + allCSS + '</style>');
|
|
785
|
+
parts.push('</head>');
|
|
786
|
+
parts.push('<body>');
|
|
787
|
+
parts.push(bodyHTML);
|
|
788
|
+
if (bodyEndScript) parts.push(bodyEndScript);
|
|
789
|
+
parts.push('</body>');
|
|
790
|
+
parts.push('</html>');
|
|
791
|
+
|
|
792
|
+
return parts.join('\n');
|
|
793
|
+
};
|
|
794
|
+
|
|
555
795
|
/**
|
|
556
796
|
* Create a live DOM element from a TACO object (browser only).
|
|
557
797
|
*
|
|
@@ -596,7 +836,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
596
836
|
}
|
|
597
837
|
|
|
598
838
|
// Handle text nodes
|
|
599
|
-
if (
|
|
839
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
600
840
|
return document.createTextNode(String(taco));
|
|
601
841
|
}
|
|
602
842
|
|
|
@@ -609,16 +849,16 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
609
849
|
for (const [key, value] of Object.entries(attrs)) {
|
|
610
850
|
if (value == null || value === false) continue;
|
|
611
851
|
|
|
612
|
-
if (key === 'style' &&
|
|
852
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
613
853
|
// Apply styles directly
|
|
614
854
|
Object.assign(el.style, value);
|
|
615
855
|
} else if (key === 'class') {
|
|
616
856
|
// Handle class as array or string
|
|
617
|
-
const classStr =
|
|
857
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
618
858
|
if (classStr) {
|
|
619
859
|
el.className = classStr;
|
|
620
860
|
}
|
|
621
|
-
} else if (key.startsWith('on') &&
|
|
861
|
+
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
622
862
|
// Event handlers
|
|
623
863
|
const eventName = key.slice(2).toLowerCase();
|
|
624
864
|
el.addEventListener(eventName, value);
|
|
@@ -638,7 +878,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
638
878
|
// Children with data-bw_id or id attributes get local refs on the parent,
|
|
639
879
|
// so o.render functions can access them without any DOM lookup.
|
|
640
880
|
if (content != null) {
|
|
641
|
-
if (
|
|
881
|
+
if (_isA(content)) {
|
|
642
882
|
content.forEach(child => {
|
|
643
883
|
if (child != null) {
|
|
644
884
|
// Handle ComponentHandle in content arrays (Level 2 children)
|
|
@@ -658,20 +898,20 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
658
898
|
if (childEl._bw_refs) {
|
|
659
899
|
if (!el._bw_refs) el._bw_refs = {};
|
|
660
900
|
for (var rk in childEl._bw_refs) {
|
|
661
|
-
if (
|
|
901
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
662
902
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
663
903
|
}
|
|
664
904
|
}
|
|
665
905
|
}
|
|
666
906
|
}
|
|
667
907
|
});
|
|
668
|
-
} else if (
|
|
908
|
+
} else if (_is(content, 'object') && content.__bw_raw) {
|
|
669
909
|
// Raw HTML content — inject via innerHTML
|
|
670
910
|
el.innerHTML = content.v;
|
|
671
911
|
} else if (content._bwComponent === true) {
|
|
672
912
|
// Single ComponentHandle as content
|
|
673
913
|
content.mount(el);
|
|
674
|
-
} else if (
|
|
914
|
+
} else if (_is(content, 'object') && content.t) {
|
|
675
915
|
var childEl = bw.createDOM(content, options);
|
|
676
916
|
el.appendChild(childEl);
|
|
677
917
|
var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
|
|
@@ -682,7 +922,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
682
922
|
if (childEl._bw_refs) {
|
|
683
923
|
if (!el._bw_refs) el._bw_refs = {};
|
|
684
924
|
for (var rk in childEl._bw_refs) {
|
|
685
|
-
if (
|
|
925
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
686
926
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
687
927
|
}
|
|
688
928
|
}
|
|
@@ -715,7 +955,7 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
715
955
|
el._bw_render = opts.render;
|
|
716
956
|
|
|
717
957
|
if (opts.mounted) {
|
|
718
|
-
|
|
958
|
+
_cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
|
|
719
959
|
}
|
|
720
960
|
|
|
721
961
|
// Queue initial render (same timing as mounted)
|
|
@@ -788,7 +1028,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
788
1028
|
const targetEl = bw._el(target);
|
|
789
1029
|
|
|
790
1030
|
if (!targetEl) {
|
|
791
|
-
|
|
1031
|
+
_ce('bw.DOM: Target element not found:', target);
|
|
792
1032
|
return null;
|
|
793
1033
|
}
|
|
794
1034
|
|
|
@@ -828,7 +1068,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
828
1068
|
targetEl.appendChild(taco.element);
|
|
829
1069
|
}
|
|
830
1070
|
// Handle arrays
|
|
831
|
-
else if (
|
|
1071
|
+
else if (_isA(taco)) {
|
|
832
1072
|
taco.forEach(t => {
|
|
833
1073
|
if (t != null) {
|
|
834
1074
|
if (t._bwComponent === true) {
|
|
@@ -864,7 +1104,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
864
1104
|
bw.compileProps = function(handle, props = {}) {
|
|
865
1105
|
const compiledProps = {};
|
|
866
1106
|
|
|
867
|
-
|
|
1107
|
+
_keys(props).forEach(key => {
|
|
868
1108
|
// Create getter/setter for each prop
|
|
869
1109
|
Object.defineProperty(compiledProps, key, {
|
|
870
1110
|
get() {
|
|
@@ -1182,17 +1422,17 @@ bw.patch = function(id, content, attr) {
|
|
|
1182
1422
|
if (attr) {
|
|
1183
1423
|
// Patch an attribute
|
|
1184
1424
|
el.setAttribute(attr, String(content));
|
|
1185
|
-
} else if (
|
|
1425
|
+
} else if (_isA(content)) {
|
|
1186
1426
|
// Patch with array of children (strings and/or TACOs)
|
|
1187
1427
|
el.innerHTML = '';
|
|
1188
1428
|
content.forEach(function(item) {
|
|
1189
|
-
if (
|
|
1429
|
+
if (_is(item, 'string') || _is(item, 'number')) {
|
|
1190
1430
|
el.appendChild(document.createTextNode(String(item)));
|
|
1191
1431
|
} else if (item && item.t) {
|
|
1192
1432
|
el.appendChild(bw.createDOM(item));
|
|
1193
1433
|
}
|
|
1194
1434
|
});
|
|
1195
|
-
} else if (
|
|
1435
|
+
} else if (_is(content, 'object') && content.t) {
|
|
1196
1436
|
// Patch with a TACO — replace children
|
|
1197
1437
|
el.innerHTML = '';
|
|
1198
1438
|
el.appendChild(bw.createDOM(content));
|
|
@@ -1223,7 +1463,7 @@ bw.patch = function(id, content, attr) {
|
|
|
1223
1463
|
bw.patchAll = function(patches) {
|
|
1224
1464
|
var results = {};
|
|
1225
1465
|
for (var id in patches) {
|
|
1226
|
-
if (
|
|
1466
|
+
if (_hop.call(patches, id)) {
|
|
1227
1467
|
results[id] = bw.patch(id, patches[id]);
|
|
1228
1468
|
}
|
|
1229
1469
|
}
|
|
@@ -1320,7 +1560,7 @@ bw.pub = function(topic, detail) {
|
|
|
1320
1560
|
snapshot[i].handler(detail);
|
|
1321
1561
|
called++;
|
|
1322
1562
|
} catch (err) {
|
|
1323
|
-
|
|
1563
|
+
_cw('bw.pub: subscriber error on topic "' + topic + '":', err);
|
|
1324
1564
|
}
|
|
1325
1565
|
}
|
|
1326
1566
|
return called;
|
|
@@ -1416,8 +1656,8 @@ bw._fnIDCounter = 0;
|
|
|
1416
1656
|
* @see bw.funcGetDispatchStr
|
|
1417
1657
|
*/
|
|
1418
1658
|
bw.funcRegister = function(fn, name) {
|
|
1419
|
-
if (
|
|
1420
|
-
var fnID = (
|
|
1659
|
+
if (!_is(fn, 'function')) return '';
|
|
1660
|
+
var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
|
|
1421
1661
|
bw._fnRegistry[fnID] = fn;
|
|
1422
1662
|
return fnID;
|
|
1423
1663
|
};
|
|
@@ -1436,7 +1676,7 @@ bw.funcRegister = function(fn, name) {
|
|
|
1436
1676
|
bw.funcGetById = function(name, errFn) {
|
|
1437
1677
|
name = String(name);
|
|
1438
1678
|
if (name in bw._fnRegistry) return bw._fnRegistry[name];
|
|
1439
|
-
return (
|
|
1679
|
+
return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
|
|
1440
1680
|
};
|
|
1441
1681
|
|
|
1442
1682
|
/**
|
|
@@ -1477,13 +1717,30 @@ bw.funcUnregister = function(name) {
|
|
|
1477
1717
|
bw.funcGetRegistry = function() {
|
|
1478
1718
|
var copy = {};
|
|
1479
1719
|
for (var k in bw._fnRegistry) {
|
|
1480
|
-
if (
|
|
1720
|
+
if (_hop.call(bw._fnRegistry, k)) {
|
|
1481
1721
|
copy[k] = bw._fnRegistry[k];
|
|
1482
1722
|
}
|
|
1483
1723
|
}
|
|
1484
1724
|
return copy;
|
|
1485
1725
|
};
|
|
1486
1726
|
|
|
1727
|
+
/**
|
|
1728
|
+
* Minimal runtime shim for funcRegister dispatch in static HTML.
|
|
1729
|
+
* When embedded in a `<script>` tag, provides just enough infrastructure
|
|
1730
|
+
* for `bw.funcGetById()` calls to resolve. The actual function bodies
|
|
1731
|
+
* are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
|
|
1732
|
+
* @type {string}
|
|
1733
|
+
* @category Function Registry
|
|
1734
|
+
*/
|
|
1735
|
+
bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
|
|
1736
|
+
'if(!bw._fnRegistry)bw._fnRegistry={};' +
|
|
1737
|
+
'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
|
|
1738
|
+
'console.warn("bw: unregistered fn "+n)};};' +
|
|
1739
|
+
'bw.funcRegister=function(fn,name){' +
|
|
1740
|
+
'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
|
|
1741
|
+
'bw._fnRegistry[id]=fn;return id;};' +
|
|
1742
|
+
'window.bw=bw;})();';
|
|
1743
|
+
|
|
1487
1744
|
// ===================================================================================
|
|
1488
1745
|
// Template Binding Utilities
|
|
1489
1746
|
// ===================================================================================
|
|
@@ -1511,7 +1768,10 @@ bw._evaluatePath = function(state, path) {
|
|
|
1511
1768
|
var parts = path.split('.');
|
|
1512
1769
|
var val = state;
|
|
1513
1770
|
for (var i = 0; i < parts.length; i++) {
|
|
1514
|
-
if (val == null)
|
|
1771
|
+
if (val == null) {
|
|
1772
|
+
if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
|
|
1773
|
+
return '';
|
|
1774
|
+
}
|
|
1515
1775
|
val = val[parts[i]];
|
|
1516
1776
|
}
|
|
1517
1777
|
return (val == null) ? '' : val;
|
|
@@ -1531,7 +1791,7 @@ bw._evaluatePath = function(state, path) {
|
|
|
1531
1791
|
*/
|
|
1532
1792
|
bw._compiledExprs = {};
|
|
1533
1793
|
bw._resolveTemplate = function(str, state, compile) {
|
|
1534
|
-
if (
|
|
1794
|
+
if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
|
|
1535
1795
|
var bindings = bw._parseBindings(str);
|
|
1536
1796
|
if (bindings.length === 0) return str;
|
|
1537
1797
|
|
|
@@ -1553,6 +1813,7 @@ bw._resolveTemplate = function(str, state, compile) {
|
|
|
1553
1813
|
try {
|
|
1554
1814
|
val = bw._compiledExprs[b.expr](state);
|
|
1555
1815
|
} catch (e) {
|
|
1816
|
+
if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
|
|
1556
1817
|
val = '';
|
|
1557
1818
|
}
|
|
1558
1819
|
} else {
|
|
@@ -1661,7 +1922,7 @@ function ComponentHandle(taco) {
|
|
|
1661
1922
|
this._state = {};
|
|
1662
1923
|
if (o.state) {
|
|
1663
1924
|
for (var k in o.state) {
|
|
1664
|
-
if (
|
|
1925
|
+
if (_hop.call(o.state, k)) {
|
|
1665
1926
|
this._state[k] = o.state[k];
|
|
1666
1927
|
}
|
|
1667
1928
|
}
|
|
@@ -1670,7 +1931,7 @@ function ComponentHandle(taco) {
|
|
|
1670
1931
|
this._actions = {};
|
|
1671
1932
|
if (o.actions) {
|
|
1672
1933
|
for (var k2 in o.actions) {
|
|
1673
|
-
if (
|
|
1934
|
+
if (_hop.call(o.actions, k2)) {
|
|
1674
1935
|
this._actions[k2] = o.actions[k2];
|
|
1675
1936
|
}
|
|
1676
1937
|
}
|
|
@@ -1680,7 +1941,7 @@ function ComponentHandle(taco) {
|
|
|
1680
1941
|
if (o.methods) {
|
|
1681
1942
|
var self = this;
|
|
1682
1943
|
for (var k3 in o.methods) {
|
|
1683
|
-
if (
|
|
1944
|
+
if (_hop.call(o.methods, k3)) {
|
|
1684
1945
|
this._methods[k3] = o.methods[k3];
|
|
1685
1946
|
(function(methodName, methodFn) {
|
|
1686
1947
|
self[methodName] = function() {
|
|
@@ -1713,14 +1974,23 @@ function ComponentHandle(taco) {
|
|
|
1713
1974
|
this._compile = !!o.compile;
|
|
1714
1975
|
this._bw_refs = {};
|
|
1715
1976
|
this._refCounter = 0;
|
|
1977
|
+
// Child component ownership (Bug #5)
|
|
1978
|
+
this._children = [];
|
|
1979
|
+
this._parent = null;
|
|
1980
|
+
// Factory metadata for BCCL rebuild (Bug #6)
|
|
1981
|
+
this._factory = taco._bwFactory || null;
|
|
1716
1982
|
}
|
|
1717
1983
|
|
|
1984
|
+
// Short alias for ComponentHandle.prototype (see alias block at top of file).
|
|
1985
|
+
// 28 method definitions × 25 chars = ~700B raw savings in minified output.
|
|
1986
|
+
var _chp = ComponentHandle.prototype;
|
|
1987
|
+
|
|
1718
1988
|
// ── State Methods ──
|
|
1719
1989
|
|
|
1720
1990
|
/**
|
|
1721
1991
|
* Get a state value. Dot-path supported: `get('user.name')`
|
|
1722
1992
|
*/
|
|
1723
|
-
|
|
1993
|
+
_chp.get = function(key) {
|
|
1724
1994
|
return bw._evaluatePath(this._state, key);
|
|
1725
1995
|
};
|
|
1726
1996
|
|
|
@@ -1730,12 +2000,13 @@ ComponentHandle.prototype.get = function(key) {
|
|
|
1730
2000
|
* @param {*} value - New value
|
|
1731
2001
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
1732
2002
|
*/
|
|
1733
|
-
|
|
2003
|
+
_chp.set = function(key, value, opts) {
|
|
1734
2004
|
// Dot-path set
|
|
1735
2005
|
var parts = key.split('.');
|
|
1736
2006
|
var obj = this._state;
|
|
1737
2007
|
for (var i = 0; i < parts.length - 1; i++) {
|
|
1738
|
-
if (obj[parts[i]]
|
|
2008
|
+
if (!_is(obj[parts[i]], 'object')) {
|
|
2009
|
+
if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
|
|
1739
2010
|
obj[parts[i]] = {};
|
|
1740
2011
|
}
|
|
1741
2012
|
obj = obj[parts[i]];
|
|
@@ -1755,10 +2026,10 @@ ComponentHandle.prototype.set = function(key, value, opts) {
|
|
|
1755
2026
|
/**
|
|
1756
2027
|
* Get a shallow clone of the full state.
|
|
1757
2028
|
*/
|
|
1758
|
-
|
|
2029
|
+
_chp.getState = function() {
|
|
1759
2030
|
var clone = {};
|
|
1760
2031
|
for (var k in this._state) {
|
|
1761
|
-
if (
|
|
2032
|
+
if (_hop.call(this._state, k)) {
|
|
1762
2033
|
clone[k] = this._state[k];
|
|
1763
2034
|
}
|
|
1764
2035
|
}
|
|
@@ -1770,9 +2041,9 @@ ComponentHandle.prototype.getState = function() {
|
|
|
1770
2041
|
* @param {Object} updates - Key-value pairs to merge
|
|
1771
2042
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
1772
2043
|
*/
|
|
1773
|
-
|
|
2044
|
+
_chp.setState = function(updates, opts) {
|
|
1774
2045
|
for (var k in updates) {
|
|
1775
|
-
if (
|
|
2046
|
+
if (_hop.call(updates, k)) {
|
|
1776
2047
|
this._state[k] = updates[k];
|
|
1777
2048
|
this._dirtyKeys[k] = true;
|
|
1778
2049
|
}
|
|
@@ -1789,9 +2060,9 @@ ComponentHandle.prototype.setState = function(updates, opts) {
|
|
|
1789
2060
|
/**
|
|
1790
2061
|
* Push a value onto an array in state. Clones the array.
|
|
1791
2062
|
*/
|
|
1792
|
-
|
|
2063
|
+
_chp.push = function(key, val) {
|
|
1793
2064
|
var arr = this.get(key);
|
|
1794
|
-
var newArr =
|
|
2065
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
1795
2066
|
newArr.push(val);
|
|
1796
2067
|
this.set(key, newArr);
|
|
1797
2068
|
};
|
|
@@ -1799,9 +2070,9 @@ ComponentHandle.prototype.push = function(key, val) {
|
|
|
1799
2070
|
/**
|
|
1800
2071
|
* Splice an array in state. Clones the array.
|
|
1801
2072
|
*/
|
|
1802
|
-
|
|
2073
|
+
_chp.splice = function(key, start, deleteCount) {
|
|
1803
2074
|
var arr = this.get(key);
|
|
1804
|
-
var newArr =
|
|
2075
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
1805
2076
|
var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
|
|
1806
2077
|
Array.prototype.splice.apply(newArr, args);
|
|
1807
2078
|
this.set(key, newArr);
|
|
@@ -1809,7 +2080,7 @@ ComponentHandle.prototype.splice = function(key, start, deleteCount) {
|
|
|
1809
2080
|
|
|
1810
2081
|
// ── Scheduling ──
|
|
1811
2082
|
|
|
1812
|
-
|
|
2083
|
+
_chp._scheduleDirty = function() {
|
|
1813
2084
|
if (!this._scheduled) {
|
|
1814
2085
|
this._scheduled = true;
|
|
1815
2086
|
bw._dirtyComponents.push(this);
|
|
@@ -1824,17 +2095,17 @@ ComponentHandle.prototype._scheduleDirty = function() {
|
|
|
1824
2095
|
* Creates binding descriptors with refIds for targeted DOM updates.
|
|
1825
2096
|
* @private
|
|
1826
2097
|
*/
|
|
1827
|
-
|
|
2098
|
+
_chp._compileBindings = function() {
|
|
1828
2099
|
this._bindings = [];
|
|
1829
2100
|
this._refCounter = 0;
|
|
1830
|
-
var stateKeys =
|
|
2101
|
+
var stateKeys = _keys(this._state);
|
|
1831
2102
|
var self = this;
|
|
1832
2103
|
|
|
1833
2104
|
function walkTaco(taco, path) {
|
|
1834
|
-
if (taco
|
|
2105
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
1835
2106
|
|
|
1836
2107
|
// Check content for bindings
|
|
1837
|
-
if (
|
|
2108
|
+
if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
|
|
1838
2109
|
var refId = 'bw_ref_' + self._refCounter++;
|
|
1839
2110
|
var parsed = bw._parseBindings(taco.c);
|
|
1840
2111
|
var deps = [];
|
|
@@ -1856,10 +2127,10 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
1856
2127
|
// Check attributes for bindings
|
|
1857
2128
|
if (taco.a) {
|
|
1858
2129
|
for (var attrName in taco.a) {
|
|
1859
|
-
if (!
|
|
2130
|
+
if (!_hop.call(taco.a, attrName)) continue;
|
|
1860
2131
|
if (attrName === 'data-bw_ref') continue;
|
|
1861
2132
|
var attrVal = taco.a[attrName];
|
|
1862
|
-
if (
|
|
2133
|
+
if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
|
|
1863
2134
|
var refId2 = 'bw_ref_' + self._refCounter++;
|
|
1864
2135
|
var parsed2 = bw._parseBindings(attrVal);
|
|
1865
2136
|
var deps2 = [];
|
|
@@ -1885,9 +2156,27 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
1885
2156
|
}
|
|
1886
2157
|
|
|
1887
2158
|
// Recurse into children
|
|
1888
|
-
if (
|
|
2159
|
+
if (_isA(taco.c)) {
|
|
1889
2160
|
for (var i = 0; i < taco.c.length; i++) {
|
|
1890
|
-
|
|
2161
|
+
// Wrap string children with ${expr} in a span so patches target the span, not the parent
|
|
2162
|
+
if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
|
|
2163
|
+
var mixedRefId = 'bw_ref_' + self._refCounter++;
|
|
2164
|
+
var mixedParsed = bw._parseBindings(taco.c[i]);
|
|
2165
|
+
var mixedDeps = [];
|
|
2166
|
+
for (var mi = 0; mi < mixedParsed.length; mi++) {
|
|
2167
|
+
mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
|
|
2168
|
+
}
|
|
2169
|
+
self._bindings.push({
|
|
2170
|
+
expr: taco.c[i],
|
|
2171
|
+
type: 'content',
|
|
2172
|
+
refId: mixedRefId,
|
|
2173
|
+
deps: mixedDeps,
|
|
2174
|
+
template: taco.c[i]
|
|
2175
|
+
});
|
|
2176
|
+
// Replace string with a span wrapper so textContent targets the span only
|
|
2177
|
+
taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
|
|
2178
|
+
}
|
|
2179
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
1891
2180
|
walkTaco(taco.c[i], path.concat(i));
|
|
1892
2181
|
}
|
|
1893
2182
|
// Handle bw.when/bw.each markers
|
|
@@ -1922,7 +2211,7 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
1922
2211
|
taco.c[i]._refId = eachRefId;
|
|
1923
2212
|
}
|
|
1924
2213
|
}
|
|
1925
|
-
} else if (taco.c
|
|
2214
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
1926
2215
|
walkTaco(taco.c, path.concat(0));
|
|
1927
2216
|
}
|
|
1928
2217
|
|
|
@@ -1938,7 +2227,7 @@ ComponentHandle.prototype._compileBindings = function() {
|
|
|
1938
2227
|
* Build ref map from the live DOM after createDOM.
|
|
1939
2228
|
* @private
|
|
1940
2229
|
*/
|
|
1941
|
-
|
|
2230
|
+
_chp._collectRefs = function() {
|
|
1942
2231
|
this._bw_refs = {};
|
|
1943
2232
|
if (!this.element) return;
|
|
1944
2233
|
var els = this.element.querySelectorAll('[data-bw_ref]');
|
|
@@ -1959,7 +2248,7 @@ ComponentHandle.prototype._collectRefs = function() {
|
|
|
1959
2248
|
* Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
|
|
1960
2249
|
* @param {Element} parentEl - DOM element to mount into
|
|
1961
2250
|
*/
|
|
1962
|
-
|
|
2251
|
+
_chp.mount = function(parentEl) {
|
|
1963
2252
|
// willMount hook
|
|
1964
2253
|
if (this._hooks.willMount) this._hooks.willMount(this);
|
|
1965
2254
|
|
|
@@ -1981,7 +2270,7 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
1981
2270
|
// Register named actions in function registry
|
|
1982
2271
|
var self = this;
|
|
1983
2272
|
for (var actionName in this._actions) {
|
|
1984
|
-
if (
|
|
2273
|
+
if (_hop.call(this._actions, actionName)) {
|
|
1985
2274
|
var registeredName = this._bwId + '_' + actionName;
|
|
1986
2275
|
(function(aName) {
|
|
1987
2276
|
bw.funcRegister(function(evt) {
|
|
@@ -2000,6 +2289,11 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
2000
2289
|
this.element = bw.createDOM(tacoForDOM);
|
|
2001
2290
|
this.element._bwComponentHandle = this;
|
|
2002
2291
|
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
2292
|
+
|
|
2293
|
+
// Restore o.render from original TACO (stripped by _tacoForDOM)
|
|
2294
|
+
if (this.taco.o && this.taco.o.render) {
|
|
2295
|
+
this.element._bw_render = this.taco.o.render;
|
|
2296
|
+
}
|
|
2003
2297
|
if (this._userTag) {
|
|
2004
2298
|
this.element.classList.add(this._userTag);
|
|
2005
2299
|
}
|
|
@@ -2015,6 +2309,16 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
2015
2309
|
|
|
2016
2310
|
this.mounted = true;
|
|
2017
2311
|
|
|
2312
|
+
// Scan for child ComponentHandles and link parent/child (Bug #5)
|
|
2313
|
+
var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
|
|
2314
|
+
for (var ci = 0; ci < childEls.length; ci++) {
|
|
2315
|
+
var ch = childEls[ci]._bwComponentHandle;
|
|
2316
|
+
if (ch && ch !== this && !ch._parent) {
|
|
2317
|
+
ch._parent = this;
|
|
2318
|
+
this._children.push(ch);
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2018
2322
|
// mounted hook (backward compat: fn.length === 2 wraps (el, state))
|
|
2019
2323
|
if (this._hooks.mounted) {
|
|
2020
2324
|
if (this._hooks.mounted.length === 2) {
|
|
@@ -2023,16 +2327,21 @@ ComponentHandle.prototype.mount = function(parentEl) {
|
|
|
2023
2327
|
this._hooks.mounted(this);
|
|
2024
2328
|
}
|
|
2025
2329
|
}
|
|
2330
|
+
|
|
2331
|
+
// Invoke o.render on initial mount (if present)
|
|
2332
|
+
if (this.element._bw_render) {
|
|
2333
|
+
this.element._bw_render(this.element, this._state);
|
|
2334
|
+
}
|
|
2026
2335
|
};
|
|
2027
2336
|
|
|
2028
2337
|
/**
|
|
2029
2338
|
* Prepare TACO for initial render: resolve when/each markers.
|
|
2030
2339
|
* @private
|
|
2031
2340
|
*/
|
|
2032
|
-
|
|
2033
|
-
if (!taco
|
|
2341
|
+
_chp._prepareTaco = function(taco) {
|
|
2342
|
+
if (!_is(taco, 'object')) return;
|
|
2034
2343
|
|
|
2035
|
-
if (
|
|
2344
|
+
if (_isA(taco.c)) {
|
|
2036
2345
|
for (var i = taco.c.length - 1; i >= 0; i--) {
|
|
2037
2346
|
var child = taco.c[i];
|
|
2038
2347
|
if (child && child._bwWhen) {
|
|
@@ -2057,18 +2366,18 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
|
|
|
2057
2366
|
var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
2058
2367
|
var arr = bw._evaluatePath(this._state, eachExprStr);
|
|
2059
2368
|
var items = [];
|
|
2060
|
-
if (
|
|
2369
|
+
if (_isA(arr)) {
|
|
2061
2370
|
for (var j = 0; j < arr.length; j++) {
|
|
2062
2371
|
items.push(child.factory(arr[j], j));
|
|
2063
2372
|
}
|
|
2064
2373
|
}
|
|
2065
2374
|
taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
|
|
2066
2375
|
}
|
|
2067
|
-
if (taco.c[i]
|
|
2376
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
2068
2377
|
this._prepareTaco(taco.c[i]);
|
|
2069
2378
|
}
|
|
2070
2379
|
}
|
|
2071
|
-
} else if (taco.c
|
|
2380
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
2072
2381
|
this._prepareTaco(taco.c);
|
|
2073
2382
|
}
|
|
2074
2383
|
};
|
|
@@ -2077,12 +2386,12 @@ ComponentHandle.prototype._prepareTaco = function(taco) {
|
|
|
2077
2386
|
* Wire action name strings (in onclick etc.) to dispatch function calls.
|
|
2078
2387
|
* @private
|
|
2079
2388
|
*/
|
|
2080
|
-
|
|
2081
|
-
if (!taco
|
|
2389
|
+
_chp._wireActions = function(taco) {
|
|
2390
|
+
if (!_is(taco, 'object') || !taco.t) return;
|
|
2082
2391
|
if (taco.a) {
|
|
2083
2392
|
for (var key in taco.a) {
|
|
2084
|
-
if (!
|
|
2085
|
-
if (key.startsWith('on') &&
|
|
2393
|
+
if (!_hop.call(taco.a, key)) continue;
|
|
2394
|
+
if (key.startsWith('on') && _is(taco.a[key], 'string')) {
|
|
2086
2395
|
var actionName = taco.a[key];
|
|
2087
2396
|
if (actionName in this._actions) {
|
|
2088
2397
|
var registeredName = this._bwId + '_' + actionName;
|
|
@@ -2096,11 +2405,11 @@ ComponentHandle.prototype._wireActions = function(taco) {
|
|
|
2096
2405
|
}
|
|
2097
2406
|
}
|
|
2098
2407
|
}
|
|
2099
|
-
if (
|
|
2408
|
+
if (_isA(taco.c)) {
|
|
2100
2409
|
for (var i = 0; i < taco.c.length; i++) {
|
|
2101
2410
|
this._wireActions(taco.c[i]);
|
|
2102
2411
|
}
|
|
2103
|
-
} else if (taco.c
|
|
2412
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
2104
2413
|
this._wireActions(taco.c);
|
|
2105
2414
|
}
|
|
2106
2415
|
};
|
|
@@ -2109,7 +2418,7 @@ ComponentHandle.prototype._wireActions = function(taco) {
|
|
|
2109
2418
|
* Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
|
|
2110
2419
|
* @private
|
|
2111
2420
|
*/
|
|
2112
|
-
|
|
2421
|
+
_chp._deepCloneTaco = function(taco) {
|
|
2113
2422
|
if (taco == null) return taco;
|
|
2114
2423
|
// Preserve _bwWhen / _bwEach markers (contain functions)
|
|
2115
2424
|
if (taco._bwWhen) {
|
|
@@ -2121,18 +2430,18 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
|
|
|
2121
2430
|
if (taco._bwEach) {
|
|
2122
2431
|
return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
|
|
2123
2432
|
}
|
|
2124
|
-
if (
|
|
2433
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
2125
2434
|
var result = { t: taco.t };
|
|
2126
2435
|
if (taco.a) {
|
|
2127
2436
|
result.a = {};
|
|
2128
2437
|
for (var k in taco.a) {
|
|
2129
|
-
if (
|
|
2438
|
+
if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
|
|
2130
2439
|
}
|
|
2131
2440
|
}
|
|
2132
2441
|
if (taco.c != null) {
|
|
2133
|
-
if (
|
|
2442
|
+
if (_isA(taco.c)) {
|
|
2134
2443
|
result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
|
|
2135
|
-
} else if (
|
|
2444
|
+
} else if (_is(taco.c, 'object')) {
|
|
2136
2445
|
result.c = this._deepCloneTaco(taco.c);
|
|
2137
2446
|
} else {
|
|
2138
2447
|
result.c = taco.c;
|
|
@@ -2146,27 +2455,31 @@ ComponentHandle.prototype._deepCloneTaco = function(taco) {
|
|
|
2146
2455
|
* Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
|
|
2147
2456
|
* @private
|
|
2148
2457
|
*/
|
|
2149
|
-
|
|
2150
|
-
if (!taco
|
|
2458
|
+
_chp._tacoForDOM = function(taco) {
|
|
2459
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
2151
2460
|
var result = { t: taco.t };
|
|
2152
2461
|
if (taco.a) result.a = taco.a;
|
|
2153
2462
|
if (taco.c != null) {
|
|
2154
|
-
if (
|
|
2463
|
+
if (_isA(taco.c)) {
|
|
2155
2464
|
result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
|
|
2156
|
-
} else if (
|
|
2465
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
2157
2466
|
result.c = this._tacoForDOM(taco.c);
|
|
2158
2467
|
} else {
|
|
2159
2468
|
result.c = taco.c;
|
|
2160
2469
|
}
|
|
2161
2470
|
}
|
|
2162
2471
|
// Intentionally strip o (no mounted/unmount/state/render on sub-elements)
|
|
2472
|
+
if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
|
|
2473
|
+
_cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
|
|
2474
|
+
'>. Use onclick attribute or bw.component() for child interactivity.');
|
|
2475
|
+
}
|
|
2163
2476
|
return result;
|
|
2164
2477
|
};
|
|
2165
2478
|
|
|
2166
2479
|
/**
|
|
2167
2480
|
* Unmount: remove from DOM, deactivate, preserve state for re-mount.
|
|
2168
2481
|
*/
|
|
2169
|
-
|
|
2482
|
+
_chp.unmount = function() {
|
|
2170
2483
|
if (!this.mounted) return;
|
|
2171
2484
|
|
|
2172
2485
|
// unmount hook
|
|
@@ -2201,12 +2514,23 @@ ComponentHandle.prototype.unmount = function() {
|
|
|
2201
2514
|
/**
|
|
2202
2515
|
* Destroy: unmount + clear state + unregister actions.
|
|
2203
2516
|
*/
|
|
2204
|
-
|
|
2517
|
+
_chp.destroy = function() {
|
|
2205
2518
|
// willDestroy hook
|
|
2206
2519
|
if (this._hooks.willDestroy) {
|
|
2207
2520
|
this._hooks.willDestroy(this);
|
|
2208
2521
|
}
|
|
2209
2522
|
|
|
2523
|
+
// Cascade destroy to children depth-first (Bug #5)
|
|
2524
|
+
for (var ci = this._children.length - 1; ci >= 0; ci--) {
|
|
2525
|
+
this._children[ci].destroy();
|
|
2526
|
+
}
|
|
2527
|
+
this._children = [];
|
|
2528
|
+
if (this._parent) {
|
|
2529
|
+
var idx = this._parent._children.indexOf(this);
|
|
2530
|
+
if (idx >= 0) this._parent._children.splice(idx, 1);
|
|
2531
|
+
this._parent = null;
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2210
2534
|
this.unmount();
|
|
2211
2535
|
|
|
2212
2536
|
// Unregister actions from function registry
|
|
@@ -2233,12 +2557,36 @@ ComponentHandle.prototype.destroy = function() {
|
|
|
2233
2557
|
* Flush dirty state: resolve changed bindings and apply to DOM.
|
|
2234
2558
|
* @private
|
|
2235
2559
|
*/
|
|
2236
|
-
|
|
2560
|
+
_chp._flush = function() {
|
|
2237
2561
|
this._scheduled = false;
|
|
2238
|
-
var changedKeys =
|
|
2562
|
+
var changedKeys = _keys(this._dirtyKeys);
|
|
2239
2563
|
this._dirtyKeys = {};
|
|
2240
2564
|
if (changedKeys.length === 0 || !this.mounted) return;
|
|
2241
2565
|
|
|
2566
|
+
// Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
|
|
2567
|
+
// rebuild the TACO from the factory with merged state (Bug #6)
|
|
2568
|
+
if (this._factory) {
|
|
2569
|
+
var rebuildNeeded = false;
|
|
2570
|
+
for (var fi = 0; fi < changedKeys.length; fi++) {
|
|
2571
|
+
if (_hop.call(this._factory.props, changedKeys[fi])) {
|
|
2572
|
+
rebuildNeeded = true; break;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
if (rebuildNeeded) {
|
|
2576
|
+
var merged = {};
|
|
2577
|
+
for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
|
|
2578
|
+
for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
|
|
2579
|
+
this._factory.props = merged;
|
|
2580
|
+
var newTaco = bw.make(this._factory.type, merged);
|
|
2581
|
+
newTaco._bwFactory = this._factory;
|
|
2582
|
+
this.taco = newTaco;
|
|
2583
|
+
this._originalTaco = this._deepCloneTaco(newTaco);
|
|
2584
|
+
this._render();
|
|
2585
|
+
if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2242
2590
|
// willUpdate hook
|
|
2243
2591
|
if (this._hooks.willUpdate) {
|
|
2244
2592
|
this._hooks.willUpdate(this, changedKeys);
|
|
@@ -2277,7 +2625,7 @@ ComponentHandle.prototype._flush = function() {
|
|
|
2277
2625
|
* Returns list of patches to apply.
|
|
2278
2626
|
* @private
|
|
2279
2627
|
*/
|
|
2280
|
-
|
|
2628
|
+
_chp._resolveBindings = function(changedKeys) {
|
|
2281
2629
|
var patches = [];
|
|
2282
2630
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
2283
2631
|
var b = this._bindings[i];
|
|
@@ -2313,11 +2661,14 @@ ComponentHandle.prototype._resolveBindings = function(changedKeys) {
|
|
|
2313
2661
|
* Apply patches to DOM.
|
|
2314
2662
|
* @private
|
|
2315
2663
|
*/
|
|
2316
|
-
|
|
2664
|
+
_chp._applyPatches = function(patches) {
|
|
2317
2665
|
for (var i = 0; i < patches.length; i++) {
|
|
2318
2666
|
var p = patches[i];
|
|
2319
2667
|
var el = this._bw_refs[p.refId];
|
|
2320
|
-
if (!el)
|
|
2668
|
+
if (!el) {
|
|
2669
|
+
if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
|
|
2670
|
+
continue;
|
|
2671
|
+
}
|
|
2321
2672
|
if (p.type === 'content') {
|
|
2322
2673
|
el.textContent = p.value;
|
|
2323
2674
|
} else if (p.type === 'attribute') {
|
|
@@ -2334,7 +2685,7 @@ ComponentHandle.prototype._applyPatches = function(patches) {
|
|
|
2334
2685
|
* Resolve all bindings and apply (used for initial render).
|
|
2335
2686
|
* @private
|
|
2336
2687
|
*/
|
|
2337
|
-
|
|
2688
|
+
_chp._resolveAndApplyAll = function() {
|
|
2338
2689
|
var patches = [];
|
|
2339
2690
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
2340
2691
|
var b = this._bindings[i];
|
|
@@ -2357,7 +2708,7 @@ ComponentHandle.prototype._resolveAndApplyAll = function() {
|
|
|
2357
2708
|
* Full re-render for structural changes (when/each branch switches).
|
|
2358
2709
|
* @private
|
|
2359
2710
|
*/
|
|
2360
|
-
|
|
2711
|
+
_chp._render = function() {
|
|
2361
2712
|
if (!this.element || !this.element.parentNode) return;
|
|
2362
2713
|
var parent = this.element.parentNode;
|
|
2363
2714
|
var nextSibling = this.element.nextSibling;
|
|
@@ -2397,7 +2748,7 @@ ComponentHandle.prototype._render = function() {
|
|
|
2397
2748
|
* @param {string} event - Event name (e.g., 'click')
|
|
2398
2749
|
* @param {Function} handler - Event handler
|
|
2399
2750
|
*/
|
|
2400
|
-
|
|
2751
|
+
_chp.on = function(event, handler) {
|
|
2401
2752
|
if (this.element) {
|
|
2402
2753
|
this.element.addEventListener(event, handler);
|
|
2403
2754
|
}
|
|
@@ -2409,7 +2760,7 @@ ComponentHandle.prototype.on = function(event, handler) {
|
|
|
2409
2760
|
* @param {string} event - Event name
|
|
2410
2761
|
* @param {Function} handler - Handler to remove
|
|
2411
2762
|
*/
|
|
2412
|
-
|
|
2763
|
+
_chp.off = function(event, handler) {
|
|
2413
2764
|
if (this.element) {
|
|
2414
2765
|
this.element.removeEventListener(event, handler);
|
|
2415
2766
|
}
|
|
@@ -2424,7 +2775,7 @@ ComponentHandle.prototype.off = function(event, handler) {
|
|
|
2424
2775
|
* @param {Function} handler - Handler function
|
|
2425
2776
|
* @returns {Function} Unsubscribe function
|
|
2426
2777
|
*/
|
|
2427
|
-
|
|
2778
|
+
_chp.sub = function(topic, handler) {
|
|
2428
2779
|
var unsub = bw.sub(topic, handler);
|
|
2429
2780
|
this._subs.push(unsub);
|
|
2430
2781
|
return unsub;
|
|
@@ -2435,10 +2786,10 @@ ComponentHandle.prototype.sub = function(topic, handler) {
|
|
|
2435
2786
|
* @param {string} name - Action name
|
|
2436
2787
|
* @param {...*} args - Arguments passed after comp
|
|
2437
2788
|
*/
|
|
2438
|
-
|
|
2789
|
+
_chp.action = function(name) {
|
|
2439
2790
|
var fn = this._actions[name];
|
|
2440
2791
|
if (!fn) {
|
|
2441
|
-
|
|
2792
|
+
_cw('ComponentHandle.action: unknown action "' + name + '"');
|
|
2442
2793
|
return;
|
|
2443
2794
|
}
|
|
2444
2795
|
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
|
|
@@ -2450,7 +2801,7 @@ ComponentHandle.prototype.action = function(name) {
|
|
|
2450
2801
|
* @param {string} sel - CSS selector
|
|
2451
2802
|
* @returns {Element|null}
|
|
2452
2803
|
*/
|
|
2453
|
-
|
|
2804
|
+
_chp.select = function(sel) {
|
|
2454
2805
|
return this.element ? this.element.querySelector(sel) : null;
|
|
2455
2806
|
};
|
|
2456
2807
|
|
|
@@ -2459,7 +2810,7 @@ ComponentHandle.prototype.select = function(sel) {
|
|
|
2459
2810
|
* @param {string} sel - CSS selector
|
|
2460
2811
|
* @returns {Element[]}
|
|
2461
2812
|
*/
|
|
2462
|
-
|
|
2813
|
+
_chp.selectAll = function(sel) {
|
|
2463
2814
|
if (!this.element) return [];
|
|
2464
2815
|
return Array.prototype.slice.call(this.element.querySelectorAll(sel));
|
|
2465
2816
|
};
|
|
@@ -2470,7 +2821,7 @@ ComponentHandle.prototype.selectAll = function(sel) {
|
|
|
2470
2821
|
* @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
|
|
2471
2822
|
* @returns {ComponentHandle} this (for chaining)
|
|
2472
2823
|
*/
|
|
2473
|
-
|
|
2824
|
+
_chp.userTag = function(tag) {
|
|
2474
2825
|
this._userTag = tag;
|
|
2475
2826
|
if (this.element) {
|
|
2476
2827
|
this.element.classList.add(tag);
|
|
@@ -2571,8 +2922,8 @@ bw.message = function(target, action, data) {
|
|
|
2571
2922
|
}
|
|
2572
2923
|
if (!el || !el._bwComponentHandle) return false;
|
|
2573
2924
|
var comp = el._bwComponentHandle;
|
|
2574
|
-
if (
|
|
2575
|
-
|
|
2925
|
+
if (!_is(comp[action], 'function')) {
|
|
2926
|
+
_cw('bw.message: unknown action "' + action + '" on component ' + target);
|
|
2576
2927
|
return false;
|
|
2577
2928
|
}
|
|
2578
2929
|
comp[action](data);
|
|
@@ -2609,7 +2960,7 @@ bw._builtinClientFunctions = {
|
|
|
2609
2960
|
},
|
|
2610
2961
|
focus: function(selector) {
|
|
2611
2962
|
var el = bw._el(selector);
|
|
2612
|
-
if (el &&
|
|
2963
|
+
if (el && _is(el.focus, 'function')) el.focus();
|
|
2613
2964
|
},
|
|
2614
2965
|
download: function(filename, content, mimeType) {
|
|
2615
2966
|
if (typeof document === 'undefined') return;
|
|
@@ -2774,12 +3125,12 @@ bw.clientApply = function(msg) {
|
|
|
2774
3125
|
} else if (type === 'remove') {
|
|
2775
3126
|
var toRemove = bw._el(target);
|
|
2776
3127
|
if (!toRemove) return false;
|
|
2777
|
-
if (
|
|
3128
|
+
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
2778
3129
|
toRemove.remove();
|
|
2779
3130
|
return true;
|
|
2780
3131
|
|
|
2781
3132
|
} else if (type === 'batch') {
|
|
2782
|
-
if (!
|
|
3133
|
+
if (!_isA(msg.ops)) return false;
|
|
2783
3134
|
var allOk = true;
|
|
2784
3135
|
msg.ops.forEach(function(op) {
|
|
2785
3136
|
if (!bw.clientApply(op)) allOk = false;
|
|
@@ -2795,26 +3146,26 @@ bw.clientApply = function(msg) {
|
|
|
2795
3146
|
bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
|
|
2796
3147
|
return true;
|
|
2797
3148
|
} catch (e) {
|
|
2798
|
-
|
|
3149
|
+
_ce('[bw] register error:', msg.name, e);
|
|
2799
3150
|
return false;
|
|
2800
3151
|
}
|
|
2801
3152
|
|
|
2802
3153
|
} else if (type === 'call') {
|
|
2803
3154
|
if (!msg.name) return false;
|
|
2804
3155
|
var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
|
|
2805
|
-
if (
|
|
3156
|
+
if (!_is(fn, 'function')) return false;
|
|
2806
3157
|
try {
|
|
2807
|
-
var args =
|
|
3158
|
+
var args = _isA(msg.args) ? msg.args : [];
|
|
2808
3159
|
fn.apply(null, args);
|
|
2809
3160
|
return true;
|
|
2810
3161
|
} catch (e) {
|
|
2811
|
-
|
|
3162
|
+
_ce('[bw] call error:', msg.name, e);
|
|
2812
3163
|
return false;
|
|
2813
3164
|
}
|
|
2814
3165
|
|
|
2815
3166
|
} else if (type === 'exec') {
|
|
2816
3167
|
if (!bw._allowExec) {
|
|
2817
|
-
|
|
3168
|
+
_cw('[bw] exec rejected: allowExec is not enabled');
|
|
2818
3169
|
return false;
|
|
2819
3170
|
}
|
|
2820
3171
|
if (!msg.code) return false;
|
|
@@ -2822,7 +3173,7 @@ bw.clientApply = function(msg) {
|
|
|
2822
3173
|
new Function(msg.code)();
|
|
2823
3174
|
return true;
|
|
2824
3175
|
} catch (e) {
|
|
2825
|
-
|
|
3176
|
+
_ce('[bw] exec error:', e);
|
|
2826
3177
|
return false;
|
|
2827
3178
|
}
|
|
2828
3179
|
}
|
|
@@ -2870,7 +3221,7 @@ bw.clientConnect = function(url, opts) {
|
|
|
2870
3221
|
|
|
2871
3222
|
function handleMessage(data) {
|
|
2872
3223
|
try {
|
|
2873
|
-
var msg =
|
|
3224
|
+
var msg = _is(data, 'string') ? bw.clientParse(data) : data;
|
|
2874
3225
|
if (onMessage) onMessage(msg);
|
|
2875
3226
|
if (handlers.message) handlers.message(msg);
|
|
2876
3227
|
bw.clientApply(msg);
|
|
@@ -2908,7 +3259,7 @@ bw.clientConnect = function(url, opts) {
|
|
|
2908
3259
|
setStatus('connected');
|
|
2909
3260
|
conn._pollTimer = setInterval(function() {
|
|
2910
3261
|
fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
|
|
2911
|
-
if (
|
|
3262
|
+
if (_isA(msgs)) {
|
|
2912
3263
|
msgs.forEach(handleMessage);
|
|
2913
3264
|
} else if (msgs && msgs.type) {
|
|
2914
3265
|
handleMessage(msgs);
|
|
@@ -2990,33 +3341,33 @@ bw.inspect = function(target) {
|
|
|
2990
3341
|
el = target.element;
|
|
2991
3342
|
comp = target;
|
|
2992
3343
|
} else {
|
|
2993
|
-
if (
|
|
3344
|
+
if (_is(target, 'string')) {
|
|
2994
3345
|
el = bw.$(target)[0];
|
|
2995
3346
|
}
|
|
2996
3347
|
if (!el) {
|
|
2997
|
-
|
|
3348
|
+
_cw('bw.inspect: element not found');
|
|
2998
3349
|
return null;
|
|
2999
3350
|
}
|
|
3000
3351
|
comp = el._bwComponentHandle;
|
|
3001
3352
|
}
|
|
3002
3353
|
if (!comp) {
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3354
|
+
_cl('bw.inspect: no ComponentHandle on this element');
|
|
3355
|
+
_cl(' Tag:', el.tagName);
|
|
3356
|
+
_cl(' Classes:', el.className);
|
|
3357
|
+
_cl(' _bw_state:', el._bw_state || '(none)');
|
|
3007
3358
|
return null;
|
|
3008
3359
|
}
|
|
3009
3360
|
var deps = comp._bindings.reduce(function(s, b) {
|
|
3010
3361
|
return s.concat(b.deps || []);
|
|
3011
3362
|
}, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
|
|
3012
3363
|
console.group('Component: ' + comp._bwId);
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3364
|
+
_cl('State:', comp._state);
|
|
3365
|
+
_cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
|
|
3366
|
+
_cl('Methods:', _keys(comp._methods));
|
|
3367
|
+
_cl('Actions:', _keys(comp._actions));
|
|
3368
|
+
_cl('User tag:', comp._userTag || '(none)');
|
|
3369
|
+
_cl('Mounted:', comp.mounted);
|
|
3370
|
+
_cl('Element:', comp.element);
|
|
3020
3371
|
console.groupEnd();
|
|
3021
3372
|
return comp;
|
|
3022
3373
|
};
|
|
@@ -3039,8 +3390,8 @@ bw.compile = function(taco) {
|
|
|
3039
3390
|
// Pre-extract all binding expressions
|
|
3040
3391
|
var precompiled = [];
|
|
3041
3392
|
function walkExpressions(node) {
|
|
3042
|
-
if (!node
|
|
3043
|
-
if (
|
|
3393
|
+
if (!_is(node, 'object')) return;
|
|
3394
|
+
if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
|
|
3044
3395
|
var parsed = bw._parseBindings(node.c);
|
|
3045
3396
|
for (var i = 0; i < parsed.length; i++) {
|
|
3046
3397
|
try {
|
|
@@ -3055,9 +3406,9 @@ bw.compile = function(taco) {
|
|
|
3055
3406
|
}
|
|
3056
3407
|
if (node.a) {
|
|
3057
3408
|
for (var key in node.a) {
|
|
3058
|
-
if (
|
|
3409
|
+
if (_hop.call(node.a, key)) {
|
|
3059
3410
|
var v = node.a[key];
|
|
3060
|
-
if (
|
|
3411
|
+
if (_is(v, 'string') && v.indexOf('${') >= 0) {
|
|
3061
3412
|
var parsed2 = bw._parseBindings(v);
|
|
3062
3413
|
for (var j = 0; j < parsed2.length; j++) {
|
|
3063
3414
|
try {
|
|
@@ -3073,9 +3424,9 @@ bw.compile = function(taco) {
|
|
|
3073
3424
|
}
|
|
3074
3425
|
}
|
|
3075
3426
|
}
|
|
3076
|
-
if (
|
|
3427
|
+
if (_isA(node.c)) {
|
|
3077
3428
|
for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
|
|
3078
|
-
} else if (node.c
|
|
3429
|
+
} else if (_is(node.c, 'object') && node.c.t) {
|
|
3079
3430
|
walkExpressions(node.c);
|
|
3080
3431
|
}
|
|
3081
3432
|
}
|
|
@@ -3087,7 +3438,7 @@ bw.compile = function(taco) {
|
|
|
3087
3438
|
handle._precompiledBindings = precompiled;
|
|
3088
3439
|
if (initialState) {
|
|
3089
3440
|
for (var k in initialState) {
|
|
3090
|
-
if (
|
|
3441
|
+
if (_hop.call(initialState, k)) {
|
|
3091
3442
|
handle._state[k] = initialState[k];
|
|
3092
3443
|
}
|
|
3093
3444
|
}
|
|
@@ -3118,18 +3469,18 @@ bw.compile = function(taco) {
|
|
|
3118
3469
|
bw.css = function(rules, options = {}) {
|
|
3119
3470
|
const { minify = false, pretty = !minify } = options;
|
|
3120
3471
|
|
|
3121
|
-
if (
|
|
3472
|
+
if (_is(rules, 'string')) return rules;
|
|
3122
3473
|
|
|
3123
3474
|
let css = '';
|
|
3124
3475
|
const indent = pretty ? ' ' : '';
|
|
3125
3476
|
const newline = pretty ? '\n' : '';
|
|
3126
3477
|
const space = pretty ? ' ' : '';
|
|
3127
3478
|
|
|
3128
|
-
if (
|
|
3479
|
+
if (_isA(rules)) {
|
|
3129
3480
|
css = rules.map(rule => bw.css(rule, options)).join(newline);
|
|
3130
|
-
} else if (
|
|
3481
|
+
} else if (_is(rules, 'object')) {
|
|
3131
3482
|
Object.entries(rules).forEach(([selector, styles]) => {
|
|
3132
|
-
if (
|
|
3483
|
+
if (_is(styles, 'object')) {
|
|
3133
3484
|
// Handle @media, @keyframes, @supports — recurse into nested block
|
|
3134
3485
|
if (selector.charAt(0) === '@') {
|
|
3135
3486
|
const inner = bw.css(styles, options);
|
|
@@ -3178,7 +3529,7 @@ bw.css = function(rules, options = {}) {
|
|
|
3178
3529
|
*/
|
|
3179
3530
|
bw.injectCSS = function(css, options = {}) {
|
|
3180
3531
|
if (!bw._isBrowser) {
|
|
3181
|
-
|
|
3532
|
+
_cw('bw.injectCSS requires a DOM environment');
|
|
3182
3533
|
return null;
|
|
3183
3534
|
}
|
|
3184
3535
|
|
|
@@ -3195,7 +3546,7 @@ bw.injectCSS = function(css, options = {}) {
|
|
|
3195
3546
|
}
|
|
3196
3547
|
|
|
3197
3548
|
// Convert CSS if needed
|
|
3198
|
-
const cssStr =
|
|
3549
|
+
const cssStr = _is(css, 'string') ? css : bw.css(css, options);
|
|
3199
3550
|
|
|
3200
3551
|
// Set or append CSS
|
|
3201
3552
|
if (append && styleEl.textContent) {
|
|
@@ -3225,7 +3576,7 @@ bw.s = function() {
|
|
|
3225
3576
|
var result = {};
|
|
3226
3577
|
for (var i = 0; i < arguments.length; i++) {
|
|
3227
3578
|
var arg = arguments[i];
|
|
3228
|
-
if (arg
|
|
3579
|
+
if (_is(arg, 'object')) Object.assign(result, arg);
|
|
3229
3580
|
}
|
|
3230
3581
|
return result;
|
|
3231
3582
|
};
|
|
@@ -3348,7 +3699,7 @@ bw.u = {
|
|
|
3348
3699
|
bw.responsive = function(selector, breakpoints) {
|
|
3349
3700
|
var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
|
|
3350
3701
|
var parts = [];
|
|
3351
|
-
|
|
3702
|
+
_keys(breakpoints).forEach(function(key) {
|
|
3352
3703
|
var rules = {};
|
|
3353
3704
|
if (key === 'base') {
|
|
3354
3705
|
rules[selector] = breakpoints[key];
|
|
@@ -3420,18 +3771,18 @@ if (bw._isBrowser) {
|
|
|
3420
3771
|
if (!selector) return [];
|
|
3421
3772
|
|
|
3422
3773
|
// Already an array
|
|
3423
|
-
if (
|
|
3774
|
+
if (_isA(selector)) return selector;
|
|
3424
3775
|
|
|
3425
3776
|
// Single element
|
|
3426
3777
|
if (selector.nodeType) return [selector];
|
|
3427
3778
|
|
|
3428
3779
|
// NodeList or HTMLCollection
|
|
3429
|
-
if (selector.length !== undefined &&
|
|
3780
|
+
if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
3430
3781
|
return Array.from(selector);
|
|
3431
3782
|
}
|
|
3432
3783
|
|
|
3433
3784
|
// CSS selector string
|
|
3434
|
-
if (
|
|
3785
|
+
if (_is(selector, 'string')) {
|
|
3435
3786
|
return Array.from(document.querySelectorAll(selector));
|
|
3436
3787
|
}
|
|
3437
3788
|
|
|
@@ -3935,7 +4286,7 @@ bw.makeTable = function(config) {
|
|
|
3935
4286
|
|
|
3936
4287
|
// Auto-detect columns if not provided
|
|
3937
4288
|
const cols = columns || (data.length > 0
|
|
3938
|
-
?
|
|
4289
|
+
? _keys(data[0]).map(key => ({ key, label: key }))
|
|
3939
4290
|
: []);
|
|
3940
4291
|
|
|
3941
4292
|
// Current sort state
|
|
@@ -3950,7 +4301,7 @@ bw.makeTable = function(config) {
|
|
|
3950
4301
|
const bVal = b[currentSortColumn];
|
|
3951
4302
|
|
|
3952
4303
|
// Handle different types
|
|
3953
|
-
if (
|
|
4304
|
+
if (_is(aVal, 'number') && _is(bVal, 'number')) {
|
|
3954
4305
|
return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
3955
4306
|
}
|
|
3956
4307
|
|
|
@@ -4060,7 +4411,7 @@ bw.makeTable = function(config) {
|
|
|
4060
4411
|
bw.makeTableFromArray = function(config) {
|
|
4061
4412
|
const { data = [], headerRow = true, columns, ...rest } = config;
|
|
4062
4413
|
|
|
4063
|
-
if (!
|
|
4414
|
+
if (!_isA(data) || data.length === 0) {
|
|
4064
4415
|
return bw.makeTable({ data: [], columns: columns || [], ...rest });
|
|
4065
4416
|
}
|
|
4066
4417
|
|
|
@@ -4142,7 +4493,7 @@ bw.makeBarChart = function(config) {
|
|
|
4142
4493
|
className = ''
|
|
4143
4494
|
} = config;
|
|
4144
4495
|
|
|
4145
|
-
if (!
|
|
4496
|
+
if (!_isA(data) || data.length === 0) {
|
|
4146
4497
|
return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
|
|
4147
4498
|
}
|
|
4148
4499
|
|
|
@@ -4291,7 +4642,7 @@ bw._componentRegistry = new Map();
|
|
|
4291
4642
|
*/
|
|
4292
4643
|
bw.render = function(element, position, taco) {
|
|
4293
4644
|
// Get target element
|
|
4294
|
-
const targetEl =
|
|
4645
|
+
const targetEl = _is(element, 'string')
|
|
4295
4646
|
? document.querySelector(element)
|
|
4296
4647
|
: element;
|
|
4297
4648
|
|
|
@@ -4441,7 +4792,7 @@ bw.render = function(element, position, taco) {
|
|
|
4441
4792
|
setContent(content) {
|
|
4442
4793
|
this._taco.c = content;
|
|
4443
4794
|
if (this.element) {
|
|
4444
|
-
if (
|
|
4795
|
+
if (_is(content, 'string')) {
|
|
4445
4796
|
this.element.textContent = content;
|
|
4446
4797
|
} else {
|
|
4447
4798
|
// Re-render for complex content
|