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