bitwrench 2.0.18 → 2.0.20
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 +86 -81
- package/dist/bitwrench-bccl.cjs.js +221 -48
- package/dist/bitwrench-bccl.cjs.min.js +3 -3
- package/dist/bitwrench-bccl.esm.js +221 -48
- package/dist/bitwrench-bccl.esm.min.js +3 -3
- package/dist/bitwrench-bccl.umd.js +221 -48
- package/dist/bitwrench-bccl.umd.min.js +3 -3
- package/dist/bitwrench-code-edit.cjs.js +7 -9
- package/dist/bitwrench-code-edit.cjs.min.js +5 -7
- package/dist/bitwrench-code-edit.es5.js +6 -8
- package/dist/bitwrench-code-edit.es5.min.js +5 -7
- package/dist/bitwrench-code-edit.esm.js +7 -9
- package/dist/bitwrench-code-edit.esm.min.js +5 -7
- package/dist/bitwrench-code-edit.umd.js +7 -9
- package/dist/bitwrench-code-edit.umd.min.js +5 -7
- package/dist/bitwrench-debug.js +268 -0
- package/dist/bitwrench-debug.min.js +3 -0
- package/dist/bitwrench-lean.cjs.js +250 -1574
- package/dist/bitwrench-lean.cjs.min.js +6 -6
- package/dist/bitwrench-lean.es5.js +344 -1661
- package/dist/bitwrench-lean.es5.min.js +4 -4
- package/dist/bitwrench-lean.esm.js +250 -1574
- package/dist/bitwrench-lean.esm.min.js +6 -6
- package/dist/bitwrench-lean.umd.js +250 -1574
- package/dist/bitwrench-lean.umd.min.js +6 -6
- package/dist/bitwrench-util-css.cjs.js +1 -1
- package/dist/bitwrench-util-css.cjs.min.js +1 -1
- package/dist/bitwrench-util-css.es5.js +1 -1
- package/dist/bitwrench-util-css.es5.min.js +1 -1
- package/dist/bitwrench-util-css.esm.js +1 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -1
- package/dist/bitwrench-util-css.umd.js +1 -1
- package/dist/bitwrench-util-css.umd.min.js +1 -1
- package/dist/bitwrench.cjs.js +510 -1660
- package/dist/bitwrench.cjs.min.js +7 -7
- package/dist/bitwrench.css +80 -33
- package/dist/bitwrench.es5.js +569 -1694
- package/dist/bitwrench.es5.min.js +5 -5
- package/dist/bitwrench.esm.js +510 -1660
- package/dist/bitwrench.esm.min.js +7 -7
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +510 -1660
- package/dist/bitwrench.umd.min.js +7 -7
- package/dist/builds.json +133 -111
- package/dist/bwserve.cjs.js +2 -2
- package/dist/bwserve.esm.js +2 -2
- package/dist/sri.json +46 -44
- package/package.json +5 -3
- package/readme.html +86 -75
- package/src/bitwrench-bccl-entry.js +3 -4
- package/src/bitwrench-bccl.js +217 -43
- package/src/bitwrench-code-edit.js +6 -8
- package/src/bitwrench-debug.js +245 -0
- package/src/bitwrench-styles.js +35 -8
- package/src/bitwrench.js +212 -1563
- package/src/cli/attach.js +53 -21
- package/src/cli/serve.js +179 -3
- package/src/version.js +3 -3
package/dist/bitwrench.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bitwrench v2.0.
|
|
1
|
+
/*! bitwrench v2.0.20 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -8,14 +8,14 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const VERSION_INFO = {
|
|
11
|
-
version: '2.0.
|
|
11
|
+
version: '2.0.20',
|
|
12
12
|
name: 'bitwrench',
|
|
13
13
|
description: 'A library for javascript UI functions.',
|
|
14
14
|
license: 'BSD-2-Clause',
|
|
15
15
|
homepage: 'https://deftio.github.com/bitwrench/pages',
|
|
16
16
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
17
17
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
18
|
-
buildDate: '2026-03-
|
|
18
|
+
buildDate: '2026-03-23T05:19:31.951Z'
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -2172,10 +2172,10 @@ var structuralRules = {
|
|
|
2172
2172
|
'position': 'relative', 'display': 'flex', 'flex-direction': 'column', 'pointer-events': 'auto',
|
|
2173
2173
|
'background-clip': 'padding-box', 'border': '1px solid transparent', 'outline': '0'
|
|
2174
2174
|
},
|
|
2175
|
-
'.bw_modal_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between' },
|
|
2175
|
+
'.bw_modal_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '1rem 1.25rem', 'border-bottom': '1px solid transparent' },
|
|
2176
2176
|
'.bw_modal_title': { 'margin': '0', 'font-size': '1.25rem', 'font-weight': '600', 'line-height': '1.3' },
|
|
2177
|
-
'.bw_modal_body': { 'position': 'relative', 'flex': '1 1 auto' },
|
|
2178
|
-
'.bw_modal_footer': { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'flex-end', 'gap': '0.5rem' }
|
|
2177
|
+
'.bw_modal_body': { 'position': 'relative', 'flex': '1 1 auto', 'padding': '1rem 1.25rem' },
|
|
2178
|
+
'.bw_modal_footer': { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'flex-end', 'gap': '0.5rem', 'padding': '0.75rem 1.25rem', 'border-top': '1px solid transparent' }
|
|
2179
2179
|
},
|
|
2180
2180
|
|
|
2181
2181
|
// ---- Toast ----
|
|
@@ -2196,8 +2196,8 @@ var structuralRules = {
|
|
|
2196
2196
|
},
|
|
2197
2197
|
'.bw_toast.bw_toast_show': { 'opacity': '1', 'transform': 'translateY(0)' },
|
|
2198
2198
|
'.bw_toast.bw_toast_hiding': { 'opacity': '0' },
|
|
2199
|
-
'.bw_toast_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'font-size': '0.875rem' },
|
|
2200
|
-
'.bw_toast_body': { 'font-size': '0.9375rem' }
|
|
2199
|
+
'.bw_toast_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '0.5rem 0.75rem', 'font-size': '0.875rem', 'border-bottom': '1px solid transparent' },
|
|
2200
|
+
'.bw_toast_body': { 'padding': '0.5rem 0.75rem', 'font-size': '0.9375rem' }
|
|
2201
2201
|
},
|
|
2202
2202
|
|
|
2203
2203
|
// ---- Dropdown ----
|
|
@@ -2211,15 +2211,15 @@ var structuralRules = {
|
|
|
2211
2211
|
'.bw_dropdown_menu': {
|
|
2212
2212
|
'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': 'block',
|
|
2213
2213
|
'min-width': '10rem', 'padding': '0.5rem 0', 'margin': '0.125rem 0 0',
|
|
2214
|
-
'background-clip': 'padding-box',
|
|
2214
|
+
'background-clip': 'padding-box', 'border': '1px solid transparent',
|
|
2215
2215
|
'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none'
|
|
2216
2216
|
},
|
|
2217
2217
|
'.bw_dropdown_menu.bw_dropdown_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
|
|
2218
2218
|
'.bw_dropdown_menu_end': { 'left': 'auto', 'right': '0' },
|
|
2219
2219
|
'.bw_dropdown_item': {
|
|
2220
|
-
'display': 'block', 'width': '100%', 'clear': 'both',
|
|
2220
|
+
'display': 'block', 'width': '100%', 'padding': '0.4rem 1rem', 'clear': 'both',
|
|
2221
2221
|
'font-weight': '400', 'text-align': 'inherit', 'text-decoration': 'none', 'white-space': 'nowrap',
|
|
2222
|
-
'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem'
|
|
2222
|
+
'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem', 'cursor': 'pointer'
|
|
2223
2223
|
},
|
|
2224
2224
|
'.bw_dropdown_item:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '-2px' },
|
|
2225
2225
|
'.bw_dropdown_divider': { 'height': '0', 'margin': '0.5rem 0', 'overflow': 'hidden', 'opacity': '1' }
|
|
@@ -2541,6 +2541,33 @@ function generateUtilityRules() {
|
|
|
2541
2541
|
rules['.bw_text_left'] = { 'text-align': 'left' };
|
|
2542
2542
|
rules['.bw_text_right'] = { 'text-align': 'right' };
|
|
2543
2543
|
rules['.bw_text_center'] = { 'text-align': 'center' };
|
|
2544
|
+
rules['.bw_text_justify'] = { 'text-align': 'justify' };
|
|
2545
|
+
|
|
2546
|
+
// Font weight
|
|
2547
|
+
rules['.bw_fw_bold'] = { 'font-weight': '700' };
|
|
2548
|
+
rules['.bw_fw_semibold'] = { 'font-weight': '600' };
|
|
2549
|
+
rules['.bw_fw_normal'] = { 'font-weight': '400' };
|
|
2550
|
+
rules['.bw_fw_light'] = { 'font-weight': '300' };
|
|
2551
|
+
|
|
2552
|
+
// Font style
|
|
2553
|
+
rules['.bw_fst_italic'] = { 'font-style': 'italic' };
|
|
2554
|
+
rules['.bw_fst_normal'] = { 'font-style': 'normal' };
|
|
2555
|
+
|
|
2556
|
+
// Text decoration
|
|
2557
|
+
rules['.bw_text_underline'] = { 'text-decoration': 'underline' };
|
|
2558
|
+
rules['.bw_text_line_through'] = { 'text-decoration': 'line-through' };
|
|
2559
|
+
rules['.bw_text_decoration_none'] = { 'text-decoration': 'none' };
|
|
2560
|
+
|
|
2561
|
+
// Text transform
|
|
2562
|
+
rules['.bw_text_uppercase'] = { 'text-transform': 'uppercase' };
|
|
2563
|
+
rules['.bw_text_lowercase'] = { 'text-transform': 'lowercase' };
|
|
2564
|
+
rules['.bw_text_capitalize'] = { 'text-transform': 'capitalize' };
|
|
2565
|
+
|
|
2566
|
+
// Font size
|
|
2567
|
+
rules['.bw_fs_sm'] = { 'font-size': '0.875rem' };
|
|
2568
|
+
rules['.bw_fs_base'] = { 'font-size': '1rem' };
|
|
2569
|
+
rules['.bw_fs_lg'] = { 'font-size': '1.25rem' };
|
|
2570
|
+
rules['.bw_fs_xl'] = { 'font-size': '1.5rem' };
|
|
2544
2571
|
|
|
2545
2572
|
// Flexbox
|
|
2546
2573
|
var jc = { start: 'flex-start', end: 'flex-end', center: 'center', between: 'space-between', around: 'space-around' };
|
|
@@ -3607,7 +3634,12 @@ function makeCard(props = {}) {
|
|
|
3607
3634
|
},
|
|
3608
3635
|
o: {
|
|
3609
3636
|
type: 'card',
|
|
3610
|
-
state: props.state || {}
|
|
3637
|
+
state: props.state || {},
|
|
3638
|
+
slots: {
|
|
3639
|
+
title: '.bw_card_title',
|
|
3640
|
+
content: '.bw_card_body',
|
|
3641
|
+
footer: '.bw_card_footer'
|
|
3642
|
+
}
|
|
3611
3643
|
}
|
|
3612
3644
|
};
|
|
3613
3645
|
}
|
|
@@ -3618,7 +3650,12 @@ function makeCard(props = {}) {
|
|
|
3618
3650
|
c: cardContent,
|
|
3619
3651
|
o: {
|
|
3620
3652
|
type: 'card',
|
|
3621
|
-
state: props.state || {}
|
|
3653
|
+
state: props.state || {},
|
|
3654
|
+
slots: {
|
|
3655
|
+
title: '.bw_card_title',
|
|
3656
|
+
content: '.bw_card_body',
|
|
3657
|
+
footer: '.bw_card_footer'
|
|
3658
|
+
}
|
|
3622
3659
|
}
|
|
3623
3660
|
};
|
|
3624
3661
|
}
|
|
@@ -3936,6 +3973,24 @@ function makeTabs(props = {}) {
|
|
|
3936
3973
|
}
|
|
3937
3974
|
});
|
|
3938
3975
|
|
|
3976
|
+
// Shared tab switching logic
|
|
3977
|
+
function switchTab(el, index) {
|
|
3978
|
+
var allTabs = el.querySelectorAll('.bw_nav_link');
|
|
3979
|
+
var allPanes = el.querySelectorAll('.bw_tab_pane');
|
|
3980
|
+
if (index < 0 || index >= allTabs.length) return;
|
|
3981
|
+
allTabs.forEach(function(t) {
|
|
3982
|
+
t.classList.remove('active');
|
|
3983
|
+
t.setAttribute('aria-selected', 'false');
|
|
3984
|
+
t.setAttribute('tabindex', '-1');
|
|
3985
|
+
});
|
|
3986
|
+
allPanes.forEach(function(p) { p.classList.remove('active'); });
|
|
3987
|
+
allTabs[index].classList.add('active');
|
|
3988
|
+
allTabs[index].setAttribute('aria-selected', 'true');
|
|
3989
|
+
allTabs[index].setAttribute('tabindex', '0');
|
|
3990
|
+
allPanes[index].classList.add('active');
|
|
3991
|
+
if (el._bw_state) el._bw_state.activeIndex = index;
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3939
3994
|
return {
|
|
3940
3995
|
t: 'div',
|
|
3941
3996
|
a: { class: 'bw_tabs' },
|
|
@@ -3954,24 +4009,8 @@ function makeTabs(props = {}) {
|
|
|
3954
4009
|
role: 'tab',
|
|
3955
4010
|
tabindex: index === actualActiveIndex ? '0' : '-1',
|
|
3956
4011
|
'aria-selected': index === actualActiveIndex ? 'true' : 'false',
|
|
3957
|
-
'data-tab-index': index,
|
|
3958
4012
|
onclick: (e) => {
|
|
3959
|
-
|
|
3960
|
-
const allTabs = tabsContainer.querySelectorAll('.bw_nav_link');
|
|
3961
|
-
const allPanes = tabsContainer.querySelectorAll('.bw_tab_pane');
|
|
3962
|
-
|
|
3963
|
-
allTabs.forEach(t => {
|
|
3964
|
-
t.classList.remove('active');
|
|
3965
|
-
t.setAttribute('aria-selected', 'false');
|
|
3966
|
-
t.setAttribute('tabindex', '-1');
|
|
3967
|
-
});
|
|
3968
|
-
allPanes.forEach(p => p.classList.remove('active'));
|
|
3969
|
-
|
|
3970
|
-
e.target.classList.add('active');
|
|
3971
|
-
e.target.setAttribute('aria-selected', 'true');
|
|
3972
|
-
e.target.setAttribute('tabindex', '0');
|
|
3973
|
-
const targetIndex = parseInt(e.target.getAttribute('data-tab-index'));
|
|
3974
|
-
allPanes[targetIndex].classList.add('active');
|
|
4013
|
+
switchTab(e.target.closest('.bw_tabs'), index);
|
|
3975
4014
|
}
|
|
3976
4015
|
},
|
|
3977
4016
|
c: tab.label
|
|
@@ -3994,6 +4033,10 @@ function makeTabs(props = {}) {
|
|
|
3994
4033
|
o: {
|
|
3995
4034
|
type: 'tabs',
|
|
3996
4035
|
state: { activeIndex: actualActiveIndex },
|
|
4036
|
+
handle: {
|
|
4037
|
+
setActiveTab: switchTab,
|
|
4038
|
+
getActiveTab: function(el) { return (el._bw_state && el._bw_state.activeIndex) || 0; }
|
|
4039
|
+
},
|
|
3997
4040
|
mounted: function(el) {
|
|
3998
4041
|
var tablist = el.querySelector('[role="tablist"]');
|
|
3999
4042
|
if (!tablist) return;
|
|
@@ -4079,7 +4122,13 @@ function makeAlert(props = {}) {
|
|
|
4079
4122
|
},
|
|
4080
4123
|
c: '×'
|
|
4081
4124
|
}
|
|
4082
|
-
].filter(Boolean)
|
|
4125
|
+
].filter(Boolean),
|
|
4126
|
+
o: {
|
|
4127
|
+
type: 'alert',
|
|
4128
|
+
handle: {
|
|
4129
|
+
dismiss: function(el) { if (el && el.parentNode) el.parentNode.removeChild(el); }
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4083
4132
|
};
|
|
4084
4133
|
}
|
|
4085
4134
|
|
|
@@ -4177,6 +4226,24 @@ function makeProgress(props = {}) {
|
|
|
4177
4226
|
'aria-valuemax': max
|
|
4178
4227
|
},
|
|
4179
4228
|
c: label || `${percentage}%`
|
|
4229
|
+
},
|
|
4230
|
+
o: {
|
|
4231
|
+
type: 'progress',
|
|
4232
|
+
handle: {
|
|
4233
|
+
setValue: function(el, n) {
|
|
4234
|
+
var bar = el.querySelector('.bw_progress_bar');
|
|
4235
|
+
if (!bar) return;
|
|
4236
|
+
var maxVal = parseInt(bar.getAttribute('aria-valuemax')) || 100;
|
|
4237
|
+
var pct = Math.round((n / maxVal) * 100);
|
|
4238
|
+
bar.style.width = pct + '%';
|
|
4239
|
+
bar.setAttribute('aria-valuenow', n);
|
|
4240
|
+
bar.textContent = pct + '%';
|
|
4241
|
+
},
|
|
4242
|
+
getValue: function(el) {
|
|
4243
|
+
var bar = el.querySelector('.bw_progress_bar');
|
|
4244
|
+
return bar ? parseInt(bar.getAttribute('aria-valuenow')) || 0 : 0;
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4180
4247
|
}
|
|
4181
4248
|
};
|
|
4182
4249
|
}
|
|
@@ -5186,6 +5253,26 @@ function makePagination(props = {}) {
|
|
|
5186
5253
|
class: `bw_pagination ${size ? 'bw_pagination_' + size : ''} ${className}`.trim()
|
|
5187
5254
|
},
|
|
5188
5255
|
c: items
|
|
5256
|
+
},
|
|
5257
|
+
o: {
|
|
5258
|
+
type: 'pagination',
|
|
5259
|
+
state: { currentPage: currentPage, pages: pages },
|
|
5260
|
+
handle: {
|
|
5261
|
+
setPage: function(el, n) {
|
|
5262
|
+
if (n < 1 || n > pages) return;
|
|
5263
|
+
var allItems = el.querySelectorAll('.bw_page_item');
|
|
5264
|
+
for (var i = 0; i < allItems.length; i++) {
|
|
5265
|
+
allItems[i].classList.remove('bw_active');
|
|
5266
|
+
}
|
|
5267
|
+
// +1 offset: first item is prev arrow
|
|
5268
|
+
if (allItems[n]) allItems[n].classList.add('bw_active');
|
|
5269
|
+
if (el._bw_state) el._bw_state.currentPage = n;
|
|
5270
|
+
if (onPageChange) onPageChange(n);
|
|
5271
|
+
},
|
|
5272
|
+
getPage: function(el) {
|
|
5273
|
+
return (el._bw_state && el._bw_state.currentPage) || 1;
|
|
5274
|
+
}
|
|
5275
|
+
}
|
|
5189
5276
|
}
|
|
5190
5277
|
};
|
|
5191
5278
|
}
|
|
@@ -5334,7 +5421,6 @@ function makeAccordion(props = {}) {
|
|
|
5334
5421
|
class: `bw_accordion_button ${item.open ? '' : 'bw_collapsed'}`.trim(),
|
|
5335
5422
|
type: 'button',
|
|
5336
5423
|
'aria-expanded': item.open ? 'true' : 'false',
|
|
5337
|
-
'data-accordion-index': index,
|
|
5338
5424
|
onclick: function(e) {
|
|
5339
5425
|
var btn = e.target.closest('.bw_accordion_button');
|
|
5340
5426
|
var accordionEl = btn.closest('.bw_accordion');
|
|
@@ -5409,7 +5495,43 @@ function makeAccordion(props = {}) {
|
|
|
5409
5495
|
}),
|
|
5410
5496
|
o: {
|
|
5411
5497
|
type: 'accordion',
|
|
5412
|
-
state: { multiOpen: multiOpen }
|
|
5498
|
+
state: { multiOpen: multiOpen },
|
|
5499
|
+
handle: {
|
|
5500
|
+
toggle: function(el, index) {
|
|
5501
|
+
var items = el.querySelectorAll('.bw_accordion_item');
|
|
5502
|
+
if (index < 0 || index >= items.length) return;
|
|
5503
|
+
var btn = items[index].querySelector('.bw_accordion_button');
|
|
5504
|
+
if (btn) btn.click();
|
|
5505
|
+
},
|
|
5506
|
+
openAll: function(el) {
|
|
5507
|
+
var items = el.querySelectorAll('.bw_accordion_item');
|
|
5508
|
+
for (var i = 0; i < items.length; i++) {
|
|
5509
|
+
var collapse = items[i].querySelector('.bw_accordion_collapse');
|
|
5510
|
+
var btn = items[i].querySelector('.bw_accordion_button');
|
|
5511
|
+
if (!collapse.classList.contains('bw_collapse_show')) {
|
|
5512
|
+
collapse.classList.add('bw_collapse_show');
|
|
5513
|
+
collapse.style.maxHeight = 'none';
|
|
5514
|
+
btn.classList.remove('bw_collapsed');
|
|
5515
|
+
btn.setAttribute('aria-expanded', 'true');
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
},
|
|
5519
|
+
closeAll: function(el) {
|
|
5520
|
+
var items = el.querySelectorAll('.bw_accordion_item');
|
|
5521
|
+
for (var i = 0; i < items.length; i++) {
|
|
5522
|
+
var collapse = items[i].querySelector('.bw_accordion_collapse');
|
|
5523
|
+
var btn = items[i].querySelector('.bw_accordion_button');
|
|
5524
|
+
if (collapse.classList.contains('bw_collapse_show')) {
|
|
5525
|
+
collapse.style.maxHeight = collapse.scrollHeight + 'px';
|
|
5526
|
+
collapse.offsetHeight;
|
|
5527
|
+
collapse.style.maxHeight = '0px';
|
|
5528
|
+
collapse.classList.remove('bw_collapse_show');
|
|
5529
|
+
btn.classList.add('bw_collapsed');
|
|
5530
|
+
btn.setAttribute('aria-expanded', 'false');
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5413
5535
|
}
|
|
5414
5536
|
};
|
|
5415
5537
|
}
|
|
@@ -5499,6 +5621,14 @@ function makeModal(props = {}) {
|
|
|
5499
5621
|
},
|
|
5500
5622
|
o: {
|
|
5501
5623
|
type: 'modal',
|
|
5624
|
+
handle: {
|
|
5625
|
+
open: function(el) {
|
|
5626
|
+
el.classList.add('bw_modal_show');
|
|
5627
|
+
el.style.display = 'flex';
|
|
5628
|
+
document.body.style.overflow = 'hidden';
|
|
5629
|
+
},
|
|
5630
|
+
close: function(el) { closeModal(el); }
|
|
5631
|
+
},
|
|
5502
5632
|
mounted: function(el) {
|
|
5503
5633
|
// Click backdrop to close
|
|
5504
5634
|
el.addEventListener('click', function(e) {
|
|
@@ -5557,9 +5687,8 @@ function makeToast(props = {}) {
|
|
|
5557
5687
|
return {
|
|
5558
5688
|
t: 'div',
|
|
5559
5689
|
a: {
|
|
5560
|
-
class: `bw_toast ${variantClass(variant)} ${className}`.trim(),
|
|
5561
|
-
role: 'alert'
|
|
5562
|
-
'data-position': position
|
|
5690
|
+
class: `bw_toast ${variantClass(variant)} bw_toast_${position.replace(/-/g, '_')} ${className}`.trim(),
|
|
5691
|
+
role: 'alert'
|
|
5563
5692
|
},
|
|
5564
5693
|
c: [
|
|
5565
5694
|
(title) && {
|
|
@@ -5593,6 +5722,12 @@ function makeToast(props = {}) {
|
|
|
5593
5722
|
].filter(Boolean),
|
|
5594
5723
|
o: {
|
|
5595
5724
|
type: 'toast',
|
|
5725
|
+
handle: {
|
|
5726
|
+
dismiss: function(el) {
|
|
5727
|
+
el.classList.add('bw_toast_hiding');
|
|
5728
|
+
setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 300);
|
|
5729
|
+
}
|
|
5730
|
+
},
|
|
5596
5731
|
mounted: function(el) {
|
|
5597
5732
|
// Trigger show animation
|
|
5598
5733
|
requestAnimationFrame(function() {
|
|
@@ -5950,7 +6085,7 @@ function makeCarousel(props = {}) {
|
|
|
5950
6085
|
var total = carouselEl.querySelectorAll('.bw_carousel_slide').length;
|
|
5951
6086
|
if (index < 0) index = total - 1;
|
|
5952
6087
|
if (index >= total) index = 0;
|
|
5953
|
-
carouselEl.
|
|
6088
|
+
carouselEl._bw_carouselIndex = index;
|
|
5954
6089
|
var track = carouselEl.querySelector('.bw_carousel_track');
|
|
5955
6090
|
track.style.transform = 'translateX(-' + (index * 100) + '%)';
|
|
5956
6091
|
// Update indicators
|
|
@@ -6007,7 +6142,7 @@ function makeCarousel(props = {}) {
|
|
|
6007
6142
|
'aria-label': 'Previous slide',
|
|
6008
6143
|
onclick: function(e) {
|
|
6009
6144
|
var carousel = e.target.closest('.bw_carousel');
|
|
6010
|
-
var idx =
|
|
6145
|
+
var idx = carousel._bw_carouselIndex || 0;
|
|
6011
6146
|
goToSlide(carousel, idx - 1);
|
|
6012
6147
|
}
|
|
6013
6148
|
},
|
|
@@ -6021,7 +6156,7 @@ function makeCarousel(props = {}) {
|
|
|
6021
6156
|
'aria-label': 'Next slide',
|
|
6022
6157
|
onclick: function(e) {
|
|
6023
6158
|
var carousel = e.target.closest('.bw_carousel');
|
|
6024
|
-
var idx =
|
|
6159
|
+
var idx = carousel._bw_carouselIndex || 0;
|
|
6025
6160
|
goToSlide(carousel, idx + 1);
|
|
6026
6161
|
}
|
|
6027
6162
|
},
|
|
@@ -6041,11 +6176,9 @@ function makeCarousel(props = {}) {
|
|
|
6041
6176
|
class: 'bw_carousel_indicator' + (i === startIndex ? ' active' : ''),
|
|
6042
6177
|
type: 'button',
|
|
6043
6178
|
'aria-label': 'Go to slide ' + (i + 1),
|
|
6044
|
-
'data-slide-index': i,
|
|
6045
6179
|
onclick: function(e) {
|
|
6046
6180
|
var carousel = e.target.closest('.bw_carousel');
|
|
6047
|
-
|
|
6048
|
-
goToSlide(carousel, idx);
|
|
6181
|
+
goToSlide(carousel, i);
|
|
6049
6182
|
}
|
|
6050
6183
|
}
|
|
6051
6184
|
};
|
|
@@ -6059,17 +6192,37 @@ function makeCarousel(props = {}) {
|
|
|
6059
6192
|
class: ('bw_carousel ' + className).trim(),
|
|
6060
6193
|
style: 'height: ' + height,
|
|
6061
6194
|
tabindex: '0',
|
|
6062
|
-
'aria-roledescription': 'carousel'
|
|
6063
|
-
'data-carousel-index': startIndex
|
|
6195
|
+
'aria-roledescription': 'carousel'
|
|
6064
6196
|
},
|
|
6065
6197
|
c: children,
|
|
6066
6198
|
o: {
|
|
6067
6199
|
type: 'carousel',
|
|
6068
6200
|
state: { activeIndex: startIndex, autoPlay: autoPlay, interval: interval },
|
|
6201
|
+
handle: {
|
|
6202
|
+
goToSlide: function(el, index) { goToSlide(el, index); },
|
|
6203
|
+
next: function(el) { goToSlide(el, (el._bw_carouselIndex || 0) + 1); },
|
|
6204
|
+
prev: function(el) { goToSlide(el, (el._bw_carouselIndex || 0) - 1); },
|
|
6205
|
+
getActiveIndex: function(el) { return el._bw_carouselIndex || 0; },
|
|
6206
|
+
pause: function(el) {
|
|
6207
|
+
if (el._bw_carouselInterval) {
|
|
6208
|
+
clearInterval(el._bw_carouselInterval);
|
|
6209
|
+
el._bw_carouselInterval = null;
|
|
6210
|
+
}
|
|
6211
|
+
},
|
|
6212
|
+
play: function(el) {
|
|
6213
|
+
if (!el._bw_carouselInterval && el._bw_state) {
|
|
6214
|
+
var ms = el._bw_state.interval || 5000;
|
|
6215
|
+
el._bw_carouselInterval = setInterval(function() {
|
|
6216
|
+
goToSlide(el, (el._bw_carouselIndex || 0) + 1);
|
|
6217
|
+
}, ms);
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
},
|
|
6069
6221
|
mounted: function(el) {
|
|
6222
|
+
el._bw_carouselIndex = startIndex;
|
|
6070
6223
|
// Keyboard navigation
|
|
6071
6224
|
el.addEventListener('keydown', function(e) {
|
|
6072
|
-
var idx =
|
|
6225
|
+
var idx = el._bw_carouselIndex || 0;
|
|
6073
6226
|
if (e.key === 'ArrowLeft') {
|
|
6074
6227
|
e.preventDefault();
|
|
6075
6228
|
goToSlide(el, idx - 1);
|
|
@@ -6081,7 +6234,7 @@ function makeCarousel(props = {}) {
|
|
|
6081
6234
|
// Auto-play
|
|
6082
6235
|
if (autoPlay) {
|
|
6083
6236
|
var intervalId = setInterval(function() {
|
|
6084
|
-
var idx =
|
|
6237
|
+
var idx = el._bw_carouselIndex || 0;
|
|
6085
6238
|
goToSlide(el, idx + 1);
|
|
6086
6239
|
}, interval);
|
|
6087
6240
|
el._bw_carouselInterval = intervalId;
|
|
@@ -6091,7 +6244,7 @@ function makeCarousel(props = {}) {
|
|
|
6091
6244
|
});
|
|
6092
6245
|
el.addEventListener('mouseleave', function() {
|
|
6093
6246
|
el._bw_carouselInterval = setInterval(function() {
|
|
6094
|
-
var idx =
|
|
6247
|
+
var idx = el._bw_carouselIndex || 0;
|
|
6095
6248
|
goToSlide(el, idx + 1);
|
|
6096
6249
|
}, interval);
|
|
6097
6250
|
});
|
|
@@ -6207,7 +6360,13 @@ function makeStatCard(props = {}) {
|
|
|
6207
6360
|
t: 'div',
|
|
6208
6361
|
a: { class: classes, style: style },
|
|
6209
6362
|
c: children,
|
|
6210
|
-
o: {
|
|
6363
|
+
o: {
|
|
6364
|
+
type: 'stat-card',
|
|
6365
|
+
slots: {
|
|
6366
|
+
value: '.bw_stat_value',
|
|
6367
|
+
label: '.bw_stat_label'
|
|
6368
|
+
}
|
|
6369
|
+
}
|
|
6211
6370
|
};
|
|
6212
6371
|
}
|
|
6213
6372
|
|
|
@@ -6886,7 +7045,7 @@ function makeChipInput(props = {}) {
|
|
|
6886
7045
|
function makeChipEl(text) {
|
|
6887
7046
|
return {
|
|
6888
7047
|
t: 'span',
|
|
6889
|
-
a: { class: 'bw_chip'
|
|
7048
|
+
a: { class: 'bw_chip' },
|
|
6890
7049
|
c: [
|
|
6891
7050
|
text,
|
|
6892
7051
|
{
|
|
@@ -6897,9 +7056,8 @@ function makeChipInput(props = {}) {
|
|
|
6897
7056
|
'aria-label': 'Remove ' + text,
|
|
6898
7057
|
onclick: function(e) {
|
|
6899
7058
|
var chip = e.target.closest('.bw_chip');
|
|
6900
|
-
var val = chip.getAttribute('data-chip-value');
|
|
6901
7059
|
chip.parentNode.removeChild(chip);
|
|
6902
|
-
if (onRemove) onRemove(
|
|
7060
|
+
if (onRemove) onRemove(text);
|
|
6903
7061
|
}
|
|
6904
7062
|
},
|
|
6905
7063
|
c: '\u00D7'
|
|
@@ -6927,7 +7085,7 @@ function makeChipInput(props = {}) {
|
|
|
6927
7085
|
// Insert chip before the input
|
|
6928
7086
|
var chipEl = document.createElement('span');
|
|
6929
7087
|
chipEl.className = 'bw_chip';
|
|
6930
|
-
chipEl.
|
|
7088
|
+
chipEl._bw_chipValue = val;
|
|
6931
7089
|
chipEl.innerHTML = '';
|
|
6932
7090
|
chipEl.textContent = val;
|
|
6933
7091
|
var removeBtn = document.createElement('button');
|
|
@@ -6950,7 +7108,7 @@ function makeChipInput(props = {}) {
|
|
|
6950
7108
|
var chipEls = wrapper.querySelectorAll('.bw_chip');
|
|
6951
7109
|
if (chipEls.length) {
|
|
6952
7110
|
var last = chipEls[chipEls.length - 1];
|
|
6953
|
-
var removedVal = last.
|
|
7111
|
+
var removedVal = last._bw_chipValue || last.firstChild.textContent;
|
|
6954
7112
|
last.parentNode.removeChild(last);
|
|
6955
7113
|
if (onRemove) onRemove(removedVal);
|
|
6956
7114
|
}
|
|
@@ -6959,7 +7117,50 @@ function makeChipInput(props = {}) {
|
|
|
6959
7117
|
}
|
|
6960
7118
|
}
|
|
6961
7119
|
],
|
|
6962
|
-
o: {
|
|
7120
|
+
o: {
|
|
7121
|
+
type: 'chip-input',
|
|
7122
|
+
handle: {
|
|
7123
|
+
addChip: function(el, text) {
|
|
7124
|
+
if (!text) return;
|
|
7125
|
+
var input = el.querySelector('.bw_chip_field');
|
|
7126
|
+
var chipEl = document.createElement('span');
|
|
7127
|
+
chipEl.className = 'bw_chip';
|
|
7128
|
+
chipEl._bw_chipValue = text;
|
|
7129
|
+
chipEl.textContent = text;
|
|
7130
|
+
var removeBtn = document.createElement('button');
|
|
7131
|
+
removeBtn.type = 'button';
|
|
7132
|
+
removeBtn.className = 'bw_chip_remove';
|
|
7133
|
+
removeBtn.setAttribute('aria-label', 'Remove ' + text);
|
|
7134
|
+
removeBtn.textContent = '\u00D7';
|
|
7135
|
+
removeBtn.onclick = function() { chipEl.parentNode.removeChild(chipEl); };
|
|
7136
|
+
chipEl.appendChild(removeBtn);
|
|
7137
|
+
el.insertBefore(chipEl, input);
|
|
7138
|
+
},
|
|
7139
|
+
removeChip: function(el, text) {
|
|
7140
|
+
var chips = el.querySelectorAll('.bw_chip');
|
|
7141
|
+
for (var i = 0; i < chips.length; i++) {
|
|
7142
|
+
if ((chips[i]._bw_chipValue || chips[i].firstChild.textContent) === text) {
|
|
7143
|
+
chips[i].parentNode.removeChild(chips[i]);
|
|
7144
|
+
return;
|
|
7145
|
+
}
|
|
7146
|
+
}
|
|
7147
|
+
},
|
|
7148
|
+
getChips: function(el) {
|
|
7149
|
+
var chips = el.querySelectorAll('.bw_chip');
|
|
7150
|
+
var values = [];
|
|
7151
|
+
for (var i = 0; i < chips.length; i++) {
|
|
7152
|
+
values.push(chips[i]._bw_chipValue || chips[i].firstChild.textContent);
|
|
7153
|
+
}
|
|
7154
|
+
return values;
|
|
7155
|
+
},
|
|
7156
|
+
clear: function(el) {
|
|
7157
|
+
var chips = el.querySelectorAll('.bw_chip');
|
|
7158
|
+
for (var i = chips.length - 1; i >= 0; i--) {
|
|
7159
|
+
chips[i].parentNode.removeChild(chips[i]);
|
|
7160
|
+
}
|
|
7161
|
+
}
|
|
7162
|
+
}
|
|
7163
|
+
}
|
|
6963
7164
|
};
|
|
6964
7165
|
}
|
|
6965
7166
|
|
|
@@ -7146,12 +7347,11 @@ const bw = {
|
|
|
7146
7347
|
_subIdCounter: 0, // monotonic ID for subscriptions
|
|
7147
7348
|
|
|
7148
7349
|
// ── Node reference cache ──────────────────────────────────────────────
|
|
7149
|
-
// Fast O(1) lookup for elements by
|
|
7350
|
+
// Fast O(1) lookup for elements by id attribute or bw_uuid_* class.
|
|
7150
7351
|
//
|
|
7151
7352
|
// Populated by bw.createDOM() when elements have:
|
|
7152
|
-
// - data-bw_id attribute (user-declared addressable elements)
|
|
7153
7353
|
// - id attribute (standard HTML id)
|
|
7154
|
-
// -
|
|
7354
|
+
// - bw_uuid_* class (lifecycle-managed or explicitly addressed elements)
|
|
7155
7355
|
//
|
|
7156
7356
|
// Cleaned up by bw.cleanup() when elements are destroyed via bitwrench APIs.
|
|
7157
7357
|
// On cache miss, falls back to querySelector/getElementById — never fails,
|
|
@@ -7159,7 +7359,7 @@ const bw = {
|
|
|
7159
7359
|
// via parentNode === null check (IE11-safe, unlike el.isConnected).
|
|
7160
7360
|
//
|
|
7161
7361
|
// Elements created via bw.createDOM() also get el._bw_refs — a local map of
|
|
7162
|
-
// child
|
|
7362
|
+
// child id/UUID -> DOM node ref for fast parent->child access in o.render.
|
|
7163
7363
|
// This is the bitwrench equivalent of React's compiled template "holes".
|
|
7164
7364
|
//
|
|
7165
7365
|
// Contract: if you remove elements outside of bitwrench APIs (raw el.remove()),
|
|
@@ -7239,7 +7439,6 @@ Object.defineProperty(bw, '_isBrowser', {
|
|
|
7239
7439
|
// _cw console.warn 8
|
|
7240
7440
|
// _cl console.log 11
|
|
7241
7441
|
// _ce console.error 4
|
|
7242
|
-
// _chp ComponentHandle.prototype 28 (defined after constructor)
|
|
7243
7442
|
//
|
|
7244
7443
|
// Note: document.createElement etc. are NOT aliased because they require
|
|
7245
7444
|
// `this === document` and .bind() would add overhead on every call.
|
|
@@ -7412,15 +7611,15 @@ bw.uuid = function(prefix) {
|
|
|
7412
7611
|
* 1. Check `bw._nodeMap[id]` — if found and still attached (parentNode !== null), return it
|
|
7413
7612
|
* 2. If cached ref is detached (parentNode === null), remove stale entry
|
|
7414
7613
|
* 3. Fall back to `document.getElementById(id)` then `document.querySelector(...)`
|
|
7415
|
-
* 4.
|
|
7416
|
-
* 5.
|
|
7614
|
+
* 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
7615
|
+
* 5. Cache the result for next time
|
|
7417
7616
|
*
|
|
7418
7617
|
* Accepts a DOM element directly (pass-through) or a string identifier.
|
|
7419
7618
|
* String identifiers are tried as: direct map key, getElementById,
|
|
7420
7619
|
* querySelector (for CSS selectors starting with . or #), and
|
|
7421
|
-
*
|
|
7620
|
+
* bw_uuid_* class selector.
|
|
7422
7621
|
*
|
|
7423
|
-
* @param {string|Element} id - Element ID, CSS selector,
|
|
7622
|
+
* @param {string|Element} id - Element ID, CSS selector, bw_uuid_* class, or DOM element
|
|
7424
7623
|
* @returns {Element|null} The DOM element, or null if not found
|
|
7425
7624
|
* @category Internal
|
|
7426
7625
|
*/
|
|
@@ -7449,17 +7648,12 @@ bw._el = function(id) {
|
|
|
7449
7648
|
el = document.querySelector(id);
|
|
7450
7649
|
}
|
|
7451
7650
|
|
|
7452
|
-
// 4. Try
|
|
7453
|
-
if (!el) {
|
|
7454
|
-
el = document.querySelector('[data-bw_id="' + id + '"]');
|
|
7455
|
-
}
|
|
7456
|
-
|
|
7457
|
-
// 5. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
7651
|
+
// 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
7458
7652
|
if (!el && id.indexOf('bw_uuid_') === 0) {
|
|
7459
7653
|
el = document.querySelector('.' + id);
|
|
7460
7654
|
}
|
|
7461
7655
|
|
|
7462
|
-
//
|
|
7656
|
+
// 5. Cache the result for next time
|
|
7463
7657
|
if (el) {
|
|
7464
7658
|
bw._nodeMap[id] = el;
|
|
7465
7659
|
}
|
|
@@ -7471,17 +7665,17 @@ bw._el = function(id) {
|
|
|
7471
7665
|
* Register a DOM element in the node cache under one or more keys.
|
|
7472
7666
|
*
|
|
7473
7667
|
* Called internally by `bw.createDOM()`. Registers elements that have
|
|
7474
|
-
* id attributes,
|
|
7668
|
+
* id attributes, UUID classes, or both.
|
|
7475
7669
|
*
|
|
7476
7670
|
* @param {Element} el - DOM element to register
|
|
7477
|
-
* @param {string} [
|
|
7671
|
+
* @param {string} [uuid] - bw_uuid_* class token to register under
|
|
7478
7672
|
* @category Internal
|
|
7479
7673
|
*/
|
|
7480
|
-
bw._registerNode = function(el,
|
|
7674
|
+
bw._registerNode = function(el, uuid) {
|
|
7481
7675
|
if (!el) return;
|
|
7482
|
-
// Register under
|
|
7483
|
-
if (
|
|
7484
|
-
bw._nodeMap[
|
|
7676
|
+
// Register under UUID class token
|
|
7677
|
+
if (uuid) {
|
|
7678
|
+
bw._nodeMap[uuid] = el;
|
|
7485
7679
|
}
|
|
7486
7680
|
// Register under id attribute
|
|
7487
7681
|
var htmlId = el.getAttribute ? el.getAttribute('id') : null;
|
|
@@ -7497,13 +7691,13 @@ bw._registerNode = function(el, bwId) {
|
|
|
7497
7691
|
* through bitwrench APIs.
|
|
7498
7692
|
*
|
|
7499
7693
|
* @param {Element} el - DOM element to deregister
|
|
7500
|
-
* @param {string} [
|
|
7694
|
+
* @param {string} [uuid] - bw_uuid_* class token to remove
|
|
7501
7695
|
* @category Internal
|
|
7502
7696
|
*/
|
|
7503
|
-
bw._deregisterNode = function(el,
|
|
7504
|
-
// Remove
|
|
7505
|
-
if (
|
|
7506
|
-
delete bw._nodeMap[
|
|
7697
|
+
bw._deregisterNode = function(el, uuid) {
|
|
7698
|
+
// Remove UUID class entry
|
|
7699
|
+
if (uuid) {
|
|
7700
|
+
delete bw._nodeMap[uuid];
|
|
7507
7701
|
}
|
|
7508
7702
|
// Remove id attribute entry
|
|
7509
7703
|
var htmlId = el && el.getAttribute ? el.getAttribute('id') : null;
|
|
@@ -7516,6 +7710,13 @@ bw._deregisterNode = function(el, bwId) {
|
|
|
7516
7710
|
// bw.assignUUID() / bw.getUUID() — Explicit UUID addressing for TACO objects
|
|
7517
7711
|
// ===================================================================================
|
|
7518
7712
|
|
|
7713
|
+
/**
|
|
7714
|
+
* Marker class for elements with lifecycle hooks (mounted/unmount/render/state).
|
|
7715
|
+
* Used by cleanup() to find lifecycle-managed elements via querySelectorAll('.bw_lc').
|
|
7716
|
+
* @private
|
|
7717
|
+
*/
|
|
7718
|
+
var _BW_LC = 'bw_lc';
|
|
7719
|
+
|
|
7519
7720
|
/**
|
|
7520
7721
|
* Regex to match a bw_uuid_* token in a class string.
|
|
7521
7722
|
* @private
|
|
@@ -7704,15 +7905,6 @@ bw.html = function(taco, options = {}) {
|
|
|
7704
7905
|
// Handle null/undefined
|
|
7705
7906
|
if (taco == null) return '';
|
|
7706
7907
|
|
|
7707
|
-
// Handle ComponentHandle — use its .taco
|
|
7708
|
-
if (taco && taco._bwComponent === true) {
|
|
7709
|
-
var compOptions = Object.assign({}, options);
|
|
7710
|
-
if (!compOptions.state && taco._state) {
|
|
7711
|
-
compOptions.state = taco._state;
|
|
7712
|
-
}
|
|
7713
|
-
return bw.html(taco.taco, compOptions);
|
|
7714
|
-
}
|
|
7715
|
-
|
|
7716
7908
|
// Handle arrays of TACOs
|
|
7717
7909
|
if (_isA(taco)) {
|
|
7718
7910
|
return taco.map(t => bw.html(t, options)).join('');
|
|
@@ -7723,24 +7915,6 @@ bw.html = function(taco, options = {}) {
|
|
|
7723
7915
|
return taco.v;
|
|
7724
7916
|
}
|
|
7725
7917
|
|
|
7726
|
-
// Handle bw.when() markers
|
|
7727
|
-
if (taco && taco._bwWhen && options.state) {
|
|
7728
|
-
var whenExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
7729
|
-
var whenVal = options.compile
|
|
7730
|
-
? bw._resolveTemplate('${' + whenExpr + '}', options.state, true)
|
|
7731
|
-
: bw._evaluatePath(options.state, whenExpr);
|
|
7732
|
-
var branch = whenVal ? taco.branches[0] : (taco.branches[1] || null);
|
|
7733
|
-
return branch ? bw.html(branch, options) : '';
|
|
7734
|
-
}
|
|
7735
|
-
|
|
7736
|
-
// Handle bw.each() markers
|
|
7737
|
-
if (taco && taco._bwEach && options.state) {
|
|
7738
|
-
var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
7739
|
-
var arr = bw._evaluatePath(options.state, eachExpr);
|
|
7740
|
-
if (!_isA(arr)) return '';
|
|
7741
|
-
return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
|
|
7742
|
-
}
|
|
7743
|
-
|
|
7744
7918
|
// Handle primitives and non-TACO objects
|
|
7745
7919
|
if (!_is(taco, 'object') || !taco.t) {
|
|
7746
7920
|
var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
|
|
@@ -7804,14 +7978,14 @@ bw.html = function(taco, options = {}) {
|
|
|
7804
7978
|
}
|
|
7805
7979
|
}
|
|
7806
7980
|
|
|
7807
|
-
// Add
|
|
7808
|
-
if ((opts.mounted || opts.unmount) && !attrs.class
|
|
7809
|
-
const
|
|
7981
|
+
// Add bw_uuid + bw_lc classes if lifecycle hooks present
|
|
7982
|
+
if ((opts.mounted || opts.unmount) && !_UUID_RE.test(attrs.class || '')) {
|
|
7983
|
+
const uuid = bw.uuid('uuid');
|
|
7810
7984
|
attrStr = attrStr.replace(/class="([^"]*)"/, (_match, classes) => {
|
|
7811
|
-
return `class="${classes}
|
|
7985
|
+
return `class="${classes} ${uuid} ${_BW_LC}"`.trim();
|
|
7812
7986
|
});
|
|
7813
7987
|
if (!attrStr.includes('class=')) {
|
|
7814
|
-
attrStr += ` class="
|
|
7988
|
+
attrStr += ` class="${uuid} ${_BW_LC}"`;
|
|
7815
7989
|
}
|
|
7816
7990
|
}
|
|
7817
7991
|
|
|
@@ -8039,11 +8213,6 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8039
8213
|
return frag;
|
|
8040
8214
|
}
|
|
8041
8215
|
|
|
8042
|
-
// Handle ComponentHandle — extract .taco for DOM creation
|
|
8043
|
-
if (taco && taco._bwComponent === true) {
|
|
8044
|
-
return bw.createDOM(taco.taco, options);
|
|
8045
|
-
}
|
|
8046
|
-
|
|
8047
8216
|
// Handle text nodes
|
|
8048
8217
|
if (!_is(taco, 'object') || !taco.t) {
|
|
8049
8218
|
return document.createTextNode(String(taco));
|
|
@@ -8084,24 +8253,19 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8084
8253
|
}
|
|
8085
8254
|
|
|
8086
8255
|
// Add children, building _bw_refs for fast parent→child access.
|
|
8087
|
-
// Children with
|
|
8256
|
+
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
8088
8257
|
// so o.render functions can access them without any DOM lookup.
|
|
8089
8258
|
if (content != null) {
|
|
8090
8259
|
if (_isA(content)) {
|
|
8091
8260
|
content.forEach(child => {
|
|
8092
8261
|
if (child != null) {
|
|
8093
|
-
// Handle ComponentHandle in content arrays (Level 2 children)
|
|
8094
|
-
if (child._bwComponent === true) {
|
|
8095
|
-
child.mount(el);
|
|
8096
|
-
return;
|
|
8097
|
-
}
|
|
8098
8262
|
var childEl = bw.createDOM(child, options);
|
|
8099
8263
|
el.appendChild(childEl);
|
|
8100
8264
|
// Build local refs for addressable children
|
|
8101
|
-
var
|
|
8102
|
-
if (
|
|
8265
|
+
var childRefId = (child && child.a) ? (child.a.id || bw.getUUID(child)) : null;
|
|
8266
|
+
if (childRefId) {
|
|
8103
8267
|
if (!el._bw_refs) el._bw_refs = {};
|
|
8104
|
-
el._bw_refs[
|
|
8268
|
+
el._bw_refs[childRefId] = childEl;
|
|
8105
8269
|
}
|
|
8106
8270
|
// Bubble up grandchild refs (flatten one level)
|
|
8107
8271
|
if (childEl._bw_refs) {
|
|
@@ -8117,16 +8281,13 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8117
8281
|
} else if (_is(content, 'object') && content.__bw_raw) {
|
|
8118
8282
|
// Raw HTML content — inject via innerHTML
|
|
8119
8283
|
el.innerHTML = content.v;
|
|
8120
|
-
} else if (content._bwComponent === true) {
|
|
8121
|
-
// Single ComponentHandle as content
|
|
8122
|
-
content.mount(el);
|
|
8123
8284
|
} else if (_is(content, 'object') && content.t) {
|
|
8124
8285
|
var childEl = bw.createDOM(content, options);
|
|
8125
8286
|
el.appendChild(childEl);
|
|
8126
|
-
var
|
|
8127
|
-
if (
|
|
8287
|
+
var childRefId = content.a ? (content.a.id || bw.getUUID(content)) : null;
|
|
8288
|
+
if (childRefId) {
|
|
8128
8289
|
if (!el._bw_refs) el._bw_refs = {};
|
|
8129
|
-
el._bw_refs[
|
|
8290
|
+
el._bw_refs[childRefId] = childEl;
|
|
8130
8291
|
}
|
|
8131
8292
|
if (childEl._bw_refs) {
|
|
8132
8293
|
if (!el._bw_refs) el._bw_refs = {};
|
|
@@ -8156,57 +8317,88 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
8156
8317
|
|
|
8157
8318
|
// Handle lifecycle hooks and state
|
|
8158
8319
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
8159
|
-
|
|
8160
|
-
el.
|
|
8320
|
+
// Ensure element has a UUID class for identity
|
|
8321
|
+
var uuid = bw.getUUID(el) || bw.uuid('uuid');
|
|
8322
|
+
el.classList.add(uuid);
|
|
8323
|
+
el.classList.add(_BW_LC);
|
|
8161
8324
|
|
|
8162
|
-
// Register in node cache under
|
|
8163
|
-
bw._registerNode(el,
|
|
8325
|
+
// Register in node cache under UUID class
|
|
8326
|
+
bw._registerNode(el, uuid);
|
|
8164
8327
|
|
|
8165
8328
|
// Store state
|
|
8166
8329
|
if (opts.state) {
|
|
8167
8330
|
el._bw_state = opts.state;
|
|
8168
8331
|
}
|
|
8169
8332
|
|
|
8170
|
-
// o.render —
|
|
8333
|
+
// o.render — store the render function for bw.update()
|
|
8171
8334
|
if (opts.render) {
|
|
8172
8335
|
el._bw_render = opts.render;
|
|
8336
|
+
}
|
|
8173
8337
|
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8338
|
+
// Determine what to call on mount:
|
|
8339
|
+
// - If o.mounted exists, call it (it can call el._bw_render() for initial render)
|
|
8340
|
+
// - Otherwise if o.render exists, auto-call it as a convenience shorthand
|
|
8341
|
+
var mountFn = opts.mounted || (opts.render ? function(mountEl) {
|
|
8342
|
+
opts.render(mountEl, mountEl._bw_state || {});
|
|
8343
|
+
} : null);
|
|
8177
8344
|
|
|
8178
|
-
|
|
8179
|
-
if (document.body.contains(el)) {
|
|
8180
|
-
opts.render(el, el._bw_state || {});
|
|
8181
|
-
} else {
|
|
8182
|
-
requestAnimationFrame(() => {
|
|
8183
|
-
if (document.body.contains(el)) {
|
|
8184
|
-
opts.render(el, el._bw_state || {});
|
|
8185
|
-
}
|
|
8186
|
-
});
|
|
8187
|
-
}
|
|
8188
|
-
} else if (opts.mounted) {
|
|
8189
|
-
// Queue mounted callback (legacy pattern)
|
|
8345
|
+
if (mountFn) {
|
|
8190
8346
|
if (document.body.contains(el)) {
|
|
8191
|
-
|
|
8347
|
+
mountFn(el, el._bw_state || {});
|
|
8192
8348
|
} else {
|
|
8193
8349
|
requestAnimationFrame(() => {
|
|
8194
8350
|
if (document.body.contains(el)) {
|
|
8195
|
-
|
|
8351
|
+
mountFn(el, el._bw_state || {});
|
|
8196
8352
|
}
|
|
8197
8353
|
});
|
|
8198
8354
|
}
|
|
8199
8355
|
}
|
|
8200
8356
|
|
|
8201
|
-
// Store unmount callback
|
|
8357
|
+
// Store unmount callback keyed by UUID class
|
|
8202
8358
|
if (opts.unmount) {
|
|
8203
|
-
bw._unmountCallbacks.set(
|
|
8359
|
+
bw._unmountCallbacks.set(uuid, () => {
|
|
8204
8360
|
opts.unmount(el, el._bw_state || {});
|
|
8205
8361
|
});
|
|
8206
8362
|
}
|
|
8207
|
-
}
|
|
8208
|
-
|
|
8209
|
-
|
|
8363
|
+
}
|
|
8364
|
+
|
|
8365
|
+
// Component handle: attach methods to el.bw namespace
|
|
8366
|
+
if (opts.handle || opts.slots) {
|
|
8367
|
+
if (!el.bw) el.bw = {};
|
|
8368
|
+
|
|
8369
|
+
// Explicit handle methods: fn(el, ...args) -> el.bw.method(...args)
|
|
8370
|
+
if (opts.handle) {
|
|
8371
|
+
for (var hk in opts.handle) {
|
|
8372
|
+
if (_hop.call(opts.handle, hk)) {
|
|
8373
|
+
el.bw[hk] = opts.handle[hk].bind(null, el);
|
|
8374
|
+
}
|
|
8375
|
+
}
|
|
8376
|
+
}
|
|
8377
|
+
|
|
8378
|
+
// Slot declarations: auto-generate setX/getX pairs
|
|
8379
|
+
if (opts.slots) {
|
|
8380
|
+
for (var sk in opts.slots) {
|
|
8381
|
+
if (_hop.call(opts.slots, sk)) {
|
|
8382
|
+
(function(name, selector) {
|
|
8383
|
+
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
8384
|
+
el.bw['set' + cap] = function(value) {
|
|
8385
|
+
var t = el.querySelector(selector);
|
|
8386
|
+
if (!t) return;
|
|
8387
|
+
if (value != null && typeof value === 'object' && value.t) {
|
|
8388
|
+
t.innerHTML = '';
|
|
8389
|
+
t.appendChild(bw.createDOM(value));
|
|
8390
|
+
} else {
|
|
8391
|
+
t.textContent = (value != null) ? String(value) : '';
|
|
8392
|
+
}
|
|
8393
|
+
};
|
|
8394
|
+
el.bw['get' + cap] = function() {
|
|
8395
|
+
var t = el.querySelector(selector);
|
|
8396
|
+
return t ? t.textContent : '';
|
|
8397
|
+
};
|
|
8398
|
+
})(sk, opts.slots[sk]);
|
|
8399
|
+
}
|
|
8400
|
+
}
|
|
8401
|
+
}
|
|
8210
8402
|
}
|
|
8211
8403
|
|
|
8212
8404
|
return el;
|
|
@@ -8253,7 +8445,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
8253
8445
|
// the target is the mount point, not the content being replaced)
|
|
8254
8446
|
const savedState = targetEl._bw_state;
|
|
8255
8447
|
const savedRender = targetEl._bw_render;
|
|
8256
|
-
const
|
|
8448
|
+
const savedUuid = bw.getUUID(targetEl);
|
|
8257
8449
|
const savedSubs = targetEl._bw_subs;
|
|
8258
8450
|
|
|
8259
8451
|
// Temporarily remove _bw_subs so cleanup doesn't call them
|
|
@@ -8265,10 +8457,9 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
8265
8457
|
// Restore the target's own state/render/subs after cleanup
|
|
8266
8458
|
if (savedState !== undefined) targetEl._bw_state = savedState;
|
|
8267
8459
|
if (savedRender) targetEl._bw_render = savedRender;
|
|
8268
|
-
if (
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
bw._registerNode(targetEl, savedBwId);
|
|
8460
|
+
if (savedUuid) {
|
|
8461
|
+
// UUID class stays on element through cleanup; re-register in cache
|
|
8462
|
+
bw._registerNode(targetEl, savedUuid);
|
|
8272
8463
|
}
|
|
8273
8464
|
if (savedSubs) targetEl._bw_subs = savedSubs;
|
|
8274
8465
|
|
|
@@ -8276,25 +8467,11 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
8276
8467
|
targetEl.innerHTML = '';
|
|
8277
8468
|
|
|
8278
8469
|
if (taco != null) {
|
|
8279
|
-
// Handle ComponentHandle (reactive components from bw.component())
|
|
8280
|
-
if (taco._bwComponent === true) {
|
|
8281
|
-
taco.mount(targetEl);
|
|
8282
|
-
}
|
|
8283
|
-
// Handle component handles (objects with element property)
|
|
8284
|
-
else if (taco.element instanceof Element) {
|
|
8285
|
-
targetEl.appendChild(taco.element);
|
|
8286
|
-
}
|
|
8287
8470
|
// Handle arrays
|
|
8288
|
-
|
|
8471
|
+
if (_isA(taco)) {
|
|
8289
8472
|
taco.forEach(t => {
|
|
8290
8473
|
if (t != null) {
|
|
8291
|
-
|
|
8292
|
-
t.mount(targetEl);
|
|
8293
|
-
} else if (t.element instanceof Element) {
|
|
8294
|
-
targetEl.appendChild(t.element);
|
|
8295
|
-
} else {
|
|
8296
|
-
targetEl.appendChild(bw.createDOM(t, options));
|
|
8297
|
-
}
|
|
8474
|
+
targetEl.appendChild(bw.createDOM(t, options));
|
|
8298
8475
|
}
|
|
8299
8476
|
});
|
|
8300
8477
|
}
|
|
@@ -8307,205 +8484,36 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
8307
8484
|
return targetEl;
|
|
8308
8485
|
};
|
|
8309
8486
|
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
* Used internally by `bw.renderComponent()`. Creates a proxy-like object
|
|
8314
|
-
* where setting a property triggers `handle.onPropChange()`.
|
|
8315
|
-
*
|
|
8316
|
-
* @param {Object} handle - Component handle
|
|
8317
|
-
* @param {Object} props - Initial props
|
|
8318
|
-
* @returns {Object} Compiled props object with getters/setters
|
|
8319
|
-
* @category DOM Generation
|
|
8320
|
-
*/
|
|
8321
|
-
bw.compileProps = function(handle, props = {}) {
|
|
8322
|
-
const compiledProps = {};
|
|
8323
|
-
|
|
8324
|
-
_keys(props).forEach(key => {
|
|
8325
|
-
// Create getter/setter for each prop
|
|
8326
|
-
Object.defineProperty(compiledProps, key, {
|
|
8327
|
-
get() {
|
|
8328
|
-
return handle._props[key];
|
|
8329
|
-
},
|
|
8330
|
-
set(value) {
|
|
8331
|
-
const oldValue = handle._props[key];
|
|
8332
|
-
if (oldValue !== value) {
|
|
8333
|
-
handle._props[key] = value;
|
|
8334
|
-
// Trigger update if prop changed
|
|
8335
|
-
if (handle.onPropChange) {
|
|
8336
|
-
handle.onPropChange(key, value, oldValue);
|
|
8337
|
-
}
|
|
8338
|
-
}
|
|
8339
|
-
},
|
|
8340
|
-
enumerable: true,
|
|
8341
|
-
configurable: true
|
|
8342
|
-
});
|
|
8343
|
-
});
|
|
8344
|
-
|
|
8345
|
-
return compiledProps;
|
|
8346
|
-
};
|
|
8487
|
+
// Deprecation stubs for removed ComponentHandle APIs
|
|
8488
|
+
bw.compileProps = function() { throw new Error('bw.compileProps() removed in v2.0.19. Use o.handle/o.slots instead.'); };
|
|
8489
|
+
bw.renderComponent = function() { throw new Error('bw.renderComponent() removed in v2.0.19. Use bw.mount() with o.handle/o.slots instead.'); };
|
|
8347
8490
|
|
|
8348
8491
|
/**
|
|
8349
|
-
*
|
|
8350
|
-
*
|
|
8351
|
-
*
|
|
8352
|
-
* and a destroy method. Used internally by `bw.createCard()`, `bw.createTable()`, etc.
|
|
8492
|
+
* Mount a TACO into a target element and return the created root element.
|
|
8493
|
+
* Like bw.DOM() but returns the root element of the TACO (not the container),
|
|
8494
|
+
* giving direct access to el.bw handle methods.
|
|
8353
8495
|
*
|
|
8354
|
-
* @param {
|
|
8355
|
-
* @param {Object}
|
|
8356
|
-
* @
|
|
8496
|
+
* @param {string|Element} target - CSS selector or DOM element
|
|
8497
|
+
* @param {Object} taco - TACO to render
|
|
8498
|
+
* @param {Object} [options] - Mount options
|
|
8499
|
+
* @returns {Element} The created root element
|
|
8357
8500
|
* @category DOM Generation
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
}
|
|
8375
|
-
return this._compiledProps;
|
|
8376
|
-
},
|
|
8377
|
-
|
|
8378
|
-
/**
|
|
8379
|
-
* Query all matching elements within this component
|
|
8380
|
-
* @param {string} selector - CSS selector
|
|
8381
|
-
* @returns {NodeList} Matching elements
|
|
8382
|
-
*/
|
|
8383
|
-
$(selector) {
|
|
8384
|
-
return this.element.querySelectorAll(selector);
|
|
8385
|
-
},
|
|
8386
|
-
|
|
8387
|
-
/**
|
|
8388
|
-
* Query the first matching element within this component
|
|
8389
|
-
* @param {string} selector - CSS selector
|
|
8390
|
-
* @returns {Element|null} First matching element or null
|
|
8391
|
-
*/
|
|
8392
|
-
$first(selector) {
|
|
8393
|
-
return this.element.querySelector(selector);
|
|
8394
|
-
},
|
|
8395
|
-
|
|
8396
|
-
/**
|
|
8397
|
-
* Update component with new props and re-render in place
|
|
8398
|
-
* @param {Object} newProps - Properties to merge into current props
|
|
8399
|
-
* @returns {Object} this handle (for chaining)
|
|
8400
|
-
*/
|
|
8401
|
-
update(newProps) {
|
|
8402
|
-
// Update internal props
|
|
8403
|
-
Object.assign(this._props, newProps);
|
|
8404
|
-
|
|
8405
|
-
// Rebuild TACO with new props
|
|
8406
|
-
const newTaco = { ...this.taco, a: { ...this.taco.a, ...newProps } };
|
|
8407
|
-
const newElement = bw.createDOM(newTaco, options);
|
|
8408
|
-
|
|
8409
|
-
// Replace in DOM
|
|
8410
|
-
this.element.replaceWith(newElement);
|
|
8411
|
-
this.element = newElement;
|
|
8412
|
-
this.taco = newTaco;
|
|
8413
|
-
|
|
8414
|
-
return this;
|
|
8415
|
-
},
|
|
8416
|
-
|
|
8417
|
-
/**
|
|
8418
|
-
* Re-render the component from its current TACO, replacing the DOM element
|
|
8419
|
-
* @returns {Object} this handle (for chaining)
|
|
8420
|
-
*/
|
|
8421
|
-
render() {
|
|
8422
|
-
const newElement = bw.createDOM(this.taco, options);
|
|
8423
|
-
this.element.replaceWith(newElement);
|
|
8424
|
-
this.element = newElement;
|
|
8425
|
-
return this;
|
|
8426
|
-
},
|
|
8427
|
-
|
|
8428
|
-
/**
|
|
8429
|
-
* Called when a compiled prop value changes. Override to customize behavior.
|
|
8430
|
-
* Default implementation triggers a full re-render.
|
|
8431
|
-
* @param {string} key - Property name that changed
|
|
8432
|
-
* @param {*} newValue - New property value
|
|
8433
|
-
* @param {*} oldValue - Previous property value
|
|
8434
|
-
*/
|
|
8435
|
-
onPropChange(_key, _newValue, _oldValue) {
|
|
8436
|
-
// Auto re-render on prop change by default
|
|
8437
|
-
this.render();
|
|
8438
|
-
},
|
|
8439
|
-
|
|
8440
|
-
// State management
|
|
8441
|
-
get state() {
|
|
8442
|
-
return this._state;
|
|
8443
|
-
},
|
|
8444
|
-
|
|
8445
|
-
set state(newState) {
|
|
8446
|
-
this._state = newState;
|
|
8447
|
-
this.render();
|
|
8448
|
-
},
|
|
8449
|
-
|
|
8450
|
-
/**
|
|
8451
|
-
* Merge state updates and re-render the component
|
|
8452
|
-
* @param {Object} updates - State properties to merge
|
|
8453
|
-
* @returns {Object} this handle (for chaining)
|
|
8454
|
-
*/
|
|
8455
|
-
setState(updates) {
|
|
8456
|
-
Object.assign(this._state, updates);
|
|
8457
|
-
this.render();
|
|
8458
|
-
return this;
|
|
8459
|
-
},
|
|
8460
|
-
|
|
8461
|
-
/**
|
|
8462
|
-
* Register a child component under a name for later retrieval
|
|
8463
|
-
* @param {string} name - Child name key
|
|
8464
|
-
* @param {Object} component - Child component handle
|
|
8465
|
-
* @returns {Object} this handle (for chaining)
|
|
8466
|
-
*/
|
|
8467
|
-
addChild(name, component) {
|
|
8468
|
-
this._children[name] = component;
|
|
8469
|
-
return this;
|
|
8470
|
-
},
|
|
8471
|
-
|
|
8472
|
-
/**
|
|
8473
|
-
* Retrieve a registered child component by name
|
|
8474
|
-
* @param {string} name - Child name key
|
|
8475
|
-
* @returns {Object|undefined} Child component handle
|
|
8476
|
-
*/
|
|
8477
|
-
getChild(name) {
|
|
8478
|
-
return this._children[name];
|
|
8479
|
-
},
|
|
8480
|
-
|
|
8481
|
-
/**
|
|
8482
|
-
* Destroy this component and all registered children
|
|
8483
|
-
*
|
|
8484
|
-
* Calls destroy() recursively on children, runs bw.cleanup(),
|
|
8485
|
-
* removes the element from DOM, and clears all internal references.
|
|
8486
|
-
*/
|
|
8487
|
-
destroy() {
|
|
8488
|
-
// Destroy children first
|
|
8489
|
-
Object.values(this._children).forEach(child => {
|
|
8490
|
-
if (child && child.destroy) child.destroy();
|
|
8491
|
-
});
|
|
8492
|
-
|
|
8493
|
-
// Clean up this component
|
|
8494
|
-
bw.cleanup(this.element);
|
|
8495
|
-
this.element.remove();
|
|
8496
|
-
|
|
8497
|
-
// Clear references
|
|
8498
|
-
this._children = {};
|
|
8499
|
-
this._props = {};
|
|
8500
|
-
this._state = {};
|
|
8501
|
-
this._compiledProps = null;
|
|
8502
|
-
}
|
|
8503
|
-
};
|
|
8504
|
-
|
|
8505
|
-
// Store handle reference on element
|
|
8506
|
-
element._bwHandle = handle;
|
|
8507
|
-
|
|
8508
|
-
return handle;
|
|
8501
|
+
* @example
|
|
8502
|
+
* var el = bw.mount('#app', bw.makeCarousel({ items: slides }));
|
|
8503
|
+
* el.bw.goToSlide(2);
|
|
8504
|
+
* el.bw.next();
|
|
8505
|
+
*/
|
|
8506
|
+
bw.mount = function(target, taco, options) {
|
|
8507
|
+
var container = _is(target, 'string') ? bw.$(target)[0] : target;
|
|
8508
|
+
if (!container) {
|
|
8509
|
+
_cw('bw.mount: target not found');
|
|
8510
|
+
return null;
|
|
8511
|
+
}
|
|
8512
|
+
bw.cleanup(container);
|
|
8513
|
+
container.innerHTML = '';
|
|
8514
|
+
var el = bw.createDOM(taco, options || {});
|
|
8515
|
+
container.appendChild(el);
|
|
8516
|
+
return el;
|
|
8509
8517
|
};
|
|
8510
8518
|
|
|
8511
8519
|
/**
|
|
@@ -8526,34 +8534,29 @@ bw.renderComponent = function(taco, options = {}) {
|
|
|
8526
8534
|
bw.cleanup = function(element) {
|
|
8527
8535
|
if (!bw._isBrowser || !element) return;
|
|
8528
8536
|
|
|
8529
|
-
// Deregister UUID classes from node cache
|
|
8530
|
-
// Covers elements that have UUID but no data-bw_id
|
|
8531
|
-
var selfUuidMatch = element.className && element.className.match(_UUID_RE);
|
|
8532
|
-
if (selfUuidMatch) delete bw._nodeMap[selfUuidMatch[0]];
|
|
8537
|
+
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
8533
8538
|
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
8534
8539
|
uuidEls.forEach(function(uel) {
|
|
8535
8540
|
var m = uel.className && uel.className.match(_UUID_RE);
|
|
8536
8541
|
if (m) delete bw._nodeMap[m[0]];
|
|
8537
8542
|
});
|
|
8538
8543
|
|
|
8539
|
-
// Find all elements
|
|
8540
|
-
const elements = element.querySelectorAll('
|
|
8544
|
+
// Find all lifecycle-managed elements (have bw_lc marker class)
|
|
8545
|
+
const elements = element.querySelectorAll('.' + _BW_LC);
|
|
8541
8546
|
|
|
8542
8547
|
elements.forEach(el => {
|
|
8543
|
-
|
|
8544
|
-
const callback = bw._unmountCallbacks.get(id);
|
|
8545
|
-
|
|
8546
|
-
if (callback) {
|
|
8547
|
-
callback();
|
|
8548
|
-
bw._unmountCallbacks.delete(id);
|
|
8549
|
-
}
|
|
8548
|
+
var uuid = bw.getUUID(el);
|
|
8550
8549
|
|
|
8551
|
-
|
|
8552
|
-
|
|
8550
|
+
if (uuid) {
|
|
8551
|
+
const callback = bw._unmountCallbacks.get(uuid);
|
|
8552
|
+
if (callback) {
|
|
8553
|
+
callback();
|
|
8554
|
+
bw._unmountCallbacks.delete(uuid);
|
|
8555
|
+
}
|
|
8553
8556
|
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
+
// Deregister from node cache
|
|
8558
|
+
bw._deregisterNode(el, uuid);
|
|
8559
|
+
}
|
|
8557
8560
|
|
|
8558
8561
|
// Clean up pub/sub subscriptions tied to this element
|
|
8559
8562
|
if (el._bw_subs) {
|
|
@@ -8568,20 +8571,18 @@ bw.cleanup = function(element) {
|
|
|
8568
8571
|
});
|
|
8569
8572
|
|
|
8570
8573
|
// Check element itself
|
|
8571
|
-
|
|
8572
|
-
if (
|
|
8573
|
-
|
|
8574
|
+
var selfUuid = bw.getUUID(element);
|
|
8575
|
+
if (selfUuid) {
|
|
8576
|
+
delete bw._nodeMap[selfUuid];
|
|
8577
|
+
|
|
8578
|
+
const callback = bw._unmountCallbacks.get(selfUuid);
|
|
8574
8579
|
if (callback) {
|
|
8575
8580
|
callback();
|
|
8576
|
-
bw._unmountCallbacks.delete(
|
|
8581
|
+
bw._unmountCallbacks.delete(selfUuid);
|
|
8577
8582
|
}
|
|
8578
8583
|
|
|
8579
8584
|
// Deregister from node cache
|
|
8580
|
-
bw._deregisterNode(element,
|
|
8581
|
-
|
|
8582
|
-
// Deregister UUID class from node cache
|
|
8583
|
-
var elemUuidMatch = element.className && element.className.match(_UUID_RE);
|
|
8584
|
-
if (elemUuidMatch) delete bw._nodeMap[elemUuidMatch[0]];
|
|
8585
|
+
bw._deregisterNode(element, selfUuid);
|
|
8585
8586
|
|
|
8586
8587
|
// Clean up pub/sub subscriptions tied to element itself
|
|
8587
8588
|
if (element._bw_subs) {
|
|
@@ -8592,11 +8593,11 @@ bw.cleanup = function(element) {
|
|
|
8592
8593
|
delete element._bw_render;
|
|
8593
8594
|
delete element._bw_refs;
|
|
8594
8595
|
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
element.
|
|
8599
|
-
delete element.
|
|
8596
|
+
} else {
|
|
8597
|
+
// No UUID on element itself, but still check for _bw_subs (from bw.sub())
|
|
8598
|
+
if (element._bw_subs) {
|
|
8599
|
+
element._bw_subs.forEach(function(unsub) { unsub(); });
|
|
8600
|
+
delete element._bw_subs;
|
|
8600
8601
|
}
|
|
8601
8602
|
}
|
|
8602
8603
|
};
|
|
@@ -8612,7 +8613,7 @@ bw.cleanup = function(element) {
|
|
|
8612
8613
|
* Calls `el._bw_render(el, state)` and emits `bw:statechange` so other
|
|
8613
8614
|
* components can react without tight coupling.
|
|
8614
8615
|
*
|
|
8615
|
-
* @param {string|Element} target - Element ID,
|
|
8616
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element
|
|
8616
8617
|
* @returns {Element|null} The element, or null if not found / no render function
|
|
8617
8618
|
* @category State Management
|
|
8618
8619
|
* @see bw.patch
|
|
@@ -8637,7 +8638,7 @@ bw.update = function(target) {
|
|
|
8637
8638
|
* Use `bw.patch()` for lightweight value updates (scores, labels, counters)
|
|
8638
8639
|
* and `bw.update()` for full structural re-renders.
|
|
8639
8640
|
*
|
|
8640
|
-
* @param {string|Element} id - Element ID,
|
|
8641
|
+
* @param {string|Element} id - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
8641
8642
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
8642
8643
|
* @param {string|Object} content - New text content, or TACO object to replace children
|
|
8643
8644
|
* @param {string} [attr] - If provided, sets this attribute instead of content
|
|
@@ -8712,7 +8713,7 @@ bw.patchAll = function(patches) {
|
|
|
8712
8713
|
* bubble by default so ancestor elements can listen. Use with `bw.on()` for
|
|
8713
8714
|
* DOM-scoped communication between components.
|
|
8714
8715
|
*
|
|
8715
|
-
* @param {string|Element} target - Element ID,
|
|
8716
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
8716
8717
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
8717
8718
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
8718
8719
|
* @param {*} [detail] - Data to pass with the event
|
|
@@ -8739,7 +8740,7 @@ bw.emit = function(target, eventName, detail) {
|
|
|
8739
8740
|
* is the first argument so you don't need to destructure `e.detail`.
|
|
8740
8741
|
* Events bubble, so you can listen on an ancestor element.
|
|
8741
8742
|
*
|
|
8742
|
-
* @param {string|Element} target - Element ID,
|
|
8743
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
8743
8744
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
8744
8745
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
8745
8746
|
* @param {Function} handler - Called with (detail, event)
|
|
@@ -8837,10 +8838,12 @@ bw.sub = function(topic, handler, el) {
|
|
|
8837
8838
|
if (el) {
|
|
8838
8839
|
if (!el._bw_subs) el._bw_subs = [];
|
|
8839
8840
|
el._bw_subs.push(unsub);
|
|
8840
|
-
// Ensure element has
|
|
8841
|
-
if (!
|
|
8842
|
-
|
|
8843
|
-
|
|
8841
|
+
// Ensure element has UUID + bw_lc so bw.cleanup() finds it
|
|
8842
|
+
if (!bw.getUUID(el)) {
|
|
8843
|
+
el.classList.add(bw.uuid('uuid'));
|
|
8844
|
+
}
|
|
8845
|
+
if (!el.classList.contains(_BW_LC)) {
|
|
8846
|
+
el.classList.add(_BW_LC);
|
|
8844
8847
|
}
|
|
8845
8848
|
}
|
|
8846
8849
|
|
|
@@ -9062,1148 +9065,97 @@ bw._resolveTemplate = function(str, state, compile) {
|
|
|
9062
9065
|
return result;
|
|
9063
9066
|
};
|
|
9064
9067
|
|
|
9065
|
-
/**
|
|
9066
|
-
* Extract top-level state keys that an expression depends on.
|
|
9067
|
-
* @param {string} expr - Expression string
|
|
9068
|
-
* @param {string[]} stateKeys - Declared state keys
|
|
9069
|
-
* @returns {string[]} Matching dependency keys
|
|
9070
|
-
* @private
|
|
9071
|
-
*/
|
|
9072
|
-
bw._extractDeps = function(expr, stateKeys) {
|
|
9073
|
-
var deps = [];
|
|
9074
|
-
for (var i = 0; i < stateKeys.length; i++) {
|
|
9075
|
-
var key = stateKeys[i];
|
|
9076
|
-
// Match word boundary: key must be preceded by start/non-word and followed by non-word/end
|
|
9077
|
-
var re = new RegExp('(?:^|[^\\w$.])' + key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:[^\\w$]|$)');
|
|
9078
|
-
if (re.test(expr) || expr === key || expr.indexOf(key + '.') === 0) {
|
|
9079
|
-
deps.push(key);
|
|
9080
|
-
}
|
|
9081
|
-
}
|
|
9082
|
-
return deps;
|
|
9083
|
-
};
|
|
9084
|
-
|
|
9085
9068
|
// ===================================================================================
|
|
9086
|
-
//
|
|
9069
|
+
// Deprecation stubs for removed ComponentHandle APIs (v2.0.19)
|
|
9087
9070
|
// ===================================================================================
|
|
9088
9071
|
|
|
9089
|
-
bw.
|
|
9090
|
-
bw.
|
|
9072
|
+
bw._extractDeps = undefined;
|
|
9073
|
+
bw._dirtyComponents = undefined;
|
|
9074
|
+
bw._flushScheduled = undefined;
|
|
9075
|
+
bw._scheduleFlush = undefined;
|
|
9076
|
+
bw._doFlush = undefined;
|
|
9077
|
+
bw._ComponentHandle = undefined;
|
|
9091
9078
|
|
|
9092
9079
|
/**
|
|
9093
|
-
*
|
|
9094
|
-
*
|
|
9080
|
+
* No-op flush (ComponentHandle removed in v2.0.19).
|
|
9081
|
+
* Kept as no-op for backward compatibility.
|
|
9082
|
+
* @category Component
|
|
9095
9083
|
*/
|
|
9096
|
-
bw.
|
|
9097
|
-
if (bw._flushScheduled) return;
|
|
9098
|
-
bw._flushScheduled = true;
|
|
9099
|
-
if (typeof Promise !== 'undefined') {
|
|
9100
|
-
Promise.resolve().then(bw._doFlush);
|
|
9101
|
-
} else {
|
|
9102
|
-
setTimeout(bw._doFlush, 0);
|
|
9103
|
-
}
|
|
9104
|
-
};
|
|
9084
|
+
bw.flush = function() {};
|
|
9105
9085
|
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
var seen = {};
|
|
9116
|
-
for (var i = 0; i < queue.length; i++) {
|
|
9117
|
-
var comp = queue[i];
|
|
9118
|
-
if (!seen[comp._bwId]) {
|
|
9119
|
-
seen[comp._bwId] = true;
|
|
9120
|
-
comp._flush();
|
|
9121
|
-
}
|
|
9122
|
-
}
|
|
9123
|
-
};
|
|
9086
|
+
|
|
9087
|
+
bw.when = function() { throw new Error('bw.when() removed in v2.0.19. Use conditional logic in o.render instead.'); };
|
|
9088
|
+
bw.each = function() { throw new Error('bw.each() removed in v2.0.19. Use array mapping in o.render instead.'); };
|
|
9089
|
+
bw.component = function() { throw new Error('bw.component() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
9090
|
+
|
|
9091
|
+
|
|
9092
|
+
// ===================================================================================
|
|
9093
|
+
// bw.message() — SendMessage() for the web
|
|
9094
|
+
// ===================================================================================
|
|
9124
9095
|
|
|
9125
9096
|
/**
|
|
9126
|
-
*
|
|
9127
|
-
*
|
|
9097
|
+
* Dispatch a message to a component by UUID, CSS class, or selector.
|
|
9098
|
+
* Finds the element, looks up el.bw, and calls the named method.
|
|
9099
|
+
* This is the bitwrench equivalent of Win32 SendMessage(hwnd, msg, wParam, lParam).
|
|
9128
9100
|
*
|
|
9101
|
+
* @param {string} target - Component UUID (bw_uuid_*), CSS class, or selector
|
|
9102
|
+
* @param {string} action - Method name to call on el.bw
|
|
9103
|
+
* @param {*} data - Data to pass to the method
|
|
9104
|
+
* @returns {boolean} True if message was dispatched successfully
|
|
9129
9105
|
* @category Component
|
|
9106
|
+
* @example
|
|
9107
|
+
* bw.message('my_carousel', 'goToSlide', 2);
|
|
9108
|
+
* // Or from SSE handler:
|
|
9109
|
+
* es.onmessage = function(e) {
|
|
9110
|
+
* var msg = JSON.parse(e.data);
|
|
9111
|
+
* bw.message(msg.target, msg.action, msg.data);
|
|
9112
|
+
* };
|
|
9130
9113
|
*/
|
|
9131
|
-
bw.
|
|
9132
|
-
bw.
|
|
9114
|
+
bw.message = function(target, action, data) {
|
|
9115
|
+
var el = bw._el(target);
|
|
9116
|
+
if (!el) el = bw.$('.' + target)[0];
|
|
9117
|
+
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
9118
|
+
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
9119
|
+
return false;
|
|
9120
|
+
}
|
|
9121
|
+
el.bw[action](data);
|
|
9122
|
+
return true;
|
|
9133
9123
|
};
|
|
9134
9124
|
|
|
9135
9125
|
// ===================================================================================
|
|
9136
|
-
//
|
|
9126
|
+
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
9137
9127
|
// ===================================================================================
|
|
9138
9128
|
|
|
9139
9129
|
/**
|
|
9140
|
-
*
|
|
9141
|
-
*
|
|
9142
|
-
*
|
|
9143
|
-
*
|
|
9144
|
-
* @param {Object} taco - TACO definition {t, a, c, o}
|
|
9145
|
-
* @constructor
|
|
9130
|
+
* Registry of named functions sent via register messages.
|
|
9131
|
+
* Populated by bw.apply({ type: 'register', name, body }).
|
|
9132
|
+
* Invoked by bw.apply({ type: 'call', name, args }).
|
|
9146
9133
|
* @private
|
|
9147
9134
|
*/
|
|
9148
|
-
|
|
9149
|
-
this._bwComponent = true; // duck-type marker
|
|
9150
|
-
this._bwId = bw.uuid('comp');
|
|
9151
|
-
this.taco = taco;
|
|
9152
|
-
this.element = null;
|
|
9153
|
-
this.mounted = false;
|
|
9154
|
-
|
|
9155
|
-
var o = taco.o || {};
|
|
9156
|
-
// Copy initial state
|
|
9157
|
-
this._state = {};
|
|
9158
|
-
if (o.state) {
|
|
9159
|
-
for (var k in o.state) {
|
|
9160
|
-
if (_hop.call(o.state, k)) {
|
|
9161
|
-
this._state[k] = o.state[k];
|
|
9162
|
-
}
|
|
9163
|
-
}
|
|
9164
|
-
}
|
|
9165
|
-
// Copy actions
|
|
9166
|
-
this._actions = {};
|
|
9167
|
-
if (o.actions) {
|
|
9168
|
-
for (var k2 in o.actions) {
|
|
9169
|
-
if (_hop.call(o.actions, k2)) {
|
|
9170
|
-
this._actions[k2] = o.actions[k2];
|
|
9171
|
-
}
|
|
9172
|
-
}
|
|
9173
|
-
}
|
|
9174
|
-
// Promote o.methods to handle API (MFC/Qt pattern: component owns its methods)
|
|
9175
|
-
this._methods = {};
|
|
9176
|
-
if (o.methods) {
|
|
9177
|
-
var self = this;
|
|
9178
|
-
for (var k3 in o.methods) {
|
|
9179
|
-
if (_hop.call(o.methods, k3)) {
|
|
9180
|
-
this._methods[k3] = o.methods[k3];
|
|
9181
|
-
(function(methodName, methodFn) {
|
|
9182
|
-
self[methodName] = function() {
|
|
9183
|
-
var args = [self].concat(Array.prototype.slice.call(arguments));
|
|
9184
|
-
return methodFn.apply(null, args);
|
|
9185
|
-
};
|
|
9186
|
-
})(k3, o.methods[k3]);
|
|
9187
|
-
}
|
|
9188
|
-
}
|
|
9189
|
-
}
|
|
9190
|
-
// User tag for addressing via bw.message()
|
|
9191
|
-
this._userTag = null;
|
|
9192
|
-
// Lifecycle hooks
|
|
9193
|
-
this._hooks = {
|
|
9194
|
-
willMount: o.willMount || null,
|
|
9195
|
-
mounted: o.mounted || null,
|
|
9196
|
-
willUpdate: o.willUpdate || null,
|
|
9197
|
-
onUpdate: o.onUpdate || o.updated || null,
|
|
9198
|
-
unmount: o.unmount || null,
|
|
9199
|
-
willDestroy: o.willDestroy || null
|
|
9200
|
-
};
|
|
9201
|
-
// Binding tracking
|
|
9202
|
-
this._bindings = [];
|
|
9203
|
-
this._dirtyKeys = {};
|
|
9204
|
-
this._scheduled = false;
|
|
9205
|
-
this._subs = [];
|
|
9206
|
-
this._eventListeners = [];
|
|
9207
|
-
this._registeredActions = [];
|
|
9208
|
-
this._prevValues = {};
|
|
9209
|
-
this._compile = !!o.compile;
|
|
9210
|
-
this._bw_refs = {};
|
|
9211
|
-
this._refCounter = 0;
|
|
9212
|
-
// Child component ownership (Bug #5)
|
|
9213
|
-
this._children = [];
|
|
9214
|
-
this._parent = null;
|
|
9215
|
-
// Factory metadata for BCCL rebuild (Bug #6)
|
|
9216
|
-
this._factory = taco._bwFactory || null;
|
|
9217
|
-
}
|
|
9218
|
-
|
|
9219
|
-
// Short alias for ComponentHandle.prototype (see alias block at top of file).
|
|
9220
|
-
// 28 method definitions × 25 chars = ~700B raw savings in minified output.
|
|
9221
|
-
var _chp = ComponentHandle.prototype;
|
|
9222
|
-
|
|
9223
|
-
// ── State Methods ──
|
|
9135
|
+
bw._clientFunctions = {};
|
|
9224
9136
|
|
|
9225
9137
|
/**
|
|
9226
|
-
*
|
|
9138
|
+
* Whether exec messages are allowed. Set by bwclient connect opts.allowExec.
|
|
9139
|
+
* Default false — exec messages are rejected unless explicitly opted in.
|
|
9140
|
+
* @private
|
|
9227
9141
|
*/
|
|
9228
|
-
|
|
9229
|
-
return bw._evaluatePath(this._state, key);
|
|
9230
|
-
};
|
|
9142
|
+
bw._allowExec = false;
|
|
9231
9143
|
|
|
9232
9144
|
/**
|
|
9233
|
-
*
|
|
9234
|
-
*
|
|
9235
|
-
*
|
|
9236
|
-
*
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
obj = obj[parts[i]];
|
|
9248
|
-
}
|
|
9249
|
-
obj[parts[parts.length - 1]] = value;
|
|
9250
|
-
// Mark top-level key dirty
|
|
9251
|
-
this._dirtyKeys[parts[0]] = true;
|
|
9252
|
-
if (this.mounted) {
|
|
9253
|
-
if (opts && opts.sync) {
|
|
9254
|
-
this._flush();
|
|
9255
|
-
} else {
|
|
9256
|
-
this._scheduleDirty();
|
|
9257
|
-
}
|
|
9258
|
-
}
|
|
9259
|
-
};
|
|
9260
|
-
|
|
9261
|
-
/**
|
|
9262
|
-
* Get a shallow clone of the full state.
|
|
9263
|
-
*/
|
|
9264
|
-
_chp.getState = function() {
|
|
9265
|
-
var clone = {};
|
|
9266
|
-
for (var k in this._state) {
|
|
9267
|
-
if (_hop.call(this._state, k)) {
|
|
9268
|
-
clone[k] = this._state[k];
|
|
9269
|
-
}
|
|
9270
|
-
}
|
|
9271
|
-
return clone;
|
|
9272
|
-
};
|
|
9273
|
-
|
|
9274
|
-
/**
|
|
9275
|
-
* Merge multiple state keys. Schedules re-render.
|
|
9276
|
-
* @param {Object} updates - Key-value pairs to merge
|
|
9277
|
-
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
9278
|
-
*/
|
|
9279
|
-
_chp.setState = function(updates, opts) {
|
|
9280
|
-
for (var k in updates) {
|
|
9281
|
-
if (_hop.call(updates, k)) {
|
|
9282
|
-
this._state[k] = updates[k];
|
|
9283
|
-
this._dirtyKeys[k] = true;
|
|
9284
|
-
}
|
|
9285
|
-
}
|
|
9286
|
-
if (this.mounted) {
|
|
9287
|
-
if (opts && opts.sync) {
|
|
9288
|
-
this._flush();
|
|
9289
|
-
} else {
|
|
9290
|
-
this._scheduleDirty();
|
|
9291
|
-
}
|
|
9292
|
-
}
|
|
9293
|
-
};
|
|
9294
|
-
|
|
9295
|
-
/**
|
|
9296
|
-
* Push a value onto an array in state. Clones the array.
|
|
9297
|
-
*/
|
|
9298
|
-
_chp.push = function(key, val) {
|
|
9299
|
-
var arr = this.get(key);
|
|
9300
|
-
var newArr = _isA(arr) ? arr.slice() : [];
|
|
9301
|
-
newArr.push(val);
|
|
9302
|
-
this.set(key, newArr);
|
|
9303
|
-
};
|
|
9304
|
-
|
|
9305
|
-
/**
|
|
9306
|
-
* Splice an array in state. Clones the array.
|
|
9307
|
-
*/
|
|
9308
|
-
_chp.splice = function(key, start, deleteCount) {
|
|
9309
|
-
var arr = this.get(key);
|
|
9310
|
-
var newArr = _isA(arr) ? arr.slice() : [];
|
|
9311
|
-
var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
|
|
9312
|
-
Array.prototype.splice.apply(newArr, args);
|
|
9313
|
-
this.set(key, newArr);
|
|
9314
|
-
};
|
|
9315
|
-
|
|
9316
|
-
// ── Scheduling ──
|
|
9317
|
-
|
|
9318
|
-
_chp._scheduleDirty = function() {
|
|
9319
|
-
if (!this._scheduled) {
|
|
9320
|
-
this._scheduled = true;
|
|
9321
|
-
bw._dirtyComponents.push(this);
|
|
9322
|
-
bw._scheduleFlush();
|
|
9323
|
-
}
|
|
9324
|
-
};
|
|
9325
|
-
|
|
9326
|
-
// ── Binding Compilation ──
|
|
9327
|
-
|
|
9328
|
-
/**
|
|
9329
|
-
* Walk the TACO tree and extract ${expr} bindings.
|
|
9330
|
-
* Creates binding descriptors with refIds for targeted DOM updates.
|
|
9331
|
-
* @private
|
|
9332
|
-
*/
|
|
9333
|
-
_chp._compileBindings = function() {
|
|
9334
|
-
this._bindings = [];
|
|
9335
|
-
this._refCounter = 0;
|
|
9336
|
-
var stateKeys = _keys(this._state);
|
|
9337
|
-
var self = this;
|
|
9338
|
-
|
|
9339
|
-
function walkTaco(taco, path) {
|
|
9340
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
9341
|
-
|
|
9342
|
-
// Check content for bindings
|
|
9343
|
-
if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
|
|
9344
|
-
var refId = 'bw_ref_' + self._refCounter++;
|
|
9345
|
-
var parsed = bw._parseBindings(taco.c);
|
|
9346
|
-
var deps = [];
|
|
9347
|
-
for (var j = 0; j < parsed.length; j++) {
|
|
9348
|
-
deps = deps.concat(bw._extractDeps(parsed[j].expr, stateKeys));
|
|
9349
|
-
}
|
|
9350
|
-
self._bindings.push({
|
|
9351
|
-
expr: taco.c,
|
|
9352
|
-
type: 'content',
|
|
9353
|
-
refId: refId,
|
|
9354
|
-
deps: deps,
|
|
9355
|
-
template: taco.c
|
|
9356
|
-
});
|
|
9357
|
-
// Inject data-bw_ref on the TACO for createDOM to pick up
|
|
9358
|
-
if (!taco.a) taco.a = {};
|
|
9359
|
-
taco.a['data-bw_ref'] = refId;
|
|
9360
|
-
}
|
|
9361
|
-
|
|
9362
|
-
// Check attributes for bindings
|
|
9363
|
-
if (taco.a) {
|
|
9364
|
-
for (var attrName in taco.a) {
|
|
9365
|
-
if (!_hop.call(taco.a, attrName)) continue;
|
|
9366
|
-
if (attrName === 'data-bw_ref') continue;
|
|
9367
|
-
var attrVal = taco.a[attrName];
|
|
9368
|
-
if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
|
|
9369
|
-
var refId2 = 'bw_ref_' + self._refCounter++;
|
|
9370
|
-
var parsed2 = bw._parseBindings(attrVal);
|
|
9371
|
-
var deps2 = [];
|
|
9372
|
-
for (var j2 = 0; j2 < parsed2.length; j2++) {
|
|
9373
|
-
deps2 = deps2.concat(bw._extractDeps(parsed2[j2].expr, stateKeys));
|
|
9374
|
-
}
|
|
9375
|
-
self._bindings.push({
|
|
9376
|
-
expr: attrVal,
|
|
9377
|
-
type: 'attribute',
|
|
9378
|
-
attrName: attrName,
|
|
9379
|
-
refId: refId2,
|
|
9380
|
-
deps: deps2,
|
|
9381
|
-
template: attrVal
|
|
9382
|
-
});
|
|
9383
|
-
if (!taco.a) taco.a = {};
|
|
9384
|
-
taco.a['data-bw_ref'] = taco.a['data-bw_ref'] || refId2;
|
|
9385
|
-
// If multiple attribute bindings on same element, store additional marker
|
|
9386
|
-
if (taco.a['data-bw_ref'] !== refId2) {
|
|
9387
|
-
taco.a['data-bw_ref_' + attrName] = refId2;
|
|
9388
|
-
}
|
|
9389
|
-
}
|
|
9390
|
-
}
|
|
9391
|
-
}
|
|
9392
|
-
|
|
9393
|
-
// Recurse into children
|
|
9394
|
-
if (_isA(taco.c)) {
|
|
9395
|
-
for (var i = 0; i < taco.c.length; i++) {
|
|
9396
|
-
// Wrap string children with ${expr} in a span so patches target the span, not the parent
|
|
9397
|
-
if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
|
|
9398
|
-
var mixedRefId = 'bw_ref_' + self._refCounter++;
|
|
9399
|
-
var mixedParsed = bw._parseBindings(taco.c[i]);
|
|
9400
|
-
var mixedDeps = [];
|
|
9401
|
-
for (var mi = 0; mi < mixedParsed.length; mi++) {
|
|
9402
|
-
mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
|
|
9403
|
-
}
|
|
9404
|
-
self._bindings.push({
|
|
9405
|
-
expr: taco.c[i],
|
|
9406
|
-
type: 'content',
|
|
9407
|
-
refId: mixedRefId,
|
|
9408
|
-
deps: mixedDeps,
|
|
9409
|
-
template: taco.c[i]
|
|
9410
|
-
});
|
|
9411
|
-
// Replace string with a span wrapper so textContent targets the span only
|
|
9412
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
|
|
9413
|
-
}
|
|
9414
|
-
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
9415
|
-
walkTaco(taco.c[i], path.concat(i));
|
|
9416
|
-
}
|
|
9417
|
-
// Handle bw.when/bw.each markers
|
|
9418
|
-
if (taco.c[i] && taco.c[i]._bwWhen) {
|
|
9419
|
-
var whenRefId = 'bw_ref_' + self._refCounter++;
|
|
9420
|
-
var whenDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
|
|
9421
|
-
self._bindings.push({
|
|
9422
|
-
expr: taco.c[i].expr,
|
|
9423
|
-
type: 'structural',
|
|
9424
|
-
subtype: 'when',
|
|
9425
|
-
refId: whenRefId,
|
|
9426
|
-
deps: whenDeps,
|
|
9427
|
-
branches: taco.c[i].branches,
|
|
9428
|
-
index: i,
|
|
9429
|
-
parentPath: path
|
|
9430
|
-
});
|
|
9431
|
-
taco.c[i]._refId = whenRefId;
|
|
9432
|
-
}
|
|
9433
|
-
if (taco.c[i] && taco.c[i]._bwEach) {
|
|
9434
|
-
var eachRefId = 'bw_ref_' + self._refCounter++;
|
|
9435
|
-
var eachDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
|
|
9436
|
-
self._bindings.push({
|
|
9437
|
-
expr: taco.c[i].expr,
|
|
9438
|
-
type: 'structural',
|
|
9439
|
-
subtype: 'each',
|
|
9440
|
-
refId: eachRefId,
|
|
9441
|
-
deps: eachDeps,
|
|
9442
|
-
factory: taco.c[i].factory,
|
|
9443
|
-
index: i,
|
|
9444
|
-
parentPath: path
|
|
9445
|
-
});
|
|
9446
|
-
taco.c[i]._refId = eachRefId;
|
|
9447
|
-
}
|
|
9448
|
-
}
|
|
9449
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
9450
|
-
walkTaco(taco.c, path.concat(0));
|
|
9451
|
-
}
|
|
9452
|
-
|
|
9453
|
-
return taco;
|
|
9454
|
-
}
|
|
9455
|
-
|
|
9456
|
-
walkTaco(this.taco, []);
|
|
9457
|
-
};
|
|
9458
|
-
|
|
9459
|
-
// ── DOM Reference Collection ──
|
|
9460
|
-
|
|
9461
|
-
/**
|
|
9462
|
-
* Build ref map from the live DOM after createDOM.
|
|
9463
|
-
* @private
|
|
9464
|
-
*/
|
|
9465
|
-
_chp._collectRefs = function() {
|
|
9466
|
-
this._bw_refs = {};
|
|
9467
|
-
if (!this.element) return;
|
|
9468
|
-
var els = this.element.querySelectorAll('[data-bw_ref]');
|
|
9469
|
-
for (var i = 0; i < els.length; i++) {
|
|
9470
|
-
this._bw_refs[els[i].getAttribute('data-bw_ref')] = els[i];
|
|
9471
|
-
}
|
|
9472
|
-
// Also check root element
|
|
9473
|
-
var rootRef = this.element.getAttribute && this.element.getAttribute('data-bw_ref');
|
|
9474
|
-
if (rootRef) {
|
|
9475
|
-
this._bw_refs[rootRef] = this.element;
|
|
9476
|
-
}
|
|
9477
|
-
};
|
|
9478
|
-
|
|
9479
|
-
// ── Lifecycle ──
|
|
9480
|
-
|
|
9481
|
-
/**
|
|
9482
|
-
* Mount the component into a parent DOM element.
|
|
9483
|
-
* Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
|
|
9484
|
-
* @param {Element} parentEl - DOM element to mount into
|
|
9485
|
-
*/
|
|
9486
|
-
_chp.mount = function(parentEl) {
|
|
9487
|
-
// willMount hook
|
|
9488
|
-
if (this._hooks.willMount) this._hooks.willMount(this);
|
|
9489
|
-
|
|
9490
|
-
// Save original TACO for re-renders (structural changes clone from this)
|
|
9491
|
-
if (!this._originalTaco) {
|
|
9492
|
-
this._originalTaco = this.taco;
|
|
9493
|
-
}
|
|
9494
|
-
|
|
9495
|
-
// Deep-clone TACO so binding annotations don't mutate original.
|
|
9496
|
-
// Custom clone to preserve _bwWhen/_bwEach markers and their factory functions.
|
|
9497
|
-
this.taco = this._deepCloneTaco(this._originalTaco);
|
|
9498
|
-
|
|
9499
|
-
// Compile bindings (annotates TACO with data-bw_ref attributes)
|
|
9500
|
-
this._compileBindings();
|
|
9501
|
-
|
|
9502
|
-
// Prepare TACO: resolve initial binding values, evaluate when/each
|
|
9503
|
-
this._prepareTaco(this.taco);
|
|
9504
|
-
|
|
9505
|
-
// Register named actions in function registry
|
|
9506
|
-
var self = this;
|
|
9507
|
-
for (var actionName in this._actions) {
|
|
9508
|
-
if (_hop.call(this._actions, actionName)) {
|
|
9509
|
-
var registeredName = this._bwId + '_' + actionName;
|
|
9510
|
-
(function(aName) {
|
|
9511
|
-
bw.funcRegister(function(evt) {
|
|
9512
|
-
self._actions[aName](self, evt);
|
|
9513
|
-
}, registeredName);
|
|
9514
|
-
})(actionName);
|
|
9515
|
-
this._registeredActions.push(registeredName);
|
|
9516
|
-
}
|
|
9517
|
-
}
|
|
9518
|
-
|
|
9519
|
-
// Wire action names in onclick etc. to dispatch strings
|
|
9520
|
-
this._wireActions(this.taco);
|
|
9521
|
-
|
|
9522
|
-
// Create DOM (strip o before createDOM to prevent double lifecycle)
|
|
9523
|
-
var tacoForDOM = this._tacoForDOM(this.taco);
|
|
9524
|
-
this.element = bw.createDOM(tacoForDOM);
|
|
9525
|
-
this.element._bwComponentHandle = this;
|
|
9526
|
-
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
9527
|
-
|
|
9528
|
-
// Restore o.render from original TACO (stripped by _tacoForDOM)
|
|
9529
|
-
if (this.taco.o && this.taco.o.render) {
|
|
9530
|
-
this.element._bw_render = this.taco.o.render;
|
|
9531
|
-
}
|
|
9532
|
-
if (this._userTag) {
|
|
9533
|
-
this.element.classList.add(this._userTag);
|
|
9534
|
-
}
|
|
9535
|
-
|
|
9536
|
-
// Append to parent
|
|
9537
|
-
parentEl.appendChild(this.element);
|
|
9538
|
-
|
|
9539
|
-
// Collect refs from live DOM
|
|
9540
|
-
this._collectRefs();
|
|
9541
|
-
|
|
9542
|
-
// Resolve initial bindings and apply to DOM
|
|
9543
|
-
this._resolveAndApplyAll();
|
|
9544
|
-
|
|
9545
|
-
this.mounted = true;
|
|
9546
|
-
|
|
9547
|
-
// Scan for child ComponentHandles and link parent/child (Bug #5)
|
|
9548
|
-
var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
|
|
9549
|
-
for (var ci = 0; ci < childEls.length; ci++) {
|
|
9550
|
-
var ch = childEls[ci]._bwComponentHandle;
|
|
9551
|
-
if (ch && ch !== this && !ch._parent) {
|
|
9552
|
-
ch._parent = this;
|
|
9553
|
-
this._children.push(ch);
|
|
9554
|
-
}
|
|
9555
|
-
}
|
|
9556
|
-
|
|
9557
|
-
// mounted hook (backward compat: fn.length === 2 wraps (el, state))
|
|
9558
|
-
if (this._hooks.mounted) {
|
|
9559
|
-
if (this._hooks.mounted.length === 2) {
|
|
9560
|
-
this._hooks.mounted(this.element, this.getState());
|
|
9561
|
-
} else {
|
|
9562
|
-
this._hooks.mounted(this);
|
|
9563
|
-
}
|
|
9564
|
-
}
|
|
9565
|
-
|
|
9566
|
-
// Invoke o.render on initial mount (if present)
|
|
9567
|
-
if (this.element._bw_render) {
|
|
9568
|
-
this.element._bw_render(this.element, this._state);
|
|
9569
|
-
}
|
|
9570
|
-
};
|
|
9571
|
-
|
|
9572
|
-
/**
|
|
9573
|
-
* Prepare TACO for initial render: resolve when/each markers.
|
|
9574
|
-
* @private
|
|
9575
|
-
*/
|
|
9576
|
-
_chp._prepareTaco = function(taco) {
|
|
9577
|
-
if (!_is(taco, 'object')) return;
|
|
9578
|
-
|
|
9579
|
-
if (_isA(taco.c)) {
|
|
9580
|
-
for (var i = taco.c.length - 1; i >= 0; i--) {
|
|
9581
|
-
var child = taco.c[i];
|
|
9582
|
-
if (child && child._bwWhen) {
|
|
9583
|
-
var exprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
9584
|
-
var val;
|
|
9585
|
-
if (this._compile) {
|
|
9586
|
-
try {
|
|
9587
|
-
val = (new Function('state', 'with(state){return (' + exprStr + ');}'))(this._state);
|
|
9588
|
-
} catch(e) { val = false; }
|
|
9589
|
-
} else {
|
|
9590
|
-
val = bw._evaluatePath(this._state, exprStr);
|
|
9591
|
-
}
|
|
9592
|
-
var branch = val ? child.branches[0] : (child.branches[1] || null);
|
|
9593
|
-
if (branch) {
|
|
9594
|
-
// Wrap in a container so we can track it
|
|
9595
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_when': child._refId, style: 'display:contents' }, c: branch };
|
|
9596
|
-
} else {
|
|
9597
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_when': child._refId, style: 'display:contents' }, c: '' };
|
|
9598
|
-
}
|
|
9599
|
-
}
|
|
9600
|
-
if (child && child._bwEach) {
|
|
9601
|
-
var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
9602
|
-
var arr = bw._evaluatePath(this._state, eachExprStr);
|
|
9603
|
-
var items = [];
|
|
9604
|
-
if (_isA(arr)) {
|
|
9605
|
-
for (var j = 0; j < arr.length; j++) {
|
|
9606
|
-
items.push(child.factory(arr[j], j));
|
|
9607
|
-
}
|
|
9608
|
-
}
|
|
9609
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
|
|
9610
|
-
}
|
|
9611
|
-
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
9612
|
-
this._prepareTaco(taco.c[i]);
|
|
9613
|
-
}
|
|
9614
|
-
}
|
|
9615
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
9616
|
-
this._prepareTaco(taco.c);
|
|
9617
|
-
}
|
|
9618
|
-
};
|
|
9619
|
-
|
|
9620
|
-
/**
|
|
9621
|
-
* Wire action name strings (in onclick etc.) to dispatch function calls.
|
|
9622
|
-
* @private
|
|
9623
|
-
*/
|
|
9624
|
-
_chp._wireActions = function(taco) {
|
|
9625
|
-
if (!_is(taco, 'object') || !taco.t) return;
|
|
9626
|
-
if (taco.a) {
|
|
9627
|
-
for (var key in taco.a) {
|
|
9628
|
-
if (!_hop.call(taco.a, key)) continue;
|
|
9629
|
-
if (key.startsWith('on') && _is(taco.a[key], 'string')) {
|
|
9630
|
-
var actionName = taco.a[key];
|
|
9631
|
-
if (actionName in this._actions) {
|
|
9632
|
-
var registeredName = this._bwId + '_' + actionName;
|
|
9633
|
-
// Replace string with actual function for createDOM event binding
|
|
9634
|
-
(function(rName) {
|
|
9635
|
-
taco.a[key] = function(evt) {
|
|
9636
|
-
bw.funcGetById(rName)(evt);
|
|
9637
|
-
};
|
|
9638
|
-
})(registeredName);
|
|
9639
|
-
}
|
|
9640
|
-
}
|
|
9641
|
-
}
|
|
9642
|
-
}
|
|
9643
|
-
if (_isA(taco.c)) {
|
|
9644
|
-
for (var i = 0; i < taco.c.length; i++) {
|
|
9645
|
-
this._wireActions(taco.c[i]);
|
|
9646
|
-
}
|
|
9647
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
9648
|
-
this._wireActions(taco.c);
|
|
9649
|
-
}
|
|
9650
|
-
};
|
|
9651
|
-
|
|
9652
|
-
/**
|
|
9653
|
-
* Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
|
|
9654
|
-
* @private
|
|
9655
|
-
*/
|
|
9656
|
-
_chp._deepCloneTaco = function(taco) {
|
|
9657
|
-
if (taco == null) return taco;
|
|
9658
|
-
// Preserve _bwWhen / _bwEach markers (contain functions)
|
|
9659
|
-
if (taco._bwWhen) {
|
|
9660
|
-
return { _bwWhen: true, expr: taco.expr, branches: [
|
|
9661
|
-
this._deepCloneTaco(taco.branches[0]),
|
|
9662
|
-
taco.branches[1] ? this._deepCloneTaco(taco.branches[1]) : null
|
|
9663
|
-
], _refId: taco._refId };
|
|
9664
|
-
}
|
|
9665
|
-
if (taco._bwEach) {
|
|
9666
|
-
return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
|
|
9667
|
-
}
|
|
9668
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
9669
|
-
var result = { t: taco.t };
|
|
9670
|
-
if (taco.a) {
|
|
9671
|
-
result.a = {};
|
|
9672
|
-
for (var k in taco.a) {
|
|
9673
|
-
if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
|
|
9674
|
-
}
|
|
9675
|
-
}
|
|
9676
|
-
if (taco.c != null) {
|
|
9677
|
-
if (_isA(taco.c)) {
|
|
9678
|
-
result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
|
|
9679
|
-
} else if (_is(taco.c, 'object')) {
|
|
9680
|
-
result.c = this._deepCloneTaco(taco.c);
|
|
9681
|
-
} else {
|
|
9682
|
-
result.c = taco.c;
|
|
9683
|
-
}
|
|
9684
|
-
}
|
|
9685
|
-
if (taco.o) result.o = taco.o; // Keep o reference (not deep-cloned; hooks are functions)
|
|
9686
|
-
return result;
|
|
9687
|
-
};
|
|
9688
|
-
|
|
9689
|
-
/**
|
|
9690
|
-
* Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
|
|
9691
|
-
* @private
|
|
9692
|
-
*/
|
|
9693
|
-
_chp._tacoForDOM = function(taco) {
|
|
9694
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
9695
|
-
var result = { t: taco.t };
|
|
9696
|
-
if (taco.a) result.a = taco.a;
|
|
9697
|
-
if (taco.c != null) {
|
|
9698
|
-
if (_isA(taco.c)) {
|
|
9699
|
-
result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
|
|
9700
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
9701
|
-
result.c = this._tacoForDOM(taco.c);
|
|
9702
|
-
} else {
|
|
9703
|
-
result.c = taco.c;
|
|
9704
|
-
}
|
|
9705
|
-
}
|
|
9706
|
-
// Intentionally strip o (no mounted/unmount/state/render on sub-elements)
|
|
9707
|
-
if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
|
|
9708
|
-
_cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
|
|
9709
|
-
'>. Use onclick attribute or bw.component() for child interactivity.');
|
|
9710
|
-
}
|
|
9711
|
-
return result;
|
|
9712
|
-
};
|
|
9713
|
-
|
|
9714
|
-
/**
|
|
9715
|
-
* Unmount: remove from DOM, deactivate, preserve state for re-mount.
|
|
9716
|
-
*/
|
|
9717
|
-
_chp.unmount = function() {
|
|
9718
|
-
if (!this.mounted) return;
|
|
9719
|
-
|
|
9720
|
-
// unmount hook
|
|
9721
|
-
if (this._hooks.unmount) {
|
|
9722
|
-
this._hooks.unmount(this);
|
|
9723
|
-
}
|
|
9724
|
-
|
|
9725
|
-
// Remove DOM event listeners
|
|
9726
|
-
for (var i = 0; i < this._eventListeners.length; i++) {
|
|
9727
|
-
var l = this._eventListeners[i];
|
|
9728
|
-
if (this.element) {
|
|
9729
|
-
this.element.removeEventListener(l.event, l.handler);
|
|
9730
|
-
}
|
|
9731
|
-
}
|
|
9732
|
-
this._eventListeners = [];
|
|
9733
|
-
|
|
9734
|
-
// Unsubscribe pub/sub
|
|
9735
|
-
for (var j = 0; j < this._subs.length; j++) {
|
|
9736
|
-
this._subs[j]();
|
|
9737
|
-
}
|
|
9738
|
-
this._subs = [];
|
|
9739
|
-
|
|
9740
|
-
// Remove from DOM
|
|
9741
|
-
if (this.element && this.element.parentNode) {
|
|
9742
|
-
this.element.parentNode.removeChild(this.element);
|
|
9743
|
-
}
|
|
9744
|
-
|
|
9745
|
-
this.mounted = false;
|
|
9746
|
-
// State preserved — can re-mount
|
|
9747
|
-
};
|
|
9748
|
-
|
|
9749
|
-
/**
|
|
9750
|
-
* Destroy: unmount + clear state + unregister actions.
|
|
9751
|
-
*/
|
|
9752
|
-
_chp.destroy = function() {
|
|
9753
|
-
// willDestroy hook
|
|
9754
|
-
if (this._hooks.willDestroy) {
|
|
9755
|
-
this._hooks.willDestroy(this);
|
|
9756
|
-
}
|
|
9757
|
-
|
|
9758
|
-
// Cascade destroy to children depth-first (Bug #5)
|
|
9759
|
-
for (var ci = this._children.length - 1; ci >= 0; ci--) {
|
|
9760
|
-
this._children[ci].destroy();
|
|
9761
|
-
}
|
|
9762
|
-
this._children = [];
|
|
9763
|
-
if (this._parent) {
|
|
9764
|
-
var idx = this._parent._children.indexOf(this);
|
|
9765
|
-
if (idx >= 0) this._parent._children.splice(idx, 1);
|
|
9766
|
-
this._parent = null;
|
|
9767
|
-
}
|
|
9768
|
-
|
|
9769
|
-
this.unmount();
|
|
9770
|
-
|
|
9771
|
-
// Unregister actions from function registry
|
|
9772
|
-
for (var i = 0; i < this._registeredActions.length; i++) {
|
|
9773
|
-
bw.funcUnregister(this._registeredActions[i]);
|
|
9774
|
-
}
|
|
9775
|
-
this._registeredActions = [];
|
|
9776
|
-
|
|
9777
|
-
// Clear state
|
|
9778
|
-
this._state = {};
|
|
9779
|
-
this._bindings = [];
|
|
9780
|
-
this._bw_refs = {};
|
|
9781
|
-
this._prevValues = {};
|
|
9782
|
-
this._dirtyKeys = {};
|
|
9783
|
-
if (this.element) {
|
|
9784
|
-
delete this.element._bwComponentHandle;
|
|
9785
|
-
this.element = null;
|
|
9786
|
-
}
|
|
9787
|
-
};
|
|
9788
|
-
|
|
9789
|
-
// ── Flush & Binding Resolution ──
|
|
9790
|
-
|
|
9791
|
-
/**
|
|
9792
|
-
* Flush dirty state: resolve changed bindings and apply to DOM.
|
|
9793
|
-
* @private
|
|
9794
|
-
*/
|
|
9795
|
-
_chp._flush = function() {
|
|
9796
|
-
this._scheduled = false;
|
|
9797
|
-
var changedKeys = _keys(this._dirtyKeys);
|
|
9798
|
-
this._dirtyKeys = {};
|
|
9799
|
-
if (changedKeys.length === 0 || !this.mounted) return;
|
|
9800
|
-
|
|
9801
|
-
// Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
|
|
9802
|
-
// rebuild the TACO from the factory with merged state (Bug #6)
|
|
9803
|
-
if (this._factory) {
|
|
9804
|
-
var rebuildNeeded = false;
|
|
9805
|
-
for (var fi = 0; fi < changedKeys.length; fi++) {
|
|
9806
|
-
if (_hop.call(this._factory.props, changedKeys[fi])) {
|
|
9807
|
-
rebuildNeeded = true; break;
|
|
9808
|
-
}
|
|
9809
|
-
}
|
|
9810
|
-
if (rebuildNeeded) {
|
|
9811
|
-
var merged = {};
|
|
9812
|
-
for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
|
|
9813
|
-
for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
|
|
9814
|
-
this._factory.props = merged;
|
|
9815
|
-
var newTaco = bw.make(this._factory.type, merged);
|
|
9816
|
-
newTaco._bwFactory = this._factory;
|
|
9817
|
-
this.taco = newTaco;
|
|
9818
|
-
this._originalTaco = this._deepCloneTaco(newTaco);
|
|
9819
|
-
this._render();
|
|
9820
|
-
if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
|
|
9821
|
-
return;
|
|
9822
|
-
}
|
|
9823
|
-
}
|
|
9824
|
-
|
|
9825
|
-
// willUpdate hook
|
|
9826
|
-
if (this._hooks.willUpdate) {
|
|
9827
|
-
this._hooks.willUpdate(this, changedKeys);
|
|
9828
|
-
}
|
|
9829
|
-
|
|
9830
|
-
// Check if any structural bindings are affected
|
|
9831
|
-
var needsFullRender = false;
|
|
9832
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
9833
|
-
var b = this._bindings[i];
|
|
9834
|
-
if (b.type === 'structural') {
|
|
9835
|
-
for (var j = 0; j < b.deps.length; j++) {
|
|
9836
|
-
if (changedKeys.indexOf(b.deps[j]) >= 0) {
|
|
9837
|
-
needsFullRender = true;
|
|
9838
|
-
break;
|
|
9839
|
-
}
|
|
9840
|
-
}
|
|
9841
|
-
if (needsFullRender) break;
|
|
9842
|
-
}
|
|
9843
|
-
}
|
|
9844
|
-
|
|
9845
|
-
if (needsFullRender) {
|
|
9846
|
-
this._render();
|
|
9847
|
-
} else {
|
|
9848
|
-
var patches = this._resolveBindings(changedKeys);
|
|
9849
|
-
this._applyPatches(patches);
|
|
9850
|
-
}
|
|
9851
|
-
|
|
9852
|
-
// onUpdate hook
|
|
9853
|
-
if (this._hooks.onUpdate) {
|
|
9854
|
-
this._hooks.onUpdate(this, changedKeys);
|
|
9855
|
-
}
|
|
9856
|
-
};
|
|
9857
|
-
|
|
9858
|
-
/**
|
|
9859
|
-
* Resolve bindings whose deps intersect with changedKeys.
|
|
9860
|
-
* Returns list of patches to apply.
|
|
9861
|
-
* @private
|
|
9862
|
-
*/
|
|
9863
|
-
_chp._resolveBindings = function(changedKeys) {
|
|
9864
|
-
var patches = [];
|
|
9865
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
9866
|
-
var b = this._bindings[i];
|
|
9867
|
-
if (b.type === 'structural') continue;
|
|
9868
|
-
|
|
9869
|
-
// Check if any dep matches
|
|
9870
|
-
var affected = false;
|
|
9871
|
-
for (var j = 0; j < b.deps.length; j++) {
|
|
9872
|
-
if (changedKeys.indexOf(b.deps[j]) >= 0) {
|
|
9873
|
-
affected = true;
|
|
9874
|
-
break;
|
|
9875
|
-
}
|
|
9876
|
-
}
|
|
9877
|
-
if (!affected) continue;
|
|
9878
|
-
|
|
9879
|
-
// Evaluate
|
|
9880
|
-
var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
|
|
9881
|
-
var prevKey = b.refId + '_' + (b.attrName || 'content');
|
|
9882
|
-
if (this._prevValues[prevKey] !== newVal) {
|
|
9883
|
-
this._prevValues[prevKey] = newVal;
|
|
9884
|
-
patches.push({
|
|
9885
|
-
refId: b.refId,
|
|
9886
|
-
type: b.type,
|
|
9887
|
-
attrName: b.attrName,
|
|
9888
|
-
value: newVal
|
|
9889
|
-
});
|
|
9890
|
-
}
|
|
9891
|
-
}
|
|
9892
|
-
return patches;
|
|
9893
|
-
};
|
|
9894
|
-
|
|
9895
|
-
/**
|
|
9896
|
-
* Apply patches to DOM.
|
|
9897
|
-
* @private
|
|
9898
|
-
*/
|
|
9899
|
-
_chp._applyPatches = function(patches) {
|
|
9900
|
-
for (var i = 0; i < patches.length; i++) {
|
|
9901
|
-
var p = patches[i];
|
|
9902
|
-
var el = this._bw_refs[p.refId];
|
|
9903
|
-
if (!el) {
|
|
9904
|
-
if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
|
|
9905
|
-
continue;
|
|
9906
|
-
}
|
|
9907
|
-
if (p.type === 'content') {
|
|
9908
|
-
el.textContent = p.value;
|
|
9909
|
-
} else if (p.type === 'attribute') {
|
|
9910
|
-
if (p.attrName === 'class') {
|
|
9911
|
-
el.className = p.value;
|
|
9912
|
-
} else {
|
|
9913
|
-
el.setAttribute(p.attrName, p.value);
|
|
9914
|
-
}
|
|
9915
|
-
}
|
|
9916
|
-
}
|
|
9917
|
-
};
|
|
9918
|
-
|
|
9919
|
-
/**
|
|
9920
|
-
* Resolve all bindings and apply (used for initial render).
|
|
9921
|
-
* @private
|
|
9922
|
-
*/
|
|
9923
|
-
_chp._resolveAndApplyAll = function() {
|
|
9924
|
-
var patches = [];
|
|
9925
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
9926
|
-
var b = this._bindings[i];
|
|
9927
|
-
if (b.type === 'structural') continue;
|
|
9928
|
-
|
|
9929
|
-
var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
|
|
9930
|
-
var prevKey = b.refId + '_' + (b.attrName || 'content');
|
|
9931
|
-
this._prevValues[prevKey] = newVal;
|
|
9932
|
-
patches.push({
|
|
9933
|
-
refId: b.refId,
|
|
9934
|
-
type: b.type,
|
|
9935
|
-
attrName: b.attrName,
|
|
9936
|
-
value: newVal
|
|
9937
|
-
});
|
|
9938
|
-
}
|
|
9939
|
-
this._applyPatches(patches);
|
|
9940
|
-
};
|
|
9941
|
-
|
|
9942
|
-
/**
|
|
9943
|
-
* Full re-render for structural changes (when/each branch switches).
|
|
9944
|
-
* @private
|
|
9945
|
-
*/
|
|
9946
|
-
_chp._render = function() {
|
|
9947
|
-
if (!this.element || !this.element.parentNode) return;
|
|
9948
|
-
var parent = this.element.parentNode;
|
|
9949
|
-
var nextSibling = this.element.nextSibling;
|
|
9950
|
-
|
|
9951
|
-
// Remove old DOM
|
|
9952
|
-
parent.removeChild(this.element);
|
|
9953
|
-
|
|
9954
|
-
// Re-prepare TACO with current state (deep clone preserving functions)
|
|
9955
|
-
this.taco = this._deepCloneTaco(this._originalTaco || this.taco);
|
|
9956
|
-
|
|
9957
|
-
// Re-compile bindings and prepare
|
|
9958
|
-
this._compileBindings();
|
|
9959
|
-
this._prepareTaco(this.taco);
|
|
9960
|
-
this._wireActions(this.taco);
|
|
9961
|
-
|
|
9962
|
-
var tacoForDOM = this._tacoForDOM(this.taco);
|
|
9963
|
-
this.element = bw.createDOM(tacoForDOM);
|
|
9964
|
-
this.element._bwComponentHandle = this;
|
|
9965
|
-
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
9966
|
-
|
|
9967
|
-
// Re-insert at same position
|
|
9968
|
-
if (nextSibling) {
|
|
9969
|
-
parent.insertBefore(this.element, nextSibling);
|
|
9970
|
-
} else {
|
|
9971
|
-
parent.appendChild(this.element);
|
|
9972
|
-
}
|
|
9973
|
-
|
|
9974
|
-
// Re-collect refs and apply all bindings
|
|
9975
|
-
this._collectRefs();
|
|
9976
|
-
this._resolveAndApplyAll();
|
|
9977
|
-
};
|
|
9978
|
-
|
|
9979
|
-
// ── Event & Pub/Sub Methods ──
|
|
9980
|
-
|
|
9981
|
-
/**
|
|
9982
|
-
* Add a DOM event listener on the component's root element.
|
|
9983
|
-
* @param {string} event - Event name (e.g., 'click')
|
|
9984
|
-
* @param {Function} handler - Event handler
|
|
9985
|
-
*/
|
|
9986
|
-
_chp.on = function(event, handler) {
|
|
9987
|
-
if (this.element) {
|
|
9988
|
-
this.element.addEventListener(event, handler);
|
|
9989
|
-
}
|
|
9990
|
-
this._eventListeners.push({ event: event, handler: handler });
|
|
9991
|
-
};
|
|
9992
|
-
|
|
9993
|
-
/**
|
|
9994
|
-
* Remove a DOM event listener.
|
|
9995
|
-
* @param {string} event - Event name
|
|
9996
|
-
* @param {Function} handler - Handler to remove
|
|
9997
|
-
*/
|
|
9998
|
-
_chp.off = function(event, handler) {
|
|
9999
|
-
if (this.element) {
|
|
10000
|
-
this.element.removeEventListener(event, handler);
|
|
10001
|
-
}
|
|
10002
|
-
this._eventListeners = this._eventListeners.filter(function(l) {
|
|
10003
|
-
return !(l.event === event && l.handler === handler);
|
|
10004
|
-
});
|
|
10005
|
-
};
|
|
10006
|
-
|
|
10007
|
-
/**
|
|
10008
|
-
* Subscribe to a pub/sub topic. Lifecycle-tied: auto-unsubs on destroy.
|
|
10009
|
-
* @param {string} topic - Topic name
|
|
10010
|
-
* @param {Function} handler - Handler function
|
|
10011
|
-
* @returns {Function} Unsubscribe function
|
|
10012
|
-
*/
|
|
10013
|
-
_chp.sub = function(topic, handler) {
|
|
10014
|
-
var unsub = bw.sub(topic, handler);
|
|
10015
|
-
this._subs.push(unsub);
|
|
10016
|
-
return unsub;
|
|
10017
|
-
};
|
|
10018
|
-
|
|
10019
|
-
/**
|
|
10020
|
-
* Call a named action.
|
|
10021
|
-
* @param {string} name - Action name
|
|
10022
|
-
* @param {...*} args - Arguments passed after comp
|
|
10023
|
-
*/
|
|
10024
|
-
_chp.action = function(name) {
|
|
10025
|
-
var fn = this._actions[name];
|
|
10026
|
-
if (!fn) {
|
|
10027
|
-
_cw('ComponentHandle.action: unknown action "' + name + '"');
|
|
10028
|
-
return;
|
|
10029
|
-
}
|
|
10030
|
-
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
|
|
10031
|
-
return fn.apply(null, args);
|
|
10032
|
-
};
|
|
10033
|
-
|
|
10034
|
-
/**
|
|
10035
|
-
* querySelector within the component's DOM.
|
|
10036
|
-
* @param {string} sel - CSS selector
|
|
10037
|
-
* @returns {Element|null}
|
|
10038
|
-
*/
|
|
10039
|
-
_chp.select = function(sel) {
|
|
10040
|
-
return this.element ? this.element.querySelector(sel) : null;
|
|
10041
|
-
};
|
|
10042
|
-
|
|
10043
|
-
/**
|
|
10044
|
-
* querySelectorAll within the component's DOM.
|
|
10045
|
-
* @param {string} sel - CSS selector
|
|
10046
|
-
* @returns {Element[]}
|
|
10047
|
-
*/
|
|
10048
|
-
_chp.selectAll = function(sel) {
|
|
10049
|
-
if (!this.element) return [];
|
|
10050
|
-
return Array.prototype.slice.call(this.element.querySelectorAll(sel));
|
|
10051
|
-
};
|
|
10052
|
-
|
|
10053
|
-
/**
|
|
10054
|
-
* Tag this component with a user-defined ID for addressing via bw.message().
|
|
10055
|
-
* The tag is added as a CSS class on the root element (DOM IS the registry).
|
|
10056
|
-
* @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
|
|
10057
|
-
* @returns {ComponentHandle} this (for chaining)
|
|
10058
|
-
*/
|
|
10059
|
-
_chp.userTag = function(tag) {
|
|
10060
|
-
this._userTag = tag;
|
|
10061
|
-
if (this.element) {
|
|
10062
|
-
this.element.classList.add(tag);
|
|
10063
|
-
}
|
|
10064
|
-
return this;
|
|
10065
|
-
};
|
|
10066
|
-
|
|
10067
|
-
// Expose ComponentHandle on bw (for testing and advanced use)
|
|
10068
|
-
bw._ComponentHandle = ComponentHandle;
|
|
10069
|
-
|
|
10070
|
-
// ===================================================================================
|
|
10071
|
-
// Control Flow Helpers
|
|
10072
|
-
// ===================================================================================
|
|
10073
|
-
|
|
10074
|
-
/**
|
|
10075
|
-
* Conditional rendering helper.
|
|
10076
|
-
* Returns a marker object that ComponentHandle detects during binding compilation.
|
|
10077
|
-
* In static contexts (bw.html with state), evaluates immediately.
|
|
10078
|
-
*
|
|
10079
|
-
* @param {string} expr - Expression string like '${loggedIn}'
|
|
10080
|
-
* @param {Object} tacoTrue - TACO to render when truthy
|
|
10081
|
-
* @param {Object} [tacoFalse] - TACO to render when falsy
|
|
10082
|
-
* @returns {Object} Marker object with _bwWhen flag
|
|
10083
|
-
* @category Component
|
|
10084
|
-
*/
|
|
10085
|
-
bw.when = function(expr, tacoTrue, tacoFalse) {
|
|
10086
|
-
return { _bwWhen: true, expr: expr, branches: [tacoTrue, tacoFalse || null] };
|
|
10087
|
-
};
|
|
10088
|
-
|
|
10089
|
-
/**
|
|
10090
|
-
* List rendering helper.
|
|
10091
|
-
* Returns a marker object that ComponentHandle detects during binding compilation.
|
|
10092
|
-
*
|
|
10093
|
-
* @param {string} expr - Expression string like '${items}'
|
|
10094
|
-
* @param {Function} fn - Factory function(item, index) returning TACO
|
|
10095
|
-
* @returns {Object} Marker object with _bwEach flag
|
|
10096
|
-
* @category Component
|
|
10097
|
-
*/
|
|
10098
|
-
bw.each = function(expr, fn) {
|
|
10099
|
-
return { _bwEach: true, expr: expr, factory: fn };
|
|
10100
|
-
};
|
|
10101
|
-
|
|
10102
|
-
// ===================================================================================
|
|
10103
|
-
// bw.component() — Factory for ComponentHandle
|
|
10104
|
-
// ===================================================================================
|
|
10105
|
-
|
|
10106
|
-
/**
|
|
10107
|
-
* Create a ComponentHandle from a TACO definition.
|
|
10108
|
-
* The returned handle has .get(), .set(), .mount(), .destroy(), etc.
|
|
10109
|
-
*
|
|
10110
|
-
* @param {Object} taco - TACO definition with {t, a, c, o}
|
|
10111
|
-
* @returns {ComponentHandle} Reactive component handle
|
|
10112
|
-
* @category Component
|
|
10113
|
-
* @see bw.DOM
|
|
10114
|
-
* @example
|
|
10115
|
-
* var counter = bw.component({
|
|
10116
|
-
* t: 'div', c: [{ t: 'h3', c: 'Count: ${count}' }],
|
|
10117
|
-
* o: { state: { count: 0 } }
|
|
10118
|
-
* });
|
|
10119
|
-
* bw.DOM('#app', counter);
|
|
10120
|
-
* counter.set('count', 42); // DOM auto-updates
|
|
10121
|
-
*/
|
|
10122
|
-
bw.component = function(taco) {
|
|
10123
|
-
return new ComponentHandle(taco);
|
|
10124
|
-
};
|
|
10125
|
-
|
|
10126
|
-
// ===================================================================================
|
|
10127
|
-
// bw.message() — SendMessage() for the web
|
|
10128
|
-
// ===================================================================================
|
|
10129
|
-
|
|
10130
|
-
/**
|
|
10131
|
-
* Dispatch a message to a component by UUID or user tag.
|
|
10132
|
-
* Finds the component's DOM element, looks up its ComponentHandle,
|
|
10133
|
-
* and calls the named method. This is the bitwrench equivalent of
|
|
10134
|
-
* Win32 SendMessage(hwnd, msg, wParam, lParam).
|
|
10135
|
-
*
|
|
10136
|
-
* @param {string} target - Component UUID (bw_uuid_*), comp ID (data-bw_comp_id), or user tag (CSS class)
|
|
10137
|
-
* @param {string} action - Method name to call on the component
|
|
10138
|
-
* @param {*} data - Data to pass to the method
|
|
10139
|
-
* @returns {boolean} True if message was dispatched successfully
|
|
10140
|
-
* @category Component
|
|
10141
|
-
* @example
|
|
10142
|
-
* // Tag a component
|
|
10143
|
-
* myDash.userTag('dashboard_prod');
|
|
10144
|
-
* // Dispatch locally
|
|
10145
|
-
* bw.message('dashboard_prod', 'addAlert', { severity: 'warning', text: 'CPU spike' });
|
|
10146
|
-
* // Or from SSE handler:
|
|
10147
|
-
* es.onmessage = function(e) {
|
|
10148
|
-
* var msg = JSON.parse(e.data);
|
|
10149
|
-
* bw.message(msg.target, msg.action, msg.data);
|
|
10150
|
-
* };
|
|
10151
|
-
*/
|
|
10152
|
-
bw.message = function(target, action, data) {
|
|
10153
|
-
// Try bw._el() first (handles UUID class, nodeMap cache, getElementById)
|
|
10154
|
-
var el = bw._el(target);
|
|
10155
|
-
// Then try data-bw_comp_id attribute
|
|
10156
|
-
if (!el || !el._bwComponentHandle) {
|
|
10157
|
-
el = bw.$('[data-bw_comp_id="' + target + '"]')[0];
|
|
10158
|
-
}
|
|
10159
|
-
// Then try CSS class (user tag)
|
|
10160
|
-
if (!el || !el._bwComponentHandle) {
|
|
10161
|
-
el = bw.$('.' + target)[0];
|
|
10162
|
-
}
|
|
10163
|
-
if (!el || !el._bwComponentHandle) return false;
|
|
10164
|
-
var comp = el._bwComponentHandle;
|
|
10165
|
-
if (!_is(comp[action], 'function')) {
|
|
10166
|
-
_cw('bw.message: unknown action "' + action + '" on component ' + target);
|
|
10167
|
-
return false;
|
|
10168
|
-
}
|
|
10169
|
-
comp[action](data);
|
|
10170
|
-
return true;
|
|
10171
|
-
};
|
|
10172
|
-
|
|
10173
|
-
// ===================================================================================
|
|
10174
|
-
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
10175
|
-
// ===================================================================================
|
|
10176
|
-
|
|
10177
|
-
/**
|
|
10178
|
-
* Registry of named functions sent via register messages.
|
|
10179
|
-
* Populated by bw.apply({ type: 'register', name, body }).
|
|
10180
|
-
* Invoked by bw.apply({ type: 'call', name, args }).
|
|
10181
|
-
* @private
|
|
10182
|
-
*/
|
|
10183
|
-
bw._clientFunctions = {};
|
|
10184
|
-
|
|
10185
|
-
/**
|
|
10186
|
-
* Whether exec messages are allowed. Set by bwclient connect opts.allowExec.
|
|
10187
|
-
* Default false — exec messages are rejected unless explicitly opted in.
|
|
10188
|
-
* @private
|
|
10189
|
-
*/
|
|
10190
|
-
bw._allowExec = false;
|
|
10191
|
-
|
|
10192
|
-
/**
|
|
10193
|
-
* Parse a bwserve protocol message string, supporting both strict JSON
|
|
10194
|
-
* and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
|
|
10195
|
-
*
|
|
10196
|
-
* The r-prefix format is designed for C/C++ string literals where
|
|
10197
|
-
* double-quote escaping is painful. The parser is a state machine
|
|
10198
|
-
* that walks character by character — not a regex replace.
|
|
10199
|
-
*
|
|
10200
|
-
* Escaping: apostrophes inside single-quoted values must be escaped
|
|
10201
|
-
* with backslash: r{'name':'Barry\'s room'}
|
|
10202
|
-
*
|
|
10203
|
-
* @param {string} str - JSON or r-prefixed relaxed JSON string
|
|
10204
|
-
* @returns {Object} Parsed message object
|
|
10205
|
-
* @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
|
|
10206
|
-
* @category Core
|
|
9145
|
+
* Parse a bwserve protocol message string, supporting both strict JSON
|
|
9146
|
+
* and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
|
|
9147
|
+
*
|
|
9148
|
+
* The r-prefix format is designed for C/C++ string literals where
|
|
9149
|
+
* double-quote escaping is painful. The parser is a state machine
|
|
9150
|
+
* that walks character by character — not a regex replace.
|
|
9151
|
+
*
|
|
9152
|
+
* Escaping: apostrophes inside single-quoted values must be escaped
|
|
9153
|
+
* with backslash: r{'name':'Barry\'s room'}
|
|
9154
|
+
*
|
|
9155
|
+
* @param {string} str - JSON or r-prefixed relaxed JSON string
|
|
9156
|
+
* @returns {Object} Parsed message object
|
|
9157
|
+
* @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
|
|
9158
|
+
* @category Core
|
|
10207
9159
|
*/
|
|
10208
9160
|
bw.parseJSONFlex = function(str) {
|
|
10209
9161
|
str = (str || '').trim();
|
|
@@ -10392,132 +9344,29 @@ bw.apply = function(msg) {
|
|
|
10392
9344
|
// ===================================================================================
|
|
10393
9345
|
|
|
10394
9346
|
/**
|
|
10395
|
-
* Inspect a
|
|
10396
|
-
* Works with DOM elements
|
|
10397
|
-
* Returns the ComponentHandle for console chaining.
|
|
9347
|
+
* Inspect a DOM element's bitwrench state, handle methods, and metadata.
|
|
9348
|
+
* Works with DOM elements or CSS selectors.
|
|
10398
9349
|
*
|
|
10399
|
-
* @param {string|Element
|
|
10400
|
-
* @returns {
|
|
9350
|
+
* @param {string|Element} target - Selector or DOM element
|
|
9351
|
+
* @returns {Element|null} The element, or null if not found
|
|
10401
9352
|
* @category Component
|
|
10402
9353
|
* @example
|
|
10403
|
-
*
|
|
9354
|
+
* bw.inspect('#my-carousel');
|
|
10404
9355
|
* bw.inspect($0);
|
|
10405
|
-
* // Or by selector:
|
|
10406
|
-
* var h = bw.inspect('#my-dashboard');
|
|
10407
|
-
* h.set('count', 99); // chain from returned handle
|
|
10408
9356
|
*/
|
|
10409
9357
|
bw.inspect = function(target) {
|
|
10410
|
-
var el = target;
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
10417
|
-
el = bw.$(target)[0];
|
|
10418
|
-
}
|
|
10419
|
-
if (!el) {
|
|
10420
|
-
_cw('bw.inspect: element not found');
|
|
10421
|
-
return null;
|
|
10422
|
-
}
|
|
10423
|
-
comp = el._bwComponentHandle;
|
|
10424
|
-
}
|
|
10425
|
-
if (!comp) {
|
|
10426
|
-
_cl('bw.inspect: no ComponentHandle on this element');
|
|
10427
|
-
_cl(' Tag:', el.tagName);
|
|
10428
|
-
_cl(' Classes:', el.className);
|
|
10429
|
-
_cl(' _bw_state:', el._bw_state || '(none)');
|
|
10430
|
-
return null;
|
|
10431
|
-
}
|
|
10432
|
-
var deps = comp._bindings.reduce(function(s, b) {
|
|
10433
|
-
return s.concat(b.deps || []);
|
|
10434
|
-
}, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
|
|
10435
|
-
console.group('Component: ' + comp._bwId);
|
|
10436
|
-
_cl('State:', comp._state);
|
|
10437
|
-
_cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
|
|
10438
|
-
_cl('Methods:', _keys(comp._methods));
|
|
10439
|
-
_cl('Actions:', _keys(comp._actions));
|
|
10440
|
-
_cl('User tag:', comp._userTag || '(none)');
|
|
10441
|
-
_cl('Mounted:', comp.mounted);
|
|
10442
|
-
_cl('Element:', comp.element);
|
|
9358
|
+
var el = _is(target, 'string') ? bw.$(target)[0] : target;
|
|
9359
|
+
if (!el) { _cw('bw.inspect: element not found'); return null; }
|
|
9360
|
+
console.group('Element: ' + (bw.getUUID(el) || el.id || el.tagName));
|
|
9361
|
+
_cl('State:', el._bw_state || '(none)');
|
|
9362
|
+
_cl('Handle:', el.bw ? _keys(el.bw) : '(none)');
|
|
9363
|
+
_cl('Classes:', el.className);
|
|
9364
|
+
_cl('Refs:', el._bw_refs || '(none)');
|
|
10443
9365
|
console.groupEnd();
|
|
10444
|
-
return
|
|
9366
|
+
return el;
|
|
10445
9367
|
};
|
|
10446
9368
|
|
|
10447
|
-
|
|
10448
|
-
// bw.compile() — Pre-compile TACO into optimized factory
|
|
10449
|
-
// ===================================================================================
|
|
10450
|
-
|
|
10451
|
-
/**
|
|
10452
|
-
* Pre-compile a TACO definition into a factory function.
|
|
10453
|
-
* The factory produces ComponentHandles with pre-compiled binding evaluators.
|
|
10454
|
-
*
|
|
10455
|
-
* Phase 1: validates API surface. Template cloning optimization deferred.
|
|
10456
|
-
*
|
|
10457
|
-
* @param {Object} taco - TACO definition
|
|
10458
|
-
* @returns {Function} Factory function(initialState?) → ComponentHandle
|
|
10459
|
-
* @category Component
|
|
10460
|
-
*/
|
|
10461
|
-
bw.compile = function(taco) {
|
|
10462
|
-
// Pre-extract all binding expressions
|
|
10463
|
-
var precompiled = [];
|
|
10464
|
-
function walkExpressions(node) {
|
|
10465
|
-
if (!_is(node, 'object')) return;
|
|
10466
|
-
if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
|
|
10467
|
-
var parsed = bw._parseBindings(node.c);
|
|
10468
|
-
for (var i = 0; i < parsed.length; i++) {
|
|
10469
|
-
try {
|
|
10470
|
-
precompiled.push({
|
|
10471
|
-
expr: parsed[i].expr,
|
|
10472
|
-
fn: new Function('state', 'with(state){return (' + parsed[i].expr + ');}')
|
|
10473
|
-
});
|
|
10474
|
-
} catch(e) {
|
|
10475
|
-
precompiled.push({ expr: parsed[i].expr, fn: function() { return ''; } });
|
|
10476
|
-
}
|
|
10477
|
-
}
|
|
10478
|
-
}
|
|
10479
|
-
if (node.a) {
|
|
10480
|
-
for (var key in node.a) {
|
|
10481
|
-
if (_hop.call(node.a, key)) {
|
|
10482
|
-
var v = node.a[key];
|
|
10483
|
-
if (_is(v, 'string') && v.indexOf('${') >= 0) {
|
|
10484
|
-
var parsed2 = bw._parseBindings(v);
|
|
10485
|
-
for (var j = 0; j < parsed2.length; j++) {
|
|
10486
|
-
try {
|
|
10487
|
-
precompiled.push({
|
|
10488
|
-
expr: parsed2[j].expr,
|
|
10489
|
-
fn: new Function('state', 'with(state){return (' + parsed2[j].expr + ');}')
|
|
10490
|
-
});
|
|
10491
|
-
} catch(e2) {
|
|
10492
|
-
precompiled.push({ expr: parsed2[j].expr, fn: function() { return ''; } });
|
|
10493
|
-
}
|
|
10494
|
-
}
|
|
10495
|
-
}
|
|
10496
|
-
}
|
|
10497
|
-
}
|
|
10498
|
-
}
|
|
10499
|
-
if (_isA(node.c)) {
|
|
10500
|
-
for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
|
|
10501
|
-
} else if (_is(node.c, 'object') && node.c.t) {
|
|
10502
|
-
walkExpressions(node.c);
|
|
10503
|
-
}
|
|
10504
|
-
}
|
|
10505
|
-
walkExpressions(taco);
|
|
10506
|
-
|
|
10507
|
-
return function(initialState) {
|
|
10508
|
-
var handle = new ComponentHandle(taco);
|
|
10509
|
-
handle._compile = true;
|
|
10510
|
-
handle._precompiledBindings = precompiled;
|
|
10511
|
-
if (initialState) {
|
|
10512
|
-
for (var k in initialState) {
|
|
10513
|
-
if (_hop.call(initialState, k)) {
|
|
10514
|
-
handle._state[k] = initialState[k];
|
|
10515
|
-
}
|
|
10516
|
-
}
|
|
10517
|
-
}
|
|
10518
|
-
return handle;
|
|
10519
|
-
};
|
|
10520
|
-
};
|
|
9369
|
+
bw.compile = function() { throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
10521
9370
|
|
|
10522
9371
|
/**
|
|
10523
9372
|
* Generate CSS from JavaScript objects.
|
|
@@ -11743,8 +10592,8 @@ bw.render = function(element, position, taco) {
|
|
|
11743
10592
|
};
|
|
11744
10593
|
}
|
|
11745
10594
|
|
|
11746
|
-
// Generate unique
|
|
11747
|
-
const componentId = taco.o?.id || bw.uuid();
|
|
10595
|
+
// Generate unique UUID class if not provided
|
|
10596
|
+
const componentId = taco.o?.id || bw.uuid('uuid');
|
|
11748
10597
|
|
|
11749
10598
|
// Create DOM element
|
|
11750
10599
|
let domElement;
|
|
@@ -11759,9 +10608,10 @@ bw.render = function(element, position, taco) {
|
|
|
11759
10608
|
};
|
|
11760
10609
|
}
|
|
11761
10610
|
|
|
11762
|
-
// Add component ID
|
|
11763
|
-
domElement.
|
|
11764
|
-
|
|
10611
|
+
// Add component ID as class + lifecycle marker
|
|
10612
|
+
domElement.classList.add(componentId);
|
|
10613
|
+
domElement.classList.add(_BW_LC);
|
|
10614
|
+
|
|
11765
10615
|
// Insert into DOM based on position
|
|
11766
10616
|
try {
|
|
11767
10617
|
switch(position) {
|
|
@@ -11835,7 +10685,8 @@ bw.render = function(element, position, taco) {
|
|
|
11835
10685
|
|
|
11836
10686
|
// Re-render
|
|
11837
10687
|
const newElement = bw.createDOM(this._taco);
|
|
11838
|
-
newElement.
|
|
10688
|
+
newElement.classList.add(componentId);
|
|
10689
|
+
newElement.classList.add(_BW_LC);
|
|
11839
10690
|
|
|
11840
10691
|
// Replace in DOM
|
|
11841
10692
|
parent.replaceChild(newElement, this.element);
|
|
@@ -12022,13 +10873,12 @@ bw.BCCL = BCCL;
|
|
|
12022
10873
|
// Variant class helper: bw.variantClass('primary') → 'bw_primary'
|
|
12023
10874
|
bw.variantClass = variantClass;
|
|
12024
10875
|
|
|
12025
|
-
// Create functions that return
|
|
10876
|
+
// Create functions that return DOM elements (createCard, createTable, etc.)
|
|
12026
10877
|
Object.entries(components).forEach(([name, fn]) => {
|
|
12027
10878
|
if (name.startsWith('make')) {
|
|
12028
|
-
const createName = 'create' + name.substring(4);
|
|
10879
|
+
const createName = 'create' + name.substring(4);
|
|
12029
10880
|
bw[createName] = function(props) {
|
|
12030
|
-
|
|
12031
|
-
return bw.renderComponent(taco);
|
|
10881
|
+
return bw.createDOM(fn(props));
|
|
12032
10882
|
};
|
|
12033
10883
|
}
|
|
12034
10884
|
});
|