bitwrench 2.0.17 → 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 +169 -75
- package/dist/bitwrench-bccl.cjs.js +228 -55
- package/dist/bitwrench-bccl.cjs.min.js +3 -3
- package/dist/bitwrench-bccl.esm.js +228 -55
- package/dist/bitwrench-bccl.esm.min.js +3 -3
- package/dist/bitwrench-bccl.umd.js +228 -55
- 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 +1190 -2348
- package/dist/bitwrench-lean.cjs.min.js +20 -20
- package/dist/bitwrench-lean.es5.js +1285 -2551
- package/dist/bitwrench-lean.es5.min.js +18 -18
- package/dist/bitwrench-lean.esm.js +1190 -2348
- package/dist/bitwrench-lean.esm.min.js +20 -20
- package/dist/bitwrench-lean.umd.js +1190 -2348
- package/dist/bitwrench-lean.umd.min.js +20 -20
- package/dist/bitwrench-util-css.cjs.js +236 -0
- package/dist/bitwrench-util-css.cjs.min.js +22 -0
- package/dist/bitwrench-util-css.es5.js +414 -0
- package/dist/bitwrench-util-css.es5.min.js +21 -0
- package/dist/bitwrench-util-css.esm.js +230 -0
- package/dist/bitwrench-util-css.esm.min.js +21 -0
- package/dist/bitwrench-util-css.umd.js +242 -0
- package/dist/bitwrench-util-css.umd.min.js +21 -0
- package/dist/bitwrench.cjs.js +1404 -2388
- package/dist/bitwrench.cjs.min.js +21 -21
- package/dist/bitwrench.css +503 -132
- package/dist/bitwrench.es5.js +1588 -2659
- package/dist/bitwrench.es5.min.js +19 -19
- package/dist/bitwrench.esm.js +1405 -2389
- package/dist/bitwrench.esm.min.js +21 -21
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +1404 -2388
- package/dist/bitwrench.umd.min.js +21 -21
- package/dist/builds.json +214 -104
- package/dist/bwserve.cjs.js +514 -68
- package/dist/bwserve.esm.js +513 -69
- package/dist/sri.json +46 -36
- package/package.json +6 -3
- package/readme.html +183 -85
- package/src/bitwrench-bccl-entry.js +3 -4
- package/src/bitwrench-bccl.js +224 -50
- package/src/bitwrench-code-edit.js +6 -8
- package/src/bitwrench-color-utils.js +31 -9
- package/src/bitwrench-debug.js +245 -0
- package/src/bitwrench-esm-entry.js +11 -0
- package/src/bitwrench-styles.js +474 -240
- package/src/bitwrench-util-css.js +229 -0
- package/src/bitwrench.js +689 -2042
- package/src/bwserve/attach.js +57 -0
- package/src/bwserve/bwclient.js +141 -0
- package/src/bwserve/bwshell.js +102 -0
- package/src/bwserve/client.js +151 -1
- package/src/bwserve/index.js +127 -28
- package/src/cli/attach.js +587 -0
- package/src/cli/convert.js +2 -5
- package/src/cli/index.js +7 -0
- package/src/cli/inject.js +1 -1
- package/src/cli/serve.js +185 -5
- package/src/generate-css.js +11 -4
- package/src/vendor/html2canvas.min.js +20 -0
- package/src/version.js +3 -3
- package/src/bwserve/shell.js +0 -106
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
/*! bitwrench-lean v2.0.
|
|
1
|
+
/*! bitwrench-lean 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
|
/**
|
|
@@ -306,13 +306,18 @@ function harmonize(sourceHex, targetHex, amount) {
|
|
|
306
306
|
*/
|
|
307
307
|
function deriveShades(hex) {
|
|
308
308
|
var rgb = colorParse(hex);
|
|
309
|
+
// For light input colors (L > 75), mixing toward white produces invisible borders.
|
|
310
|
+
// Darken instead so borders remain visible against light backgrounds.
|
|
311
|
+
var borderColor = hexToHsl(hex)[2] > 75
|
|
312
|
+
? adjustLightness(hex, -18)
|
|
313
|
+
: mixColor(hex, '#ffffff', 0.60);
|
|
309
314
|
return {
|
|
310
315
|
base: hex,
|
|
311
316
|
hover: adjustLightness(hex, -10),
|
|
312
317
|
active: adjustLightness(hex, -15),
|
|
313
318
|
light: mixColor(hex, '#ffffff', 0.85),
|
|
314
319
|
darkText: adjustLightness(hex, -40),
|
|
315
|
-
border:
|
|
320
|
+
border: borderColor,
|
|
316
321
|
focus: 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',0.25)',
|
|
317
322
|
textOn: textOnColor(hex)
|
|
318
323
|
};
|
|
@@ -371,19 +376,27 @@ function deriveAlternateConfig(config) {
|
|
|
371
376
|
alt.secondary = deriveAlternateSeed(config.secondary);
|
|
372
377
|
alt.tertiary = config.tertiary ? deriveAlternateSeed(config.tertiary) : alt.primary;
|
|
373
378
|
|
|
374
|
-
// Derive alternate surface colors from primary hue
|
|
379
|
+
// Derive alternate surface colors from primary hue.
|
|
380
|
+
// Check actual page surface brightness (not seed color brightness) to decide
|
|
381
|
+
// whether alternate should be dark or light. The page surface is what the
|
|
382
|
+
// user sees; seeds can be dark while the page is still light (default L=96).
|
|
375
383
|
var priHsl = hexToHsl(config.primary);
|
|
376
384
|
var h = priHsl[0];
|
|
377
|
-
var
|
|
385
|
+
var primarySurface = config.surface || hslToHex([h, 8, 96]);
|
|
386
|
+
var isLight = relativeLuminance(primarySurface) > 0.179;
|
|
378
387
|
|
|
379
388
|
if (isLight) {
|
|
380
|
-
//
|
|
389
|
+
// Page surface is light → alternate needs dark surfaces
|
|
381
390
|
alt.light = hslToHex([h, Math.min(priHsl[1], 15), 15]);
|
|
382
391
|
alt.dark = hslToHex([h, 5, 88]);
|
|
392
|
+
alt.surface = hslToHex([h, 12, 18]);
|
|
393
|
+
alt.background = hslToHex([h, 10, 14]);
|
|
383
394
|
} else {
|
|
384
|
-
//
|
|
395
|
+
// Page surface is dark → alternate needs light surfaces
|
|
385
396
|
alt.light = hslToHex([h, Math.min(priHsl[1], 10), 96]);
|
|
386
397
|
alt.dark = hslToHex([h, 10, 18]);
|
|
398
|
+
alt.surface = hslToHex([h, 8, 96]);
|
|
399
|
+
alt.background = hslToHex([h, 6, 98]);
|
|
387
400
|
}
|
|
388
401
|
|
|
389
402
|
// Semantic colors: harmonize toward primary, then invert for alternate
|
|
@@ -431,10 +444,18 @@ function derivePalette(config) {
|
|
|
431
444
|
var darkBase = config.dark || hslToHex([h, 10, 13]);
|
|
432
445
|
|
|
433
446
|
// Background & surface tokens — tinted with primary hue for theme personality.
|
|
434
|
-
//
|
|
447
|
+
// Saturation high enough that the hue is visible (each theme feels distinct)
|
|
448
|
+
// but low enough to stay neutral and readable.
|
|
435
449
|
// User can override with config.background / config.surface.
|
|
436
|
-
var bgBase = config.background || hslToHex([h,
|
|
437
|
-
var surfBase = config.surface || hslToHex([h,
|
|
450
|
+
var bgBase = config.background || hslToHex([h, 22, 96]);
|
|
451
|
+
var surfBase = config.surface || hslToHex([h, 25, 94]);
|
|
452
|
+
|
|
453
|
+
// surfaceAlt: subtle background variant for striped rows, hover states, headers.
|
|
454
|
+
// Slightly lighter than surface in dark mode, slightly darker in light mode.
|
|
455
|
+
var surfHsl = hexToHsl(surfBase);
|
|
456
|
+
var surfAlt = surfHsl[2] <= 50
|
|
457
|
+
? hslToHex([surfHsl[0], surfHsl[1], Math.min(surfHsl[2] + 8, 100)])
|
|
458
|
+
: hslToHex([surfHsl[0], surfHsl[1], Math.max(surfHsl[2] - 3, 0)]);
|
|
438
459
|
|
|
439
460
|
var palette = {
|
|
440
461
|
primary: deriveShades(config.primary),
|
|
@@ -447,7 +468,8 @@ function derivePalette(config) {
|
|
|
447
468
|
light: deriveShades(lightBase),
|
|
448
469
|
dark: deriveShades(darkBase),
|
|
449
470
|
background: bgBase,
|
|
450
|
-
surface: surfBase
|
|
471
|
+
surface: surfBase,
|
|
472
|
+
surfaceAlt: surfAlt
|
|
451
473
|
};
|
|
452
474
|
|
|
453
475
|
return palette;
|
|
@@ -494,10 +516,12 @@ var SPACING_SCALE = {
|
|
|
494
516
|
5: '1.5rem', // 24px
|
|
495
517
|
6: '2rem'};
|
|
496
518
|
|
|
519
|
+
let _S=SPACING_SCALE;
|
|
520
|
+
|
|
497
521
|
var SPACING_PRESETS = {
|
|
498
|
-
compact: { btn:
|
|
499
|
-
normal: { btn:
|
|
500
|
-
spacious: { btn:
|
|
522
|
+
compact: { btn: _S[1] + ' ' + _S[3], card: _S[3] + ' ' + _S[4], alert: _S[2] + ' ' + _S[4], cell: _S[2] + ' ' + _S[3], input: _S[1] + ' ' + _S[3] },
|
|
523
|
+
normal: { btn: _S[2] + ' ' + _S[4], card: _S[5] + ' ' + _S[5], alert: _S[3] + ' ' + _S[5], cell: _S[3] + ' ' + _S[4], input: _S[2] + ' ' + _S[3] },
|
|
524
|
+
spacious: { btn: _S[3] + ' ' + _S[5], card: _S[6] + ' ' + _S[6], alert: _S[4] + ' ' + _S[5], cell: _S[4] + ' ' + _S[5], input: _S[3] + ' ' + _S[4] }
|
|
501
525
|
};
|
|
502
526
|
|
|
503
527
|
var RADIUS_PRESETS = {
|
|
@@ -609,20 +633,14 @@ var DEFAULT_PALETTE_CONFIG = {
|
|
|
609
633
|
* Built-in theme presets — named color combinations
|
|
610
634
|
* Each preset provides primary, secondary, and tertiary seed colors.
|
|
611
635
|
*/
|
|
612
|
-
var THEME_PRESETS =
|
|
613
|
-
teal
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
amber: { primary: '#d97706', secondary: '#fbbf24', tertiary: '#f59e0b' },
|
|
621
|
-
emerald: { primary: '#059669', secondary: '#6ee7b7', tertiary: '#34d399' },
|
|
622
|
-
nord: { primary: '#5e81ac', secondary: '#88c0d0', tertiary: '#81a1c1' },
|
|
623
|
-
coral: { primary: '#ef6461', secondary: '#4a7c7e', tertiary: '#e8a87c' },
|
|
624
|
-
midnight: { primary: '#1e3a5f', secondary: '#7c8db5', tertiary: '#3d5a80' }
|
|
625
|
-
};
|
|
636
|
+
var THEME_PRESETS = Object.fromEntries([
|
|
637
|
+
['teal','#006666','#6c757d','#006666'],['ocean','#0077b6','#90e0ef','#00b4d8'],
|
|
638
|
+
['sunset','#e76f51','#264653','#e9c46a'],['forest','#2d6a4f','#95d5b2','#52b788'],
|
|
639
|
+
['slate','#343a40','#adb5bd','#6c757d'],['rose','#e11d48','#fda4af','#fb7185'],
|
|
640
|
+
['indigo','#4f46e5','#a5b4fc','#818cf8'],['amber','#d97706','#fbbf24','#f59e0b'],
|
|
641
|
+
['emerald','#059669','#6ee7b7','#34d399'],['nord','#5e81ac','#88c0d0','#81a1c1'],
|
|
642
|
+
['coral','#ef6461','#4a7c7e','#e8a87c'],['midnight','#1e3a5f','#7c8db5','#3d5a80']
|
|
643
|
+
].map(function(e) { return [e[0], {primary:e[1],secondary:e[2],tertiary:e[3]}]; }));
|
|
626
644
|
|
|
627
645
|
/**
|
|
628
646
|
* Resolve layout config to spacing, radius, typeScale, elevation, and motion objects.
|
|
@@ -669,6 +687,7 @@ function scopeSelector(name, sel) {
|
|
|
669
687
|
if (sel.includes(',')) return sel.split(',').map(function(s) { return '.' + name + ' ' + s.trim(); }).join(', ');
|
|
670
688
|
return '.' + name + ' ' + sel;
|
|
671
689
|
}
|
|
690
|
+
var _sx=scopeSelector;
|
|
672
691
|
|
|
673
692
|
// =========================================================================
|
|
674
693
|
// Themed CSS generators
|
|
@@ -677,12 +696,12 @@ function scopeSelector(name, sel) {
|
|
|
677
696
|
function generateTypographyThemed(scope, palette, layout) {
|
|
678
697
|
var mot = layout.motion;
|
|
679
698
|
var rules = {};
|
|
680
|
-
rules[
|
|
699
|
+
rules[_sx(scope, 'a')] = {
|
|
681
700
|
'color': palette.primary.base,
|
|
682
701
|
'text-decoration': 'none',
|
|
683
702
|
'transition': 'color ' + mot.fast + ' ' + mot.easing
|
|
684
703
|
};
|
|
685
|
-
rules[
|
|
704
|
+
rules[_sx(scope, 'a:hover')] = {
|
|
686
705
|
'color': palette.primary.hover,
|
|
687
706
|
'text-decoration': 'underline'
|
|
688
707
|
};
|
|
@@ -695,11 +714,11 @@ function generateButtons(scope, palette, layout) {
|
|
|
695
714
|
var rd = layout.radius;
|
|
696
715
|
|
|
697
716
|
// Base button (only when scoped — unscoped uses defaultStyles)
|
|
698
|
-
rules[
|
|
717
|
+
rules[_sx(scope, '.bw_btn')] = {
|
|
699
718
|
'padding': sp.btn,
|
|
700
719
|
'border-radius': rd.btn
|
|
701
720
|
};
|
|
702
|
-
rules[
|
|
721
|
+
rules[_sx(scope, '.bw_btn:focus-visible')] = {
|
|
703
722
|
'outline': '2px solid currentColor',
|
|
704
723
|
'outline-offset': '2px',
|
|
705
724
|
'box-shadow': '0 0 0 3px ' + palette.primary.focus
|
|
@@ -708,12 +727,12 @@ function generateButtons(scope, palette, layout) {
|
|
|
708
727
|
// Variant colors handled by palette class on component root
|
|
709
728
|
|
|
710
729
|
// Size variants (structural, reuse layout radius)
|
|
711
|
-
rules[
|
|
730
|
+
rules[_sx(scope, '.bw_btn_lg')] = {
|
|
712
731
|
'padding': '0.625rem 1.5rem',
|
|
713
732
|
'font-size': '1rem',
|
|
714
733
|
'border-radius': rd.btn === '50rem' ? '50rem' : (parseInt(rd.btn) + 2) + 'px'
|
|
715
734
|
};
|
|
716
|
-
rules[
|
|
735
|
+
rules[_sx(scope, '.bw_btn_sm')] = {
|
|
717
736
|
'padding': '0.25rem 0.75rem',
|
|
718
737
|
'font-size': '0.8125rem',
|
|
719
738
|
'border-radius': rd.btn === '50rem' ? '50rem' : (Math.max(parseInt(rd.btn) - 1, 0)) + 'px'
|
|
@@ -727,7 +746,7 @@ function generateAlerts(scope, palette, layout) {
|
|
|
727
746
|
var sp = layout.spacing;
|
|
728
747
|
var rd = layout.radius;
|
|
729
748
|
|
|
730
|
-
rules[
|
|
749
|
+
rules[_sx(scope, '.bw_alert')] = {
|
|
731
750
|
'padding': sp.alert,
|
|
732
751
|
'border-radius': rd.alert
|
|
733
752
|
};
|
|
@@ -746,36 +765,36 @@ function generateCards(scope, palette, layout) {
|
|
|
746
765
|
|
|
747
766
|
var elev = layout.elevation;
|
|
748
767
|
var motion = layout.motion;
|
|
749
|
-
rules[
|
|
768
|
+
rules[_sx(scope, '.bw_card')] = {
|
|
750
769
|
'background-color': palette.surface || '#fff',
|
|
751
770
|
'border': '1px solid ' + palette.light.border,
|
|
752
771
|
'border-radius': rd.card,
|
|
753
772
|
'box-shadow': elev.sm,
|
|
754
773
|
'transition': 'box-shadow ' + motion.normal + ' ' + motion.easing + ', transform ' + motion.normal + ' ' + motion.easing
|
|
755
774
|
};
|
|
756
|
-
rules[
|
|
775
|
+
rules[_sx(scope, '.bw_card:hover')] = {
|
|
757
776
|
'box-shadow': elev.md
|
|
758
777
|
};
|
|
759
|
-
rules[
|
|
778
|
+
rules[_sx(scope, '.bw_card_hoverable:hover')] = {
|
|
760
779
|
'box-shadow': elev.lg
|
|
761
780
|
};
|
|
762
|
-
rules[
|
|
781
|
+
rules[_sx(scope, '.bw_card_body')] = {
|
|
763
782
|
'padding': sp.card
|
|
764
783
|
};
|
|
765
|
-
rules[
|
|
784
|
+
rules[_sx(scope, '.bw_card_header')] = {
|
|
766
785
|
'padding': sp.card.split(' ').map(function(v) { return (parseFloat(v) * 0.7).toFixed(3).replace(/\.?0+$/, '') + 'rem'; }).join(' '),
|
|
767
|
-
'background-color': palette.
|
|
786
|
+
'background-color': palette.surfaceAlt,
|
|
768
787
|
'border-bottom': '1px solid ' + palette.light.border
|
|
769
788
|
};
|
|
770
|
-
rules[
|
|
771
|
-
'background-color': palette.
|
|
789
|
+
rules[_sx(scope, '.bw_card_footer')] = {
|
|
790
|
+
'background-color': palette.surfaceAlt,
|
|
772
791
|
'border-top': '1px solid ' + palette.light.border,
|
|
773
792
|
'color': palette.secondary.base
|
|
774
793
|
};
|
|
775
|
-
rules[
|
|
794
|
+
rules[_sx(scope, '.bw_card_title')] = {
|
|
776
795
|
'color': palette.dark.base
|
|
777
796
|
};
|
|
778
|
-
rules[
|
|
797
|
+
rules[_sx(scope, '.bw_card_subtitle')] = {
|
|
779
798
|
'color': palette.secondary.base
|
|
780
799
|
};
|
|
781
800
|
|
|
@@ -789,55 +808,55 @@ function generateForms(scope, palette, layout) {
|
|
|
789
808
|
var sp = layout.spacing;
|
|
790
809
|
var rd = layout.radius;
|
|
791
810
|
|
|
792
|
-
rules[
|
|
811
|
+
rules[_sx(scope, '.bw_form_control')] = {
|
|
793
812
|
'padding': sp.input,
|
|
794
813
|
'border-radius': rd.input,
|
|
795
814
|
'color': palette.dark.base,
|
|
796
815
|
'background-color': palette.surface || '#fff',
|
|
797
816
|
'border-color': palette.light.border
|
|
798
817
|
};
|
|
799
|
-
rules[
|
|
818
|
+
rules[_sx(scope, '.bw_form_control:focus')] = {
|
|
800
819
|
'border-color': palette.primary.border,
|
|
801
820
|
'outline': '2px solid ' + palette.primary.base,
|
|
802
821
|
'outline-offset': '-1px',
|
|
803
822
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
804
823
|
};
|
|
805
|
-
rules[
|
|
824
|
+
rules[_sx(scope, '.bw_form_control::placeholder')] = {
|
|
806
825
|
'color': palette.secondary.base
|
|
807
826
|
};
|
|
808
|
-
rules[
|
|
827
|
+
rules[_sx(scope, '.bw_form_label')] = {
|
|
809
828
|
'color': palette.dark.base
|
|
810
829
|
};
|
|
811
|
-
rules[
|
|
830
|
+
rules[_sx(scope, '.bw_form_text')] = {
|
|
812
831
|
'color': palette.secondary.base
|
|
813
832
|
};
|
|
814
|
-
rules[
|
|
833
|
+
rules[_sx(scope, '.bw_form_check_input:checked')] = {
|
|
815
834
|
'background-color': palette.primary.base,
|
|
816
835
|
'border-color': palette.primary.base
|
|
817
836
|
};
|
|
818
|
-
rules[
|
|
837
|
+
rules[_sx(scope, '.bw_form_check_input:focus')] = {
|
|
819
838
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
820
839
|
};
|
|
821
840
|
// Validation states
|
|
822
|
-
rules[
|
|
823
|
-
rules[
|
|
841
|
+
rules[_sx(scope, '.bw_form_control.bw_is_valid')] = { 'border-color': palette.success.base };
|
|
842
|
+
rules[_sx(scope, '.bw_form_control.bw_is_valid:focus')] = {
|
|
824
843
|
'border-color': palette.success.base,
|
|
825
844
|
'box-shadow': '0 0 0 0.2rem ' + palette.success.focus
|
|
826
845
|
};
|
|
827
|
-
rules[
|
|
828
|
-
rules[
|
|
846
|
+
rules[_sx(scope, '.bw_form_control.bw_is_invalid')] = { 'border-color': palette.danger.base };
|
|
847
|
+
rules[_sx(scope, '.bw_form_control.bw_is_invalid:focus')] = {
|
|
829
848
|
'border-color': palette.danger.base,
|
|
830
849
|
'box-shadow': '0 0 0 0.2rem ' + palette.danger.focus
|
|
831
850
|
};
|
|
832
851
|
// Form select
|
|
833
|
-
rules[
|
|
852
|
+
rules[_sx(scope, '.bw_form_select')] = {
|
|
834
853
|
'padding': sp.input,
|
|
835
854
|
'border-radius': rd.input,
|
|
836
855
|
'color': palette.dark.base,
|
|
837
856
|
'background-color': palette.surface || '#fff',
|
|
838
857
|
'border-color': palette.light.border
|
|
839
858
|
};
|
|
840
|
-
rules[
|
|
859
|
+
rules[_sx(scope, '.bw_form_select:focus')] = {
|
|
841
860
|
'border-color': palette.primary.border,
|
|
842
861
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
843
862
|
};
|
|
@@ -845,43 +864,46 @@ function generateForms(scope, palette, layout) {
|
|
|
845
864
|
return rules;
|
|
846
865
|
}
|
|
847
866
|
|
|
848
|
-
function generateNavigation(scope, palette) {
|
|
867
|
+
function generateNavigation(scope, palette, layout) {
|
|
849
868
|
var rules = {};
|
|
850
|
-
rules[
|
|
851
|
-
'background-color': palette.
|
|
869
|
+
rules[_sx(scope, '.bw_navbar')] = {
|
|
870
|
+
'background-color': palette.surfaceAlt,
|
|
852
871
|
'border-bottom-color': palette.light.border
|
|
853
872
|
};
|
|
854
|
-
rules[
|
|
873
|
+
rules[_sx(scope, '.bw_navbar_brand')] = {
|
|
855
874
|
'color': palette.dark.base
|
|
856
875
|
};
|
|
857
|
-
rules[
|
|
858
|
-
'color': palette.secondary.base
|
|
876
|
+
rules[_sx(scope, '.bw_navbar_nav .bw_nav_link')] = {
|
|
877
|
+
'color': palette.secondary.base,
|
|
878
|
+
'border-radius': layout.radius.btn
|
|
859
879
|
};
|
|
860
|
-
rules[
|
|
861
|
-
'color': palette.dark.base
|
|
880
|
+
rules[_sx(scope, '.bw_navbar_nav .bw_nav_link:hover')] = {
|
|
881
|
+
'color': palette.dark.base,
|
|
882
|
+
'background-color': palette.surfaceAlt
|
|
862
883
|
};
|
|
863
|
-
rules[
|
|
884
|
+
rules[_sx(scope, '.bw_navbar_nav .bw_nav_link.active')] = {
|
|
864
885
|
'color': palette.primary.base,
|
|
865
|
-
'background-color': palette.primary.focus
|
|
886
|
+
'background-color': palette.primary.focus,
|
|
887
|
+
'font-weight': '600'
|
|
866
888
|
};
|
|
867
|
-
rules[
|
|
889
|
+
rules[_sx(scope, '.bw_navbar_dark')] = {
|
|
868
890
|
'background-color': palette.dark.base,
|
|
869
891
|
'border-bottom-color': palette.dark.hover
|
|
870
892
|
};
|
|
871
|
-
rules[
|
|
893
|
+
rules[_sx(scope, '.bw_navbar_dark .bw_navbar_brand')] = {
|
|
872
894
|
'color': palette.light.base
|
|
873
895
|
};
|
|
874
|
-
rules[
|
|
875
|
-
'color':
|
|
896
|
+
rules[_sx(scope, '.bw_navbar_dark .bw_nav_link')] = {
|
|
897
|
+
'color': palette.light.border
|
|
876
898
|
};
|
|
877
|
-
rules[
|
|
878
|
-
'color':
|
|
899
|
+
rules[_sx(scope, '.bw_navbar_dark .bw_nav_link:hover')] = {
|
|
900
|
+
'color': palette.light.base
|
|
879
901
|
};
|
|
880
|
-
rules[
|
|
881
|
-
'color':
|
|
902
|
+
rules[_sx(scope, '.bw_navbar_dark .bw_nav_link.active')] = {
|
|
903
|
+
'color': palette.light.base,
|
|
882
904
|
'font-weight': '600'
|
|
883
905
|
};
|
|
884
|
-
rules[
|
|
906
|
+
rules[_sx(scope, '.bw_nav_pills .bw_nav_link.active')] = {
|
|
885
907
|
'color': palette.primary.textOn,
|
|
886
908
|
'background-color': palette.primary.base
|
|
887
909
|
};
|
|
@@ -892,49 +914,58 @@ function generateTables(scope, palette, layout) {
|
|
|
892
914
|
var rules = {};
|
|
893
915
|
var sp = layout.spacing;
|
|
894
916
|
|
|
895
|
-
rules[
|
|
917
|
+
rules[_sx(scope, '.bw_table')] = {
|
|
896
918
|
'color': palette.dark.base,
|
|
897
919
|
'border-color': palette.light.border
|
|
898
920
|
};
|
|
899
|
-
rules[
|
|
921
|
+
rules[_sx(scope, '.bw_table > :not(caption) > * > *')] = {
|
|
900
922
|
'padding': sp.cell,
|
|
901
923
|
'border-bottom-color': palette.light.border
|
|
902
924
|
};
|
|
903
|
-
rules[
|
|
925
|
+
rules[_sx(scope, '.bw_table > thead > tr > *')] = {
|
|
904
926
|
'color': palette.secondary.base,
|
|
905
927
|
'border-bottom-color': palette.light.border,
|
|
906
|
-
'background-color': palette.
|
|
928
|
+
'background-color': palette.surfaceAlt
|
|
907
929
|
};
|
|
908
|
-
rules[
|
|
909
|
-
'background-color':
|
|
930
|
+
rules[_sx(scope, '.bw_table_striped > tbody > tr:nth-of-type(odd) > *')] = {
|
|
931
|
+
'background-color': palette.surfaceAlt
|
|
910
932
|
};
|
|
911
|
-
rules[
|
|
933
|
+
rules[_sx(scope, '.bw_table_hover > tbody > tr:hover > *')] = {
|
|
912
934
|
'background-color': palette.primary.focus
|
|
913
935
|
};
|
|
914
|
-
rules[
|
|
936
|
+
rules[_sx(scope, '.bw_table_selectable > tbody > tr')] = {
|
|
937
|
+
'cursor': 'pointer'
|
|
938
|
+
};
|
|
939
|
+
rules[_sx(scope, '.bw_table > tbody > tr.bw_table_row_selected > *')] = {
|
|
940
|
+
'background-color': palette.primary.light
|
|
941
|
+
};
|
|
942
|
+
rules[_sx(scope, '.bw_table_bordered')] = {
|
|
915
943
|
'border-color': palette.light.border
|
|
916
944
|
};
|
|
917
|
-
rules[
|
|
945
|
+
rules[_sx(scope, '.bw_table caption')] = {
|
|
918
946
|
'color': palette.secondary.base
|
|
919
947
|
};
|
|
920
948
|
|
|
921
949
|
return rules;
|
|
922
950
|
}
|
|
923
951
|
|
|
924
|
-
function generateTabs(scope, palette) {
|
|
925
|
-
var rules = {};
|
|
926
|
-
rules[
|
|
952
|
+
function generateTabs(scope, palette, layout) {
|
|
953
|
+
var rules = {}, mo = layout.motion;
|
|
954
|
+
rules[_sx(scope, '.bw_nav_tabs')] = {
|
|
927
955
|
'border-bottom-color': palette.light.border
|
|
928
956
|
};
|
|
929
|
-
rules[
|
|
930
|
-
'color': palette.secondary.base
|
|
957
|
+
rules[_sx(scope, '.bw_nav_link')] = {
|
|
958
|
+
'color': palette.secondary.base,
|
|
959
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', border-color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
931
960
|
};
|
|
932
|
-
rules[
|
|
961
|
+
rules[_sx(scope, '.bw_nav_tabs .bw_nav_link:hover')] = {
|
|
933
962
|
'color': palette.dark.base,
|
|
963
|
+
'background-color': palette.surfaceAlt,
|
|
934
964
|
'border-bottom-color': palette.light.border
|
|
935
965
|
};
|
|
936
|
-
rules[
|
|
966
|
+
rules[_sx(scope, '.bw_nav_tabs .bw_nav_link.active')] = {
|
|
937
967
|
'color': palette.primary.base,
|
|
968
|
+
'background-color': palette.primary.focus,
|
|
938
969
|
'border-bottom': '2px solid ' + palette.primary.base
|
|
939
970
|
};
|
|
940
971
|
return rules;
|
|
@@ -943,23 +974,25 @@ function generateTabs(scope, palette) {
|
|
|
943
974
|
function generateListGroups(scope, palette, layout) {
|
|
944
975
|
var rules = {};
|
|
945
976
|
var sp = layout.spacing;
|
|
977
|
+
var mo = layout.motion;
|
|
946
978
|
|
|
947
|
-
rules[
|
|
979
|
+
rules[_sx(scope, '.bw_list_group_item')] = {
|
|
948
980
|
'padding': sp.cell,
|
|
949
981
|
'color': palette.dark.base,
|
|
950
982
|
'background-color': palette.surface || '#fff',
|
|
951
|
-
'border-color': palette.light.border
|
|
983
|
+
'border-color': palette.light.border,
|
|
984
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
952
985
|
};
|
|
953
|
-
rules[
|
|
954
|
-
'background-color': palette.
|
|
986
|
+
rules[_sx(scope, 'a.bw_list_group_item:hover')] = {
|
|
987
|
+
'background-color': palette.surfaceAlt,
|
|
955
988
|
'color': palette.dark.hover
|
|
956
989
|
};
|
|
957
|
-
rules[
|
|
990
|
+
rules[_sx(scope, '.bw_list_group_item.active')] = {
|
|
958
991
|
'color': palette.primary.textOn,
|
|
959
992
|
'background-color': palette.primary.base,
|
|
960
993
|
'border-color': palette.primary.base
|
|
961
994
|
};
|
|
962
|
-
rules[
|
|
995
|
+
rules[_sx(scope, '.bw_list_group_item.disabled')] = {
|
|
963
996
|
'color': palette.secondary.base,
|
|
964
997
|
'background-color': palette.surface || '#fff'
|
|
965
998
|
};
|
|
@@ -967,28 +1000,37 @@ function generateListGroups(scope, palette, layout) {
|
|
|
967
1000
|
return rules;
|
|
968
1001
|
}
|
|
969
1002
|
|
|
970
|
-
function generatePagination(scope, palette) {
|
|
971
|
-
var rules = {};
|
|
972
|
-
rules[
|
|
1003
|
+
function generatePagination(scope, palette, layout) {
|
|
1004
|
+
var rules = {}, mo = layout.motion, rd = layout.radius;
|
|
1005
|
+
rules[_sx(scope, '.bw_page_item:first-child .bw_page_link')] = {
|
|
1006
|
+
'border-top-left-radius': rd.btn,
|
|
1007
|
+
'border-bottom-left-radius': rd.btn
|
|
1008
|
+
};
|
|
1009
|
+
rules[_sx(scope, '.bw_page_item:last-child .bw_page_link')] = {
|
|
1010
|
+
'border-top-right-radius': rd.btn,
|
|
1011
|
+
'border-bottom-right-radius': rd.btn
|
|
1012
|
+
};
|
|
1013
|
+
rules[_sx(scope, '.bw_page_link')] = {
|
|
973
1014
|
'color': palette.primary.base,
|
|
974
1015
|
'background-color': palette.surface || '#fff',
|
|
975
|
-
'border-color': palette.light.border
|
|
1016
|
+
'border-color': palette.light.border,
|
|
1017
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
976
1018
|
};
|
|
977
|
-
rules[
|
|
1019
|
+
rules[_sx(scope, '.bw_page_link:hover')] = {
|
|
978
1020
|
'color': palette.primary.hover,
|
|
979
|
-
'background-color': palette.
|
|
1021
|
+
'background-color': palette.surfaceAlt,
|
|
980
1022
|
'border-color': palette.light.border
|
|
981
1023
|
};
|
|
982
|
-
rules[
|
|
1024
|
+
rules[_sx(scope, '.bw_page_link:focus')] = {
|
|
983
1025
|
'outline': '2px solid ' + palette.primary.base,
|
|
984
1026
|
'outline-offset': '-2px'
|
|
985
1027
|
};
|
|
986
|
-
rules[
|
|
1028
|
+
rules[_sx(scope, '.bw_page_item.bw_active .bw_page_link')] = {
|
|
987
1029
|
'color': palette.primary.textOn,
|
|
988
1030
|
'background-color': palette.primary.base,
|
|
989
1031
|
'border-color': palette.primary.base
|
|
990
1032
|
};
|
|
991
|
-
rules[
|
|
1033
|
+
rules[_sx(scope, '.bw_page_item.bw_disabled .bw_page_link')] = {
|
|
992
1034
|
'color': palette.secondary.base,
|
|
993
1035
|
'background-color': palette.surface || '#fff',
|
|
994
1036
|
'border-color': palette.light.border
|
|
@@ -998,12 +1040,12 @@ function generatePagination(scope, palette) {
|
|
|
998
1040
|
|
|
999
1041
|
function generateProgress(scope, palette) {
|
|
1000
1042
|
var rules = {};
|
|
1001
|
-
rules[
|
|
1002
|
-
'background-color': palette.
|
|
1043
|
+
rules[_sx(scope, '.bw_progress')] = {
|
|
1044
|
+
'background-color': palette.surfaceAlt,
|
|
1003
1045
|
'box-shadow': 'inset 0 1px 2px rgba(0,0,0,.1)'
|
|
1004
1046
|
};
|
|
1005
|
-
rules[
|
|
1006
|
-
'color':
|
|
1047
|
+
rules[_sx(scope, '.bw_progress_bar')] = {
|
|
1048
|
+
'color': palette.primary.textOn,
|
|
1007
1049
|
'background-color': palette.primary.base,
|
|
1008
1050
|
'box-shadow': 'inset 0 -1px 0 rgba(0,0,0,.15)'
|
|
1009
1051
|
};
|
|
@@ -1022,26 +1064,31 @@ function generateResetThemed(scope, palette) {
|
|
|
1022
1064
|
'color': palette.dark.base,
|
|
1023
1065
|
'background-color': bg
|
|
1024
1066
|
};
|
|
1025
|
-
rules[
|
|
1026
|
-
// Also apply to the scope element itself so themes work on any container, not just body
|
|
1027
|
-
if (scope) {
|
|
1028
|
-
rules['.' + scope] = baseReset;
|
|
1029
|
-
}
|
|
1067
|
+
rules[_sx(scope, 'body')] = baseReset;
|
|
1030
1068
|
return rules;
|
|
1031
1069
|
}
|
|
1032
1070
|
|
|
1033
|
-
function generateBreadcrumbThemed(scope, palette) {
|
|
1034
|
-
var rules = {};
|
|
1035
|
-
rules[
|
|
1036
|
-
'color': palette.
|
|
1071
|
+
function generateBreadcrumbThemed(scope, palette, layout) {
|
|
1072
|
+
var rules = {}, mo = layout.motion;
|
|
1073
|
+
rules[_sx(scope, '.bw_breadcrumb')] = {
|
|
1074
|
+
'background-color': palette.surfaceAlt,
|
|
1075
|
+
'padding': '0.625rem 1rem',
|
|
1076
|
+
'border-radius': layout.radius.btn
|
|
1037
1077
|
};
|
|
1038
|
-
rules[
|
|
1078
|
+
rules[_sx(scope, '.bw_breadcrumb_item + .bw_breadcrumb_item::before')] = {
|
|
1039
1079
|
'color': palette.secondary.base
|
|
1040
1080
|
};
|
|
1041
|
-
rules[
|
|
1081
|
+
rules[_sx(scope, '.bw_breadcrumb_item a')] = {
|
|
1082
|
+
'color': palette.primary.base,
|
|
1083
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing
|
|
1084
|
+
};
|
|
1085
|
+
rules[_sx(scope, '.bw_breadcrumb_item a:hover')] = {
|
|
1042
1086
|
'color': palette.primary.hover,
|
|
1043
1087
|
'text-decoration': 'underline'
|
|
1044
1088
|
};
|
|
1089
|
+
rules[_sx(scope, '.bw_breadcrumb_item.active')] = {
|
|
1090
|
+
'color': palette.dark.base
|
|
1091
|
+
};
|
|
1045
1092
|
return rules;
|
|
1046
1093
|
}
|
|
1047
1094
|
|
|
@@ -1049,11 +1096,11 @@ function generateBreadcrumbThemed(scope, palette) {
|
|
|
1049
1096
|
|
|
1050
1097
|
function generateCloseButtonThemed(scope, palette) {
|
|
1051
1098
|
var rules = {};
|
|
1052
|
-
rules[
|
|
1099
|
+
rules[_sx(scope, '.bw_close')] = {
|
|
1053
1100
|
'color': palette.dark.base,
|
|
1054
1101
|
'opacity': '0.5'
|
|
1055
1102
|
};
|
|
1056
|
-
rules[
|
|
1103
|
+
rules[_sx(scope, '.bw_close:focus')] = {
|
|
1057
1104
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
1058
1105
|
};
|
|
1059
1106
|
return rules;
|
|
@@ -1061,82 +1108,94 @@ function generateCloseButtonThemed(scope, palette) {
|
|
|
1061
1108
|
|
|
1062
1109
|
function generateSectionsThemed(scope, palette) {
|
|
1063
1110
|
var rules = {};
|
|
1064
|
-
rules[
|
|
1111
|
+
rules[_sx(scope, '.bw_section_subtitle')] = {
|
|
1065
1112
|
'color': palette.secondary.base
|
|
1066
1113
|
};
|
|
1067
|
-
rules[
|
|
1114
|
+
rules[_sx(scope, '.bw_feature_description')] = {
|
|
1068
1115
|
'color': palette.secondary.base
|
|
1069
1116
|
};
|
|
1070
|
-
rules[
|
|
1117
|
+
rules[_sx(scope, '.bw_cta_description')] = {
|
|
1071
1118
|
'color': palette.secondary.base
|
|
1072
1119
|
};
|
|
1073
1120
|
return rules;
|
|
1074
1121
|
}
|
|
1075
1122
|
|
|
1076
|
-
function generateAccordionThemed(scope, palette) {
|
|
1123
|
+
function generateAccordionThemed(scope, palette, layout) {
|
|
1077
1124
|
var rules = {};
|
|
1078
|
-
|
|
1125
|
+
var rd = layout ? layout.radius : { card: '8px' };
|
|
1126
|
+
rules[_sx(scope, '.bw_accordion_item')] = {
|
|
1079
1127
|
'background-color': palette.surface || '#fff',
|
|
1080
1128
|
'border-color': palette.light.border
|
|
1081
1129
|
};
|
|
1082
|
-
rules[
|
|
1130
|
+
rules[_sx(scope, '.bw_accordion_item:first-child')] = {
|
|
1131
|
+
'border-top-left-radius': rd.card,
|
|
1132
|
+
'border-top-right-radius': rd.card
|
|
1133
|
+
};
|
|
1134
|
+
rules[_sx(scope, '.bw_accordion_item:last-child')] = {
|
|
1135
|
+
'border-bottom-left-radius': rd.card,
|
|
1136
|
+
'border-bottom-right-radius': rd.card
|
|
1137
|
+
};
|
|
1138
|
+
rules[_sx(scope, '.bw_accordion_button')] = {
|
|
1083
1139
|
'color': palette.dark.base
|
|
1084
1140
|
};
|
|
1085
|
-
rules[
|
|
1141
|
+
rules[_sx(scope, '.bw_accordion_button:not(.bw_collapsed)')] = {
|
|
1086
1142
|
'color': palette.primary.darkText,
|
|
1087
|
-
'background-color': palette.primary.light
|
|
1143
|
+
'background-color': palette.primary.light,
|
|
1144
|
+
'border-left': '3px solid ' + palette.primary.base
|
|
1088
1145
|
};
|
|
1089
|
-
rules[
|
|
1090
|
-
'background-color': palette.
|
|
1146
|
+
rules[_sx(scope, '.bw_accordion_button:hover')] = {
|
|
1147
|
+
'background-color': palette.surfaceAlt
|
|
1091
1148
|
};
|
|
1092
|
-
rules[
|
|
1093
|
-
'background-color': palette.primary.
|
|
1149
|
+
rules[_sx(scope, '.bw_accordion_button:not(.bw_collapsed):hover')] = {
|
|
1150
|
+
'background-color': palette.primary.base,
|
|
1151
|
+
'color': palette.primary.textOn
|
|
1094
1152
|
};
|
|
1095
|
-
rules[
|
|
1153
|
+
rules[_sx(scope, '.bw_accordion_button:focus-visible')] = {
|
|
1096
1154
|
'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
|
|
1097
1155
|
};
|
|
1098
|
-
rules[
|
|
1099
|
-
'border-top': '1px solid ' + palette.light.border
|
|
1156
|
+
rules[_sx(scope, '.bw_accordion_body')] = {
|
|
1157
|
+
'border-top': '1px solid ' + palette.light.border,
|
|
1158
|
+
'background-color': palette.surfaceAlt
|
|
1100
1159
|
};
|
|
1101
1160
|
return rules;
|
|
1102
1161
|
}
|
|
1103
1162
|
|
|
1104
1163
|
function generateCarouselThemed(scope, palette) {
|
|
1105
1164
|
var rules = {};
|
|
1106
|
-
rules[
|
|
1107
|
-
'background-color': palette.
|
|
1165
|
+
rules[_sx(scope, '.bw_carousel')] = {
|
|
1166
|
+
'background-color': palette.surfaceAlt
|
|
1108
1167
|
};
|
|
1109
|
-
rules[
|
|
1168
|
+
rules[_sx(scope, '.bw_carousel_indicator.active')] = {
|
|
1110
1169
|
'background-color': palette.primary.base
|
|
1111
1170
|
};
|
|
1112
|
-
rules[
|
|
1113
|
-
'background-color':
|
|
1114
|
-
'color':
|
|
1171
|
+
rules[_sx(scope, '.bw_carousel_control')] = {
|
|
1172
|
+
'background-color': palette.dark.base,
|
|
1173
|
+
'color': palette.dark.textOn
|
|
1115
1174
|
};
|
|
1116
|
-
rules[
|
|
1117
|
-
'background-color':
|
|
1175
|
+
rules[_sx(scope, '.bw_carousel_control:hover')] = {
|
|
1176
|
+
'background-color': palette.dark.hover
|
|
1118
1177
|
};
|
|
1119
|
-
rules[
|
|
1120
|
-
'background': 'linear-gradient(transparent,
|
|
1121
|
-
'color':
|
|
1178
|
+
rules[_sx(scope, '.bw_carousel_caption')] = {
|
|
1179
|
+
'background': 'linear-gradient(transparent, ' + palette.dark.base + ')',
|
|
1180
|
+
'color': palette.dark.textOn
|
|
1122
1181
|
};
|
|
1123
1182
|
return rules;
|
|
1124
1183
|
}
|
|
1125
1184
|
|
|
1126
1185
|
function generateModalThemed(scope, palette, layout) {
|
|
1127
1186
|
var rules = {};
|
|
1128
|
-
rules[
|
|
1187
|
+
rules[_sx(scope, '.bw_modal_content')] = {
|
|
1129
1188
|
'background-color': palette.surface || '#fff',
|
|
1130
1189
|
'border-color': palette.light.border,
|
|
1131
1190
|
'box-shadow': layout.elevation.lg
|
|
1132
1191
|
};
|
|
1133
|
-
rules[
|
|
1192
|
+
rules[_sx(scope, '.bw_modal_header')] = {
|
|
1134
1193
|
'border-bottom-color': palette.light.border
|
|
1135
1194
|
};
|
|
1136
|
-
rules[
|
|
1195
|
+
rules[_sx(scope, '.bw_modal_footer')] = {
|
|
1137
1196
|
'border-top-color': palette.light.border
|
|
1138
1197
|
};
|
|
1139
|
-
rules[
|
|
1198
|
+
rules[_sx(scope, '.bw_modal_title')] = {
|
|
1140
1199
|
'color': palette.dark.base
|
|
1141
1200
|
};
|
|
1142
1201
|
return rules;
|
|
@@ -1144,13 +1203,13 @@ function generateModalThemed(scope, palette, layout) {
|
|
|
1144
1203
|
|
|
1145
1204
|
function generateToastThemed(scope, palette, layout) {
|
|
1146
1205
|
var rules = {};
|
|
1147
|
-
rules[
|
|
1206
|
+
rules[_sx(scope, '.bw_toast')] = {
|
|
1148
1207
|
'background-color': palette.surface || '#fff',
|
|
1149
|
-
'border-color':
|
|
1208
|
+
'border-color': palette.light.border,
|
|
1150
1209
|
'box-shadow': layout.elevation.lg
|
|
1151
1210
|
};
|
|
1152
|
-
rules[
|
|
1153
|
-
'border-bottom-color':
|
|
1211
|
+
rules[_sx(scope, '.bw_toast_header')] = {
|
|
1212
|
+
'border-bottom-color': palette.light.border
|
|
1154
1213
|
};
|
|
1155
1214
|
// Variant toast borders handled by palette class
|
|
1156
1215
|
return rules;
|
|
@@ -1158,22 +1217,23 @@ function generateToastThemed(scope, palette, layout) {
|
|
|
1158
1217
|
|
|
1159
1218
|
function generateDropdownThemed(scope, palette, layout) {
|
|
1160
1219
|
var rules = {};
|
|
1161
|
-
rules[
|
|
1220
|
+
rules[_sx(scope, '.bw_dropdown_menu')] = {
|
|
1162
1221
|
'background-color': palette.surface || '#fff',
|
|
1163
1222
|
'border-color': palette.light.border,
|
|
1164
1223
|
'box-shadow': layout.elevation.md
|
|
1165
1224
|
};
|
|
1166
|
-
rules[
|
|
1167
|
-
'color': palette.dark.base
|
|
1225
|
+
rules[_sx(scope, '.bw_dropdown_item')] = {
|
|
1226
|
+
'color': palette.dark.base,
|
|
1227
|
+
'transition': 'background-color ' + layout.motion.fast + ' ' + layout.motion.easing
|
|
1168
1228
|
};
|
|
1169
|
-
rules[
|
|
1229
|
+
rules[_sx(scope, '.bw_dropdown_item:hover')] = {
|
|
1170
1230
|
'color': palette.dark.hover,
|
|
1171
|
-
'background-color': palette.
|
|
1231
|
+
'background-color': palette.surfaceAlt
|
|
1172
1232
|
};
|
|
1173
|
-
rules[
|
|
1233
|
+
rules[_sx(scope, '.bw_dropdown_item.disabled')] = {
|
|
1174
1234
|
'color': palette.secondary.base
|
|
1175
1235
|
};
|
|
1176
|
-
rules[
|
|
1236
|
+
rules[_sx(scope, '.bw_dropdown_divider')] = {
|
|
1177
1237
|
'border-top-color': palette.light.border
|
|
1178
1238
|
};
|
|
1179
1239
|
return rules;
|
|
@@ -1181,15 +1241,15 @@ function generateDropdownThemed(scope, palette, layout) {
|
|
|
1181
1241
|
|
|
1182
1242
|
function generateSwitchThemed(scope, palette) {
|
|
1183
1243
|
var rules = {};
|
|
1184
|
-
rules[
|
|
1244
|
+
rules[_sx(scope, '.bw_form_switch .bw_switch_input')] = {
|
|
1185
1245
|
'background-color': palette.secondary.base,
|
|
1186
1246
|
'border-color': palette.secondary.base
|
|
1187
1247
|
};
|
|
1188
|
-
rules[
|
|
1248
|
+
rules[_sx(scope, '.bw_form_switch .bw_switch_input:checked')] = {
|
|
1189
1249
|
'background-color': palette.primary.base,
|
|
1190
1250
|
'border-color': palette.primary.base
|
|
1191
1251
|
};
|
|
1192
|
-
rules[
|
|
1252
|
+
rules[_sx(scope, '.bw_form_switch .bw_switch_input:focus')] = {
|
|
1193
1253
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
1194
1254
|
};
|
|
1195
1255
|
return rules;
|
|
@@ -1197,88 +1257,102 @@ function generateSwitchThemed(scope, palette) {
|
|
|
1197
1257
|
|
|
1198
1258
|
function generateSkeletonThemed(scope, palette) {
|
|
1199
1259
|
var rules = {};
|
|
1200
|
-
rules[
|
|
1201
|
-
'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.
|
|
1260
|
+
rules[_sx(scope, '.bw_skeleton')] = {
|
|
1261
|
+
'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.surfaceAlt + ' 37%, ' + palette.light.border + ' 63%)'
|
|
1202
1262
|
};
|
|
1203
1263
|
return rules;
|
|
1204
1264
|
}
|
|
1205
1265
|
|
|
1206
1266
|
// generateAvatarThemed: removed — palette class on root handles variants
|
|
1207
1267
|
|
|
1208
|
-
function generateStatCardThemed(scope, palette) {
|
|
1209
|
-
var rules = {};
|
|
1268
|
+
function generateStatCardThemed(scope, palette, layout) {
|
|
1269
|
+
var rules = {}, mo = layout.motion, el = layout.elevation, rd = layout.radius;
|
|
1270
|
+
rules[_sx(scope, '.bw_stat_card')] = {
|
|
1271
|
+
'background-color': palette.surface || '#fff',
|
|
1272
|
+
'color': palette.dark.base,
|
|
1273
|
+
'border': '1px solid ' + palette.light.border,
|
|
1274
|
+
'border-radius': rd.card,
|
|
1275
|
+
'box-shadow': el.sm,
|
|
1276
|
+
'transition': 'box-shadow ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
|
|
1277
|
+
};
|
|
1278
|
+
rules[_sx(scope, '.bw_stat_card:hover')] = { 'box-shadow': el.md };
|
|
1210
1279
|
// Variant border colors handled by palette class
|
|
1211
|
-
rules[
|
|
1212
|
-
rules[
|
|
1280
|
+
rules[_sx(scope, '.bw_stat_change_up')] = { 'color': palette.success.base };
|
|
1281
|
+
rules[_sx(scope, '.bw_stat_change_down')] = { 'color': palette.danger.base };
|
|
1213
1282
|
return rules;
|
|
1214
1283
|
}
|
|
1215
1284
|
|
|
1216
1285
|
function generateTimelineThemed(scope, palette) {
|
|
1217
1286
|
var rules = {};
|
|
1218
|
-
rules[
|
|
1287
|
+
rules[_sx(scope, '.bw_timeline::before')] = { 'background-color': palette.light.border };
|
|
1219
1288
|
// Variant marker colors handled by palette class
|
|
1220
|
-
rules[
|
|
1289
|
+
rules[_sx(scope, '.bw_timeline_date')] = { 'color': palette.secondary.base };
|
|
1221
1290
|
return rules;
|
|
1222
1291
|
}
|
|
1223
1292
|
|
|
1224
1293
|
function generateStepperThemed(scope, palette) {
|
|
1225
1294
|
var rules = {};
|
|
1226
|
-
rules[
|
|
1227
|
-
'background-color': palette.
|
|
1295
|
+
rules[_sx(scope, '.bw_step_indicator')] = {
|
|
1296
|
+
'background-color': palette.surfaceAlt,
|
|
1228
1297
|
'border': '2px solid ' + palette.light.border,
|
|
1229
1298
|
'color': palette.secondary.base
|
|
1230
1299
|
};
|
|
1231
|
-
rules[
|
|
1232
|
-
rules[
|
|
1300
|
+
rules[_sx(scope, '.bw_step + .bw_step::before')] = { 'background-color': palette.light.border };
|
|
1301
|
+
rules[_sx(scope, '.bw_step_active .bw_step_indicator')] = {
|
|
1233
1302
|
'background-color': palette.primary.base,
|
|
1234
1303
|
'color': palette.primary.textOn
|
|
1235
1304
|
};
|
|
1236
|
-
rules[
|
|
1305
|
+
rules[_sx(scope, '.bw_step_active .bw_step_label')] = {
|
|
1237
1306
|
'color': palette.dark.base,
|
|
1238
1307
|
'font-weight': '600'
|
|
1239
1308
|
};
|
|
1240
|
-
rules[
|
|
1309
|
+
rules[_sx(scope, '.bw_step_completed .bw_step_indicator')] = {
|
|
1241
1310
|
'background-color': palette.primary.base,
|
|
1242
1311
|
'color': palette.primary.textOn
|
|
1243
1312
|
};
|
|
1244
|
-
rules[
|
|
1245
|
-
rules[
|
|
1313
|
+
rules[_sx(scope, '.bw_step_completed .bw_step_label')] = { 'color': palette.primary.base };
|
|
1314
|
+
rules[_sx(scope, '.bw_step_completed + .bw_step::before')] = { 'background-color': palette.primary.base };
|
|
1246
1315
|
return rules;
|
|
1247
1316
|
}
|
|
1248
1317
|
|
|
1249
1318
|
function generateChipInputThemed(scope, palette) {
|
|
1250
1319
|
var rules = {};
|
|
1251
|
-
rules[
|
|
1252
|
-
|
|
1320
|
+
rules[_sx(scope, '.bw_chip_input')] = {
|
|
1321
|
+
'border-color': palette.light.border,
|
|
1322
|
+
'background-color': palette.surface || '#fff',
|
|
1323
|
+
'color': palette.dark.base
|
|
1324
|
+
};
|
|
1325
|
+
rules[_sx(scope, '.bw_chip_input:focus-within')] = {
|
|
1253
1326
|
'border-color': palette.primary.base,
|
|
1254
1327
|
'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
|
|
1255
1328
|
};
|
|
1256
|
-
rules[
|
|
1257
|
-
'background-color': palette.
|
|
1329
|
+
rules[_sx(scope, '.bw_chip')] = {
|
|
1330
|
+
'background-color': palette.surfaceAlt,
|
|
1258
1331
|
'color': palette.dark.base
|
|
1259
1332
|
};
|
|
1260
|
-
rules[
|
|
1333
|
+
rules[_sx(scope, '.bw_chip_remove:hover')] = {
|
|
1261
1334
|
'color': palette.danger.base,
|
|
1262
1335
|
'background-color': palette.danger.light
|
|
1263
1336
|
};
|
|
1264
1337
|
return rules;
|
|
1265
1338
|
}
|
|
1266
1339
|
|
|
1267
|
-
function generateFileUploadThemed(scope, palette) {
|
|
1268
|
-
var rules = {};
|
|
1269
|
-
rules[
|
|
1340
|
+
function generateFileUploadThemed(scope, palette, layout) {
|
|
1341
|
+
var rules = {}, mo = layout.motion;
|
|
1342
|
+
rules[_sx(scope, '.bw_file_upload')] = {
|
|
1270
1343
|
'border-color': palette.light.border,
|
|
1271
|
-
'background-color': palette.
|
|
1344
|
+
'background-color': palette.surfaceAlt,
|
|
1345
|
+
'transition': 'border-color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
1272
1346
|
};
|
|
1273
|
-
rules[
|
|
1347
|
+
rules[_sx(scope, '.bw_file_upload:hover')] = {
|
|
1274
1348
|
'border-color': palette.primary.base,
|
|
1275
1349
|
'background-color': palette.primary.light
|
|
1276
1350
|
};
|
|
1277
|
-
rules[
|
|
1351
|
+
rules[_sx(scope, '.bw_file_upload:focus')] = {
|
|
1278
1352
|
'outline': '2px solid ' + palette.primary.base,
|
|
1279
1353
|
'outline-offset': '2px'
|
|
1280
1354
|
};
|
|
1281
|
-
rules[
|
|
1355
|
+
rules[_sx(scope, '.bw_file_upload.bw_file_upload_active')] = {
|
|
1282
1356
|
'border-color': palette.primary.base,
|
|
1283
1357
|
'background-color': palette.primary.light,
|
|
1284
1358
|
'border-style': 'solid'
|
|
@@ -1288,35 +1362,73 @@ function generateFileUploadThemed(scope, palette) {
|
|
|
1288
1362
|
|
|
1289
1363
|
function generateRangeThemed(scope, palette) {
|
|
1290
1364
|
var rules = {};
|
|
1291
|
-
rules[
|
|
1292
|
-
rules[
|
|
1365
|
+
rules[_sx(scope, '.bw_range')] = { 'background-color': palette.light.border };
|
|
1366
|
+
rules[_sx(scope, '.bw_range::-webkit-slider-thumb')] = {
|
|
1293
1367
|
'background-color': palette.primary.base,
|
|
1294
|
-
'border-color': '#fff',
|
|
1368
|
+
'border-color': palette.surface || '#fff',
|
|
1295
1369
|
'box-shadow': '0 1px 3px rgba(0,0,0,0.2)',
|
|
1296
1370
|
'transition': 'background-color 0.15s ease-out, transform 0.15s ease-out'
|
|
1297
1371
|
};
|
|
1298
|
-
rules[
|
|
1372
|
+
rules[_sx(scope, '.bw_range::-moz-range-thumb')] = {
|
|
1299
1373
|
'background-color': palette.primary.base,
|
|
1300
|
-
'border-color': '#fff',
|
|
1374
|
+
'border-color': palette.surface || '#fff',
|
|
1301
1375
|
'box-shadow': '0 1px 3px rgba(0,0,0,0.2)'
|
|
1302
1376
|
};
|
|
1303
1377
|
return rules;
|
|
1304
1378
|
}
|
|
1305
1379
|
|
|
1306
|
-
function
|
|
1307
|
-
var rules = {};
|
|
1308
|
-
rules[
|
|
1380
|
+
function generateTooltipThemed(scope, palette, layout) {
|
|
1381
|
+
var rules = {}, sp = layout.spacing, rd = layout.radius, el = layout.elevation, mo = layout.motion;
|
|
1382
|
+
rules[_sx(scope, '.bw_tooltip')] = {
|
|
1383
|
+
'background-color': palette.dark.base, 'color': palette.dark.textOn,
|
|
1384
|
+
'padding': sp.input, 'border-radius': rd.badge, 'box-shadow': el.md,
|
|
1385
|
+
'transition': 'opacity ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
|
|
1386
|
+
};
|
|
1387
|
+
return rules;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
function generatePopoverThemed(scope, palette, layout) {
|
|
1391
|
+
var rules = {}, sp = layout.spacing, rd = layout.radius, el = layout.elevation, mo = layout.motion;
|
|
1392
|
+
rules[_sx(scope, '.bw_popover')] = {
|
|
1393
|
+
'background-color': palette.surface || '#fff', 'color': palette.dark.base,
|
|
1394
|
+
'border': '1px solid ' + palette.light.border, 'border-radius': rd.card, 'box-shadow': el.lg,
|
|
1395
|
+
'transition': 'opacity ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
|
|
1396
|
+
};
|
|
1397
|
+
rules[_sx(scope, '.bw_popover_header')] = {
|
|
1398
|
+
'background-color': palette.surfaceAlt, 'border-bottom': '1px solid ' + palette.light.border,
|
|
1399
|
+
'padding': sp.input
|
|
1400
|
+
};
|
|
1401
|
+
rules[_sx(scope, '.bw_popover_body')] = { 'padding': sp.card };
|
|
1402
|
+
return rules;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function generateSearchThemed(scope, palette, layout) {
|
|
1406
|
+
var rules = {}, mo = layout.motion;
|
|
1407
|
+
rules[_sx(scope, '.bw_search_input')] = {
|
|
1408
|
+
'background-color': palette.surface || '#fff',
|
|
1409
|
+
'color': palette.dark.base
|
|
1410
|
+
};
|
|
1411
|
+
rules[_sx(scope, '.bw_search_clear')] = {
|
|
1412
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
1413
|
+
};
|
|
1414
|
+
rules[_sx(scope, '.bw_search_clear:hover')] = { 'color': palette.dark.base };
|
|
1309
1415
|
return rules;
|
|
1310
1416
|
}
|
|
1311
1417
|
|
|
1312
|
-
function generateCodeDemoThemed(scope, palette) {
|
|
1418
|
+
function generateCodeDemoThemed(scope, palette, layout) {
|
|
1313
1419
|
var rules = {};
|
|
1314
|
-
|
|
1420
|
+
var rd = layout ? layout.radius : { card: '0.375rem' };
|
|
1421
|
+
rules[_sx(scope, '.bw_code_demo')] = {
|
|
1422
|
+
'background-color': palette.surface || '#fff',
|
|
1423
|
+
'color': palette.dark.base,
|
|
1424
|
+
'border-radius': rd.card
|
|
1425
|
+
};
|
|
1426
|
+
rules[_sx(scope, '.bw_code_copy_btn_copied')] = {
|
|
1315
1427
|
'background': palette.success.base,
|
|
1316
1428
|
'color': palette.success.textOn,
|
|
1317
1429
|
'border-color': palette.success.base
|
|
1318
1430
|
};
|
|
1319
|
-
rules[
|
|
1431
|
+
rules[_sx(scope, '.bw_copy_btn:hover')] = {
|
|
1320
1432
|
'background': 'rgba(255,255,255,0.2)',
|
|
1321
1433
|
'color': '#fff'
|
|
1322
1434
|
};
|
|
@@ -1326,7 +1438,7 @@ function generateCodeDemoThemed(scope, palette) {
|
|
|
1326
1438
|
function generateNavPillsThemed(scope, palette, layout) {
|
|
1327
1439
|
var rules = {};
|
|
1328
1440
|
var rd = layout.radius;
|
|
1329
|
-
rules[
|
|
1441
|
+
rules[_sx(scope, '.bw_nav_pills .bw_nav_link')] = { 'border-radius': rd.btn };
|
|
1330
1442
|
return rules;
|
|
1331
1443
|
}
|
|
1332
1444
|
|
|
@@ -1352,21 +1464,21 @@ function generatePaletteClasses(scope, palette) {
|
|
|
1352
1464
|
var s = palette[k];
|
|
1353
1465
|
|
|
1354
1466
|
// --- Root palette class: sets default bg/color/border ---
|
|
1355
|
-
rules[
|
|
1467
|
+
rules[_sx(scope, '.bw_' + k)] = {
|
|
1356
1468
|
'background-color': s.base,
|
|
1357
1469
|
'color': s.textOn,
|
|
1358
1470
|
'border-color': s.base
|
|
1359
1471
|
};
|
|
1360
1472
|
|
|
1361
1473
|
// --- Pseudo-states (shared across all components) ---
|
|
1362
|
-
rules[
|
|
1474
|
+
rules[_sx(scope, '.bw_' + k + ':hover')] = {
|
|
1363
1475
|
'background-color': s.hover,
|
|
1364
1476
|
'border-color': s.active
|
|
1365
1477
|
};
|
|
1366
|
-
rules[
|
|
1478
|
+
rules[_sx(scope, '.bw_' + k + ':active')] = {
|
|
1367
1479
|
'background-color': s.active
|
|
1368
1480
|
};
|
|
1369
|
-
rules[
|
|
1481
|
+
rules[_sx(scope, '.bw_' + k + ':focus-visible')] = {
|
|
1370
1482
|
'box-shadow': '0 0 0 3px ' + s.focus,
|
|
1371
1483
|
'outline': 'none'
|
|
1372
1484
|
};
|
|
@@ -1374,70 +1486,99 @@ function generatePaletteClasses(scope, palette) {
|
|
|
1374
1486
|
// --- Component-specific overrides ---
|
|
1375
1487
|
|
|
1376
1488
|
// Alerts: light bg, dark text, subtle border
|
|
1377
|
-
rules[
|
|
1489
|
+
rules[_sx(scope, '.bw_alert.bw_' + k)] = {
|
|
1378
1490
|
'background-color': s.light,
|
|
1379
1491
|
'color': s.darkText,
|
|
1380
1492
|
'border-color': s.border
|
|
1381
1493
|
};
|
|
1382
1494
|
|
|
1383
1495
|
// Toast: inherit bg, left border accent
|
|
1384
|
-
rules[
|
|
1496
|
+
rules[_sx(scope, '.bw_toast.bw_' + k)] = {
|
|
1385
1497
|
'background-color': 'inherit',
|
|
1386
1498
|
'color': 'inherit',
|
|
1387
1499
|
'border-left': '4px solid ' + s.base
|
|
1388
1500
|
};
|
|
1389
1501
|
|
|
1390
1502
|
// Stat card: inherit bg, left border accent
|
|
1391
|
-
rules[
|
|
1503
|
+
rules[_sx(scope, '.bw_stat_card.bw_' + k)] = {
|
|
1392
1504
|
'background-color': 'inherit',
|
|
1393
1505
|
'color': 'inherit',
|
|
1394
1506
|
'border-left-color': s.base
|
|
1395
1507
|
};
|
|
1396
1508
|
|
|
1397
1509
|
// Card accent: left border accent, inherit bg
|
|
1398
|
-
rules[
|
|
1510
|
+
rules[_sx(scope, '.bw_card.bw_' + k)] = {
|
|
1399
1511
|
'background-color': 'inherit',
|
|
1400
1512
|
'color': 'inherit',
|
|
1401
1513
|
'border-left': '4px solid ' + s.base
|
|
1402
1514
|
};
|
|
1403
1515
|
|
|
1404
1516
|
// Timeline marker: colored dot
|
|
1405
|
-
rules[
|
|
1517
|
+
rules[_sx(scope, '.bw_timeline_marker.bw_' + k)] = {
|
|
1406
1518
|
'box-shadow': '0 0 0 2px ' + s.base
|
|
1407
1519
|
};
|
|
1408
1520
|
|
|
1409
|
-
// Spinner:
|
|
1410
|
-
|
|
1521
|
+
// Spinner: set color, re-apply border pattern so the root palette class
|
|
1522
|
+
// border-color doesn't fill in the transparent gap that makes it spin.
|
|
1523
|
+
// Also neutralize hover/active which would override border-right-color.
|
|
1524
|
+
rules[_sx(scope, '.bw_spinner_border.bw_' + k)] = {
|
|
1411
1525
|
'background-color': 'transparent',
|
|
1412
1526
|
'color': s.base,
|
|
1413
|
-
'border-color':
|
|
1527
|
+
'border-color': s.base,
|
|
1528
|
+
'border-right-color': 'transparent'
|
|
1529
|
+
};
|
|
1530
|
+
rules[_sx(scope, '.bw_spinner_border.bw_' + k + ':hover')] = {
|
|
1531
|
+
'background-color': 'transparent',
|
|
1532
|
+
'border-color': s.base,
|
|
1533
|
+
'border-right-color': 'transparent'
|
|
1534
|
+
};
|
|
1535
|
+
rules[_sx(scope, '.bw_spinner_grow.bw_' + k)] = {
|
|
1536
|
+
'background-color': s.base,
|
|
1537
|
+
'color': s.base
|
|
1414
1538
|
};
|
|
1415
1539
|
|
|
1416
1540
|
// Outline button: transparent bg, colored border+text, solid on hover
|
|
1417
|
-
rules[
|
|
1541
|
+
rules[_sx(scope, '.bw_btn_outline.bw_' + k)] = {
|
|
1418
1542
|
'background-color': 'transparent',
|
|
1419
1543
|
'color': s.base,
|
|
1420
1544
|
'border-color': s.base
|
|
1421
1545
|
};
|
|
1422
|
-
rules[
|
|
1546
|
+
rules[_sx(scope, '.bw_btn_outline.bw_' + k + ':hover')] = {
|
|
1423
1547
|
'background-color': s.base,
|
|
1424
1548
|
'color': s.textOn
|
|
1425
1549
|
};
|
|
1426
1550
|
|
|
1427
1551
|
// Hero: gradient background
|
|
1428
|
-
rules[
|
|
1552
|
+
rules[_sx(scope, '.bw_hero.bw_' + k)] = {
|
|
1429
1553
|
'background': 'linear-gradient(135deg, ' + s.base + ' 0%, ' + s.hover + ' 100%)',
|
|
1430
1554
|
'color': s.textOn
|
|
1431
1555
|
};
|
|
1432
1556
|
|
|
1433
|
-
// Progress bar:
|
|
1434
|
-
rules[
|
|
1435
|
-
'color':
|
|
1557
|
+
// Progress bar: contrasting text on colored bg
|
|
1558
|
+
rules[_sx(scope, '.bw_progress_bar.bw_' + k)] = {
|
|
1559
|
+
'color': s.textOn
|
|
1560
|
+
};
|
|
1561
|
+
|
|
1562
|
+
// Background utility: .bw_bg_primary, .bw_bg_secondary, etc.
|
|
1563
|
+
rules[_sx(scope, '.bw_bg_' + k)] = {
|
|
1564
|
+
'background-color': s.base,
|
|
1565
|
+
'color': s.textOn
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
// Text color utility: .bw_text_primary, .bw_text_secondary, etc.
|
|
1569
|
+
rules[_sx(scope, '.bw_text_' + k)] = {
|
|
1570
|
+
'color': s.base
|
|
1436
1571
|
};
|
|
1437
1572
|
});
|
|
1438
1573
|
|
|
1439
|
-
// Text muted
|
|
1440
|
-
rules[
|
|
1574
|
+
// Text muted — always a neutral gray, never a brand color
|
|
1575
|
+
rules[_sx(scope, '.bw_text_muted')] = { 'color': '#6c757d' };
|
|
1576
|
+
|
|
1577
|
+
// Common bg/text utilities that aren't per-variant
|
|
1578
|
+
rules[_sx(scope, '.bw_bg_dark')] = { 'background-color': '#212529', 'color': '#f8f9fa' };
|
|
1579
|
+
rules[_sx(scope, '.bw_bg_light')] = { 'background-color': '#f8f9fa', 'color': '#212529' };
|
|
1580
|
+
rules[_sx(scope, '.bw_text_light')] = { 'color': '#f8f9fa' };
|
|
1581
|
+
rules[_sx(scope, '.bw_text_dark')] = { 'color': '#212529' };
|
|
1441
1582
|
|
|
1442
1583
|
return rules;
|
|
1443
1584
|
}
|
|
@@ -1459,30 +1600,32 @@ function generateThemedCSS(scopeName, palette, layout) {
|
|
|
1459
1600
|
generateAlerts(scopeName, palette, layout),
|
|
1460
1601
|
generateCards(scopeName, palette, layout),
|
|
1461
1602
|
generateForms(scopeName, palette, layout),
|
|
1462
|
-
generateNavigation(scopeName, palette),
|
|
1603
|
+
generateNavigation(scopeName, palette, layout),
|
|
1463
1604
|
generateTables(scopeName, palette, layout),
|
|
1464
|
-
generateTabs(scopeName, palette),
|
|
1605
|
+
generateTabs(scopeName, palette, layout),
|
|
1465
1606
|
generateListGroups(scopeName, palette, layout),
|
|
1466
|
-
generatePagination(scopeName, palette),
|
|
1607
|
+
generatePagination(scopeName, palette, layout),
|
|
1467
1608
|
generateProgress(scopeName, palette),
|
|
1468
|
-
generateBreadcrumbThemed(scopeName, palette),
|
|
1609
|
+
generateBreadcrumbThemed(scopeName, palette, layout),
|
|
1469
1610
|
generateCloseButtonThemed(scopeName, palette),
|
|
1470
1611
|
generateSectionsThemed(scopeName, palette),
|
|
1471
|
-
generateAccordionThemed(scopeName, palette),
|
|
1612
|
+
generateAccordionThemed(scopeName, palette, layout),
|
|
1472
1613
|
generateCarouselThemed(scopeName, palette),
|
|
1473
1614
|
generateModalThemed(scopeName, palette, layout),
|
|
1474
1615
|
generateToastThemed(scopeName, palette, layout),
|
|
1475
1616
|
generateDropdownThemed(scopeName, palette, layout),
|
|
1476
1617
|
generateSwitchThemed(scopeName, palette),
|
|
1477
1618
|
generateSkeletonThemed(scopeName, palette),
|
|
1478
|
-
generateStatCardThemed(scopeName, palette),
|
|
1619
|
+
generateStatCardThemed(scopeName, palette, layout),
|
|
1479
1620
|
generateTimelineThemed(scopeName, palette),
|
|
1480
1621
|
generateStepperThemed(scopeName, palette),
|
|
1481
1622
|
generateChipInputThemed(scopeName, palette),
|
|
1482
|
-
generateFileUploadThemed(scopeName, palette),
|
|
1623
|
+
generateFileUploadThemed(scopeName, palette, layout),
|
|
1483
1624
|
generateRangeThemed(scopeName, palette),
|
|
1484
|
-
generateSearchThemed(scopeName, palette),
|
|
1485
|
-
|
|
1625
|
+
generateSearchThemed(scopeName, palette, layout),
|
|
1626
|
+
generateTooltipThemed(scopeName, palette, layout),
|
|
1627
|
+
generatePopoverThemed(scopeName, palette, layout),
|
|
1628
|
+
generateCodeDemoThemed(scopeName, palette, layout),
|
|
1486
1629
|
generateNavPillsThemed(scopeName, palette, layout),
|
|
1487
1630
|
generatePaletteClasses(scopeName, palette)
|
|
1488
1631
|
);
|
|
@@ -1707,6 +1850,8 @@ var structuralRules = {
|
|
|
1707
1850
|
},
|
|
1708
1851
|
'.bw_table caption': { 'font-size': '0.875rem', 'caption-side': 'bottom' },
|
|
1709
1852
|
'.bw_table_bordered > :not(caption) > * > *': { 'border-width': '1px', 'border-style': 'solid' },
|
|
1853
|
+
'.bw_table_selectable > tbody > tr': { 'cursor': 'pointer' },
|
|
1854
|
+
'.bw_table > tbody > tr.bw_table_row_selected > *': { 'background-color': 'rgba(0, 102, 102, 0.1)' },
|
|
1710
1855
|
'.bw_table_responsive': { 'overflow-x': 'auto', '-webkit-overflow-scrolling': 'touch' }
|
|
1711
1856
|
},
|
|
1712
1857
|
|
|
@@ -1760,6 +1905,7 @@ var structuralRules = {
|
|
|
1760
1905
|
'.bw_nav_tabs .bw_nav_item': { 'margin-bottom': '-2px' },
|
|
1761
1906
|
'.bw_nav_link': {
|
|
1762
1907
|
'display': 'block', 'font-size': '0.875rem', 'font-weight': '500',
|
|
1908
|
+
'padding': '0.625rem 1rem',
|
|
1763
1909
|
'text-decoration': 'none', 'cursor': 'pointer',
|
|
1764
1910
|
'border': 'none', 'background': 'transparent', 'font-family': 'inherit'
|
|
1765
1911
|
},
|
|
@@ -1794,10 +1940,11 @@ var structuralRules = {
|
|
|
1794
1940
|
'.bw_page_item': { 'display': 'list-item', 'list-style': 'none' },
|
|
1795
1941
|
'.bw_page_link': {
|
|
1796
1942
|
'position': 'relative', 'display': 'block', 'padding': '0.375rem 0.75rem',
|
|
1797
|
-
'margin-left': '-1px', 'line-height': '1.25', 'text-decoration': 'none'
|
|
1943
|
+
'margin-left': '-1px', 'line-height': '1.25', 'text-decoration': 'none',
|
|
1944
|
+
'border': '1px solid transparent', 'cursor': 'pointer',
|
|
1945
|
+
'font-family': 'inherit', 'font-size': 'inherit', 'background': 'none'
|
|
1798
1946
|
},
|
|
1799
|
-
'.bw_page_item:first-child .bw_page_link': { 'margin-left': '0'
|
|
1800
|
-
'.bw_page_item:last-child .bw_page_link': { 'border-top-right-radius': '0.375rem', 'border-bottom-right-radius': '0.375rem' },
|
|
1947
|
+
'.bw_page_item:first-child .bw_page_link': { 'margin-left': '0' },
|
|
1801
1948
|
'.bw_page_link:focus-visible': { 'z-index': '3', 'outline': '2px solid currentColor', 'outline-offset': '-2px' }
|
|
1802
1949
|
},
|
|
1803
1950
|
|
|
@@ -1954,6 +2101,7 @@ var structuralRules = {
|
|
|
1954
2101
|
'.bw_accordion_header': { 'margin': '0' },
|
|
1955
2102
|
'.bw_accordion_button': {
|
|
1956
2103
|
'position': 'relative', 'display': 'flex', 'align-items': 'center', 'width': '100%',
|
|
2104
|
+
'padding': '0.875rem 1.25rem',
|
|
1957
2105
|
'font-size': '1rem', 'font-weight': '500', 'text-align': 'left',
|
|
1958
2106
|
'background-color': 'transparent', 'border': '0', 'overflow-anchor': 'none', 'cursor': 'pointer',
|
|
1959
2107
|
'font-family': 'inherit'
|
|
@@ -1965,10 +2113,9 @@ var structuralRules = {
|
|
|
1965
2113
|
'background-repeat': 'no-repeat', 'background-size': '1.25rem'
|
|
1966
2114
|
},
|
|
1967
2115
|
'.bw_accordion_button:not(.bw_collapsed)::after': { 'transform': 'rotate(-180deg)' },
|
|
1968
|
-
'.
|
|
1969
|
-
'.bw_accordion_collapse
|
|
1970
|
-
'.
|
|
1971
|
-
'.bw_accordion_item:last-child': { 'border-bottom-left-radius': '8px', 'border-bottom-right-radius': '8px' }
|
|
2116
|
+
'.bw_accordion_body': { 'padding': '1rem 1.25rem' },
|
|
2117
|
+
'.bw_accordion_collapse': { 'max-height': '0', 'overflow': 'hidden', 'transition': 'max-height 0.3s ease' },
|
|
2118
|
+
'.bw_accordion_collapse.bw_collapse_show': { 'max-height': 'none' }
|
|
1972
2119
|
},
|
|
1973
2120
|
|
|
1974
2121
|
// ---- Carousel ----
|
|
@@ -2022,10 +2169,10 @@ var structuralRules = {
|
|
|
2022
2169
|
'position': 'relative', 'display': 'flex', 'flex-direction': 'column', 'pointer-events': 'auto',
|
|
2023
2170
|
'background-clip': 'padding-box', 'border': '1px solid transparent', 'outline': '0'
|
|
2024
2171
|
},
|
|
2025
|
-
'.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' },
|
|
2026
2173
|
'.bw_modal_title': { 'margin': '0', 'font-size': '1.25rem', 'font-weight': '600', 'line-height': '1.3' },
|
|
2027
|
-
'.bw_modal_body': { 'position': 'relative', 'flex': '1 1 auto' },
|
|
2028
|
-
'.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' }
|
|
2029
2176
|
},
|
|
2030
2177
|
|
|
2031
2178
|
// ---- Toast ----
|
|
@@ -2046,8 +2193,8 @@ var structuralRules = {
|
|
|
2046
2193
|
},
|
|
2047
2194
|
'.bw_toast.bw_toast_show': { 'opacity': '1', 'transform': 'translateY(0)' },
|
|
2048
2195
|
'.bw_toast.bw_toast_hiding': { 'opacity': '0' },
|
|
2049
|
-
'.bw_toast_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'font-size': '0.875rem' },
|
|
2050
|
-
'.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' }
|
|
2051
2198
|
},
|
|
2052
2199
|
|
|
2053
2200
|
// ---- Dropdown ----
|
|
@@ -2061,15 +2208,15 @@ var structuralRules = {
|
|
|
2061
2208
|
'.bw_dropdown_menu': {
|
|
2062
2209
|
'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': 'block',
|
|
2063
2210
|
'min-width': '10rem', 'padding': '0.5rem 0', 'margin': '0.125rem 0 0',
|
|
2064
|
-
'background-clip': 'padding-box',
|
|
2211
|
+
'background-clip': 'padding-box', 'border': '1px solid transparent',
|
|
2065
2212
|
'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none'
|
|
2066
2213
|
},
|
|
2067
2214
|
'.bw_dropdown_menu.bw_dropdown_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
|
|
2068
2215
|
'.bw_dropdown_menu_end': { 'left': 'auto', 'right': '0' },
|
|
2069
2216
|
'.bw_dropdown_item': {
|
|
2070
|
-
'display': 'block', 'width': '100%', 'clear': 'both',
|
|
2217
|
+
'display': 'block', 'width': '100%', 'padding': '0.4rem 1rem', 'clear': 'both',
|
|
2071
2218
|
'font-weight': '400', 'text-align': 'inherit', 'text-decoration': 'none', 'white-space': 'nowrap',
|
|
2072
|
-
'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem'
|
|
2219
|
+
'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem', 'cursor': 'pointer'
|
|
2073
2220
|
},
|
|
2074
2221
|
'.bw_dropdown_item:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '-2px' },
|
|
2075
2222
|
'.bw_dropdown_divider': { 'height': '0', 'margin': '0.5rem 0', 'overflow': 'hidden', 'opacity': '1' }
|
|
@@ -2114,7 +2261,13 @@ var structuralRules = {
|
|
|
2114
2261
|
|
|
2115
2262
|
// ---- Stat card ----
|
|
2116
2263
|
statCard: {
|
|
2117
|
-
'.bw_stat_card': {
|
|
2264
|
+
'.bw_stat_card': {
|
|
2265
|
+
'padding': '1.25rem',
|
|
2266
|
+
'border-left': '4px solid transparent',
|
|
2267
|
+
'border-radius': '0.375rem',
|
|
2268
|
+
'background-color': 'inherit',
|
|
2269
|
+
'transition': 'transform 0.15s ease'
|
|
2270
|
+
},
|
|
2118
2271
|
'.bw_stat_card:hover': { 'transform': 'translateY(-1px)' },
|
|
2119
2272
|
'.bw_stat_icon': { 'font-size': '1.5rem', 'margin-bottom': '0.5rem' },
|
|
2120
2273
|
'.bw_stat_value': { 'font-size': '2rem', 'font-weight': '700', 'line-height': '1.2' },
|
|
@@ -2385,6 +2538,33 @@ function generateUtilityRules() {
|
|
|
2385
2538
|
rules['.bw_text_left'] = { 'text-align': 'left' };
|
|
2386
2539
|
rules['.bw_text_right'] = { 'text-align': 'right' };
|
|
2387
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' };
|
|
2388
2568
|
|
|
2389
2569
|
// Flexbox
|
|
2390
2570
|
var jc = { start: 'flex-start', end: 'flex-end', center: 'center', between: 'space-between', around: 'space-around' };
|
|
@@ -2477,6 +2657,20 @@ function generateUtilityRules() {
|
|
|
2477
2657
|
rules['.list-inline-item'] = { 'display': 'inline-block' };
|
|
2478
2658
|
rules['.list-inline-item:not(:last-child)'] = { 'margin-right': '.5rem' };
|
|
2479
2659
|
|
|
2660
|
+
// Typography — bw_ prefixed utilities via loops
|
|
2661
|
+
var _imp = function(p, v) { var o = {}; o[p] = v + ' !important'; return o; };
|
|
2662
|
+
[['fs',{'xs':'0.75rem','sm':'0.875rem','base':'1rem','lg':'1.125rem','xl':'1.25rem','2xl':'1.5rem'},'font-size'],
|
|
2663
|
+
['fw',{light:'300',normal:'400',medium:'500',semibold:'600',bold:'700'},'font-weight'],
|
|
2664
|
+
['lh',{tight:'1.25',normal:'1.5',relaxed:'1.75'},'line-height']
|
|
2665
|
+
].forEach(function(d) { for (var dk in d[1]) rules['.bw_'+d[0]+'_'+dk] = _imp(d[2], d[1][dk]); });
|
|
2666
|
+
|
|
2667
|
+
// Flex utilities
|
|
2668
|
+
rules['.bw_flex'] = { 'display': 'flex' };
|
|
2669
|
+
rules['.bw_flex_column'] = { 'flex-direction': 'column' };
|
|
2670
|
+
rules['.bw_flex_wrap'] = { 'flex-wrap': 'wrap' };
|
|
2671
|
+
rules['.bw_flex_center'] = { 'display': 'flex', 'align-items': 'center', 'justify-content': 'center' };
|
|
2672
|
+
for (var gk in spacingValues) rules['.bw_gap_' + gk] = { 'gap': spacingValues[gk] + ' !important' };
|
|
2673
|
+
|
|
2480
2674
|
// Visibility
|
|
2481
2675
|
rules['.bw_visible, .visible'] = { 'visibility': 'visible !important' };
|
|
2482
2676
|
rules['.bw_invisible, .invisible'] = { 'visibility': 'hidden !important' };
|
|
@@ -2537,6 +2731,26 @@ function getStructuralStyles() {
|
|
|
2537
2731
|
return getStructuralCSS();
|
|
2538
2732
|
}
|
|
2539
2733
|
|
|
2734
|
+
/**
|
|
2735
|
+
* Get CSS reset rules only (box-sizing, html/body font, reduced-motion).
|
|
2736
|
+
* Separate from themed/structural rules for independent injection.
|
|
2737
|
+
* @returns {Object} CSS rules object for the reset layer
|
|
2738
|
+
*/
|
|
2739
|
+
function getResetStyles() {
|
|
2740
|
+
var rules = {};
|
|
2741
|
+
Object.assign(rules, structuralRules.base);
|
|
2742
|
+
// Include reduced-motion preference
|
|
2743
|
+
rules['@media (prefers-reduced-motion: reduce)'] = {
|
|
2744
|
+
'*, *::before, *::after': {
|
|
2745
|
+
'animation-duration': '0.01ms !important',
|
|
2746
|
+
'animation-iteration-count': '1 !important',
|
|
2747
|
+
'transition-duration': '0.01ms !important',
|
|
2748
|
+
'scroll-behavior': 'auto !important'
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
return rules;
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2540
2754
|
// =========================================================================
|
|
2541
2755
|
// defaultStyles — backward-compatible categorized view
|
|
2542
2756
|
// =========================================================================
|
|
@@ -2566,60 +2780,41 @@ Object.assign({}, structuralRules, {
|
|
|
2566
2780
|
});
|
|
2567
2781
|
|
|
2568
2782
|
/**
|
|
2569
|
-
*
|
|
2570
|
-
*
|
|
2571
|
-
*
|
|
2572
|
-
*
|
|
2573
|
-
* @param {
|
|
2574
|
-
*
|
|
2575
|
-
* @
|
|
2576
|
-
* @returns {Object} CSS rules object scoped under .bw_theme_alt (+ optional .name)
|
|
2783
|
+
* Prefix every selector in a rules object with a scope selector.
|
|
2784
|
+
* Handles @media/@keyframes blocks and comma-separated selectors.
|
|
2785
|
+
* @param {Object} rules - CSS rules object
|
|
2786
|
+
* @param {string} prefix - Scope prefix (e.g. '#my-dashboard', '.bw_theme_alt')
|
|
2787
|
+
* @param {boolean} [compound=false] - If true, use compound selector (no space)
|
|
2788
|
+
* for the first segment: `#scope.bw_theme_alt .sel` vs `#scope .sel`
|
|
2789
|
+
* @returns {Object} New rules object with scoped selectors
|
|
2577
2790
|
*/
|
|
2578
|
-
function
|
|
2579
|
-
|
|
2580
|
-
var
|
|
2581
|
-
|
|
2582
|
-
// Re-scope every selector under .bw_theme_alt (+ optional theme name)
|
|
2583
|
-
var altPrefix = name ? '.' + name + '.bw_theme_alt' : '.bw_theme_alt';
|
|
2584
|
-
var altRules = {};
|
|
2585
|
-
|
|
2586
|
-
for (var sel in rawRules) {
|
|
2587
|
-
if (!rawRules.hasOwnProperty(sel)) continue;
|
|
2588
|
-
|
|
2791
|
+
function scopeRulesUnder(rules, prefix, compound) {
|
|
2792
|
+
var scoped = {};
|
|
2793
|
+
for (var sel in rules) {
|
|
2794
|
+
if (!rules.hasOwnProperty(sel)) continue;
|
|
2589
2795
|
if (sel.charAt(0) === '@') {
|
|
2590
2796
|
// @media / @keyframes — recurse into the block
|
|
2591
|
-
var innerBlock =
|
|
2592
|
-
var
|
|
2797
|
+
var innerBlock = rules[sel];
|
|
2798
|
+
var scopedInner = {};
|
|
2593
2799
|
for (var innerSel in innerBlock) {
|
|
2594
2800
|
if (!innerBlock.hasOwnProperty(innerSel)) continue;
|
|
2595
|
-
|
|
2801
|
+
scopedInner[_prefixSelector(innerSel, prefix)] = innerBlock[innerSel];
|
|
2596
2802
|
}
|
|
2597
|
-
|
|
2803
|
+
scoped[sel] = scopedInner;
|
|
2598
2804
|
} else {
|
|
2599
|
-
|
|
2600
|
-
// Handle comma-separated selectors
|
|
2601
|
-
var parts = sel.split(',');
|
|
2602
|
-
var scopedParts = [];
|
|
2603
|
-
for (var i = 0; i < parts.length; i++) {
|
|
2604
|
-
var s = parts[i].trim();
|
|
2605
|
-
// 'body' selector gets special treatment: .bw_theme_alt body
|
|
2606
|
-
if (s === 'body' || s.indexOf('body') === 0) {
|
|
2607
|
-
scopedParts.push(altPrefix + ' ' + s);
|
|
2608
|
-
} else {
|
|
2609
|
-
scopedParts.push(altPrefix + ' ' + s);
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
altRules[scopedParts.join(', ')] = rawRules[sel];
|
|
2805
|
+
scoped[_prefixSelector(sel, prefix)] = rules[sel];
|
|
2613
2806
|
}
|
|
2614
2807
|
}
|
|
2808
|
+
return scoped;
|
|
2809
|
+
}
|
|
2615
2810
|
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
return
|
|
2811
|
+
function _prefixSelector(sel, prefix) {
|
|
2812
|
+
var parts = sel.split(',');
|
|
2813
|
+
var result = [];
|
|
2814
|
+
for (var i = 0; i < parts.length; i++) {
|
|
2815
|
+
result.push(prefix + ' ' + parts[i].trim());
|
|
2816
|
+
}
|
|
2817
|
+
return result.join(', ');
|
|
2623
2818
|
}
|
|
2624
2819
|
|
|
2625
2820
|
/**
|
|
@@ -3319,12 +3514,11 @@ const bw = {
|
|
|
3319
3514
|
_subIdCounter: 0, // monotonic ID for subscriptions
|
|
3320
3515
|
|
|
3321
3516
|
// ── Node reference cache ──────────────────────────────────────────────
|
|
3322
|
-
// Fast O(1) lookup for elements by
|
|
3517
|
+
// Fast O(1) lookup for elements by id attribute or bw_uuid_* class.
|
|
3323
3518
|
//
|
|
3324
3519
|
// Populated by bw.createDOM() when elements have:
|
|
3325
|
-
// - data-bw_id attribute (user-declared addressable elements)
|
|
3326
3520
|
// - id attribute (standard HTML id)
|
|
3327
|
-
// -
|
|
3521
|
+
// - bw_uuid_* class (lifecycle-managed or explicitly addressed elements)
|
|
3328
3522
|
//
|
|
3329
3523
|
// Cleaned up by bw.cleanup() when elements are destroyed via bitwrench APIs.
|
|
3330
3524
|
// On cache miss, falls back to querySelector/getElementById — never fails,
|
|
@@ -3332,7 +3526,7 @@ const bw = {
|
|
|
3332
3526
|
// via parentNode === null check (IE11-safe, unlike el.isConnected).
|
|
3333
3527
|
//
|
|
3334
3528
|
// Elements created via bw.createDOM() also get el._bw_refs — a local map of
|
|
3335
|
-
// child
|
|
3529
|
+
// child id/UUID -> DOM node ref for fast parent->child access in o.render.
|
|
3336
3530
|
// This is the bitwrench equivalent of React's compiled template "holes".
|
|
3337
3531
|
//
|
|
3338
3532
|
// Contract: if you remove elements outside of bitwrench APIs (raw el.remove()),
|
|
@@ -3412,7 +3606,6 @@ Object.defineProperty(bw, '_isBrowser', {
|
|
|
3412
3606
|
// _cw console.warn 8
|
|
3413
3607
|
// _cl console.log 11
|
|
3414
3608
|
// _ce console.error 4
|
|
3415
|
-
// _chp ComponentHandle.prototype 28 (defined after constructor)
|
|
3416
3609
|
//
|
|
3417
3610
|
// Note: document.createElement etc. are NOT aliased because they require
|
|
3418
3611
|
// `this === document` and .bind() would add overhead on every call.
|
|
@@ -3585,15 +3778,15 @@ bw.uuid = function(prefix) {
|
|
|
3585
3778
|
* 1. Check `bw._nodeMap[id]` — if found and still attached (parentNode !== null), return it
|
|
3586
3779
|
* 2. If cached ref is detached (parentNode === null), remove stale entry
|
|
3587
3780
|
* 3. Fall back to `document.getElementById(id)` then `document.querySelector(...)`
|
|
3588
|
-
* 4.
|
|
3589
|
-
* 5.
|
|
3781
|
+
* 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
3782
|
+
* 5. Cache the result for next time
|
|
3590
3783
|
*
|
|
3591
3784
|
* Accepts a DOM element directly (pass-through) or a string identifier.
|
|
3592
3785
|
* String identifiers are tried as: direct map key, getElementById,
|
|
3593
3786
|
* querySelector (for CSS selectors starting with . or #), and
|
|
3594
|
-
*
|
|
3787
|
+
* bw_uuid_* class selector.
|
|
3595
3788
|
*
|
|
3596
|
-
* @param {string|Element} id - Element ID, CSS selector,
|
|
3789
|
+
* @param {string|Element} id - Element ID, CSS selector, bw_uuid_* class, or DOM element
|
|
3597
3790
|
* @returns {Element|null} The DOM element, or null if not found
|
|
3598
3791
|
* @category Internal
|
|
3599
3792
|
*/
|
|
@@ -3622,9 +3815,9 @@ bw._el = function(id) {
|
|
|
3622
3815
|
el = document.querySelector(id);
|
|
3623
3816
|
}
|
|
3624
3817
|
|
|
3625
|
-
// 4. Try
|
|
3626
|
-
if (!el) {
|
|
3627
|
-
el = document.querySelector('
|
|
3818
|
+
// 4. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
3819
|
+
if (!el && id.indexOf('bw_uuid_') === 0) {
|
|
3820
|
+
el = document.querySelector('.' + id);
|
|
3628
3821
|
}
|
|
3629
3822
|
|
|
3630
3823
|
// 5. Cache the result for next time
|
|
@@ -3639,17 +3832,17 @@ bw._el = function(id) {
|
|
|
3639
3832
|
* Register a DOM element in the node cache under one or more keys.
|
|
3640
3833
|
*
|
|
3641
3834
|
* Called internally by `bw.createDOM()`. Registers elements that have
|
|
3642
|
-
* id attributes,
|
|
3835
|
+
* id attributes, UUID classes, or both.
|
|
3643
3836
|
*
|
|
3644
3837
|
* @param {Element} el - DOM element to register
|
|
3645
|
-
* @param {string} [
|
|
3838
|
+
* @param {string} [uuid] - bw_uuid_* class token to register under
|
|
3646
3839
|
* @category Internal
|
|
3647
3840
|
*/
|
|
3648
|
-
bw._registerNode = function(el,
|
|
3841
|
+
bw._registerNode = function(el, uuid) {
|
|
3649
3842
|
if (!el) return;
|
|
3650
|
-
// Register under
|
|
3651
|
-
if (
|
|
3652
|
-
bw._nodeMap[
|
|
3843
|
+
// Register under UUID class token
|
|
3844
|
+
if (uuid) {
|
|
3845
|
+
bw._nodeMap[uuid] = el;
|
|
3653
3846
|
}
|
|
3654
3847
|
// Register under id attribute
|
|
3655
3848
|
var htmlId = el.getAttribute ? el.getAttribute('id') : null;
|
|
@@ -3665,13 +3858,13 @@ bw._registerNode = function(el, bwId) {
|
|
|
3665
3858
|
* through bitwrench APIs.
|
|
3666
3859
|
*
|
|
3667
3860
|
* @param {Element} el - DOM element to deregister
|
|
3668
|
-
* @param {string} [
|
|
3861
|
+
* @param {string} [uuid] - bw_uuid_* class token to remove
|
|
3669
3862
|
* @category Internal
|
|
3670
3863
|
*/
|
|
3671
|
-
bw._deregisterNode = function(el,
|
|
3672
|
-
// Remove
|
|
3673
|
-
if (
|
|
3674
|
-
delete bw._nodeMap[
|
|
3864
|
+
bw._deregisterNode = function(el, uuid) {
|
|
3865
|
+
// Remove UUID class entry
|
|
3866
|
+
if (uuid) {
|
|
3867
|
+
delete bw._nodeMap[uuid];
|
|
3675
3868
|
}
|
|
3676
3869
|
// Remove id attribute entry
|
|
3677
3870
|
var htmlId = el && el.getAttribute ? el.getAttribute('id') : null;
|
|
@@ -3680,6 +3873,91 @@ bw._deregisterNode = function(el, bwId) {
|
|
|
3680
3873
|
}
|
|
3681
3874
|
};
|
|
3682
3875
|
|
|
3876
|
+
// ===================================================================================
|
|
3877
|
+
// bw.assignUUID() / bw.getUUID() — Explicit UUID addressing for TACO objects
|
|
3878
|
+
// ===================================================================================
|
|
3879
|
+
|
|
3880
|
+
/**
|
|
3881
|
+
* Marker class for elements with lifecycle hooks (mounted/unmount/render/state).
|
|
3882
|
+
* Used by cleanup() to find lifecycle-managed elements via querySelectorAll('.bw_lc').
|
|
3883
|
+
* @private
|
|
3884
|
+
*/
|
|
3885
|
+
var _BW_LC = 'bw_lc';
|
|
3886
|
+
|
|
3887
|
+
/**
|
|
3888
|
+
* Regex to match a bw_uuid_* token in a class string.
|
|
3889
|
+
* @private
|
|
3890
|
+
*/
|
|
3891
|
+
var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
|
|
3892
|
+
|
|
3893
|
+
/**
|
|
3894
|
+
* Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
|
|
3895
|
+
*
|
|
3896
|
+
* Idempotent by default — calling twice returns the same UUID. Pass `forceNew=true`
|
|
3897
|
+
* to replace an existing UUID (useful in loops where each TACO needs a unique ID).
|
|
3898
|
+
*
|
|
3899
|
+
* @param {Object} taco - A TACO object `{t, a, c, o}`
|
|
3900
|
+
* @param {boolean} [forceNew=false] - If true, replaces any existing UUID with a new one
|
|
3901
|
+
* @returns {string} The UUID string (e.g. 'bw_uuid_a1b2c3d4e5')
|
|
3902
|
+
* @category Identifiers
|
|
3903
|
+
* @example
|
|
3904
|
+
* var card = bw.makeStatCard({ value: '0', label: 'Scans' });
|
|
3905
|
+
* var uuid = bw.assignUUID(card); // 'bw_uuid_a1b2c3d4e5'
|
|
3906
|
+
* var same = bw.assignUUID(card); // same UUID (idempotent)
|
|
3907
|
+
* var diff = bw.assignUUID(card, true); // new UUID (forced)
|
|
3908
|
+
*/
|
|
3909
|
+
bw.assignUUID = function(taco, forceNew) {
|
|
3910
|
+
if (!taco || !_is(taco, 'object')) return null;
|
|
3911
|
+
|
|
3912
|
+
// Ensure taco.a exists
|
|
3913
|
+
if (!taco.a) taco.a = {};
|
|
3914
|
+
if (!_is(taco.a.class, 'string')) taco.a.class = taco.a.class ? String(taco.a.class) : '';
|
|
3915
|
+
|
|
3916
|
+
var existing = taco.a.class.match(_UUID_RE);
|
|
3917
|
+
|
|
3918
|
+
if (existing && !forceNew) {
|
|
3919
|
+
return existing[0];
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
// Remove old UUID if forceNew
|
|
3923
|
+
if (existing) {
|
|
3924
|
+
taco.a.class = taco.a.class.replace(_UUID_RE, '').replace(/\s+/g, ' ').trim();
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3927
|
+
var uuid = bw.uuid('uuid');
|
|
3928
|
+
taco.a.class = (taco.a.class ? taco.a.class + ' ' : '') + uuid;
|
|
3929
|
+
return uuid;
|
|
3930
|
+
};
|
|
3931
|
+
|
|
3932
|
+
/**
|
|
3933
|
+
* Read the UUID from a TACO object or DOM element. Pure getter, no side effects.
|
|
3934
|
+
*
|
|
3935
|
+
* @param {Object|Element} tacoOrElement - A TACO object or DOM element
|
|
3936
|
+
* @returns {string|null} The UUID string, or null if none assigned
|
|
3937
|
+
* @category Identifiers
|
|
3938
|
+
* @example
|
|
3939
|
+
* bw.getUUID(card) // 'bw_uuid_a1b2c3d4e5' (from TACO)
|
|
3940
|
+
* bw.getUUID(domEl) // 'bw_uuid_a1b2c3d4e5' (from DOM element)
|
|
3941
|
+
* bw.getUUID({t:'div'}) // null (no UUID)
|
|
3942
|
+
*/
|
|
3943
|
+
bw.getUUID = function(tacoOrElement) {
|
|
3944
|
+
if (!tacoOrElement) return null;
|
|
3945
|
+
|
|
3946
|
+
var classStr;
|
|
3947
|
+
// DOM element: check className
|
|
3948
|
+
if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
|
|
3949
|
+
classStr = tacoOrElement.className;
|
|
3950
|
+
}
|
|
3951
|
+
// TACO object: check a.class
|
|
3952
|
+
else if (tacoOrElement.a && _is(tacoOrElement.a.class, 'string')) {
|
|
3953
|
+
classStr = tacoOrElement.a.class;
|
|
3954
|
+
}
|
|
3955
|
+
|
|
3956
|
+
if (!classStr) return null;
|
|
3957
|
+
var match = classStr.match(_UUID_RE);
|
|
3958
|
+
return match ? match[0] : null;
|
|
3959
|
+
};
|
|
3960
|
+
|
|
3683
3961
|
/**
|
|
3684
3962
|
* Escape HTML special characters to prevent XSS.
|
|
3685
3963
|
*
|
|
@@ -3729,6 +4007,42 @@ bw.raw = function(str) {
|
|
|
3729
4007
|
return { __bw_raw: true, v: String(str) };
|
|
3730
4008
|
};
|
|
3731
4009
|
|
|
4010
|
+
/**
|
|
4011
|
+
* Hyperscript-style TACO constructor.
|
|
4012
|
+
*
|
|
4013
|
+
* A convenience helper that returns a canonical TACO object from positional
|
|
4014
|
+
* arguments. The return value is a plain object — serializable, works with
|
|
4015
|
+
* bwserve, and accepted everywhere TACO is accepted.
|
|
4016
|
+
*
|
|
4017
|
+
* @param {string} tag - HTML tag name (e.g. 'div', 'p', 'section')
|
|
4018
|
+
* @param {Object|null} [attrs] - HTML attributes object. Pass null or omit to skip.
|
|
4019
|
+
* @param {*} [content] - Content: string, number, TACO object, or array of children.
|
|
4020
|
+
* @param {Object} [options] - TACO options (state, lifecycle hooks, render fn).
|
|
4021
|
+
* @returns {Object} Plain TACO object {t, a?, c?, o?}
|
|
4022
|
+
* @category Utilities
|
|
4023
|
+
* @see bw.html
|
|
4024
|
+
* @see bw.createDOM
|
|
4025
|
+
* @see bw.DOM
|
|
4026
|
+
* @example
|
|
4027
|
+
* bw.h('div')
|
|
4028
|
+
* // => { t: 'div' }
|
|
4029
|
+
*
|
|
4030
|
+
* bw.h('p', { class: 'bw_text_muted' }, 'Hello')
|
|
4031
|
+
* // => { t: 'p', a: { class: 'bw_text_muted' }, c: 'Hello' }
|
|
4032
|
+
*
|
|
4033
|
+
* bw.h('ul', null, [
|
|
4034
|
+
* bw.h('li', null, 'one'),
|
|
4035
|
+
* bw.h('li', null, 'two')
|
|
4036
|
+
* ])
|
|
4037
|
+
* // => { t: 'ul', c: [{ t: 'li', c: 'one' }, { t: 'li', c: 'two' }] }
|
|
4038
|
+
*/
|
|
4039
|
+
bw.h = function(tag, attrs, content, options) {
|
|
4040
|
+
var taco = { t: String(tag) };
|
|
4041
|
+
if (attrs !== null && attrs !== undefined) taco.a = attrs;
|
|
4042
|
+
if (content !== undefined) taco.c = content;
|
|
4043
|
+
if (options !== undefined) taco.o = options;
|
|
4044
|
+
return taco;
|
|
4045
|
+
};
|
|
3732
4046
|
|
|
3733
4047
|
/**
|
|
3734
4048
|
* Convert a TACO object (or array of TACOs) to an HTML string.
|
|
@@ -3758,15 +4072,6 @@ bw.html = function(taco, options = {}) {
|
|
|
3758
4072
|
// Handle null/undefined
|
|
3759
4073
|
if (taco == null) return '';
|
|
3760
4074
|
|
|
3761
|
-
// Handle ComponentHandle — use its .taco
|
|
3762
|
-
if (taco && taco._bwComponent === true) {
|
|
3763
|
-
var compOptions = Object.assign({}, options);
|
|
3764
|
-
if (!compOptions.state && taco._state) {
|
|
3765
|
-
compOptions.state = taco._state;
|
|
3766
|
-
}
|
|
3767
|
-
return bw.html(taco.taco, compOptions);
|
|
3768
|
-
}
|
|
3769
|
-
|
|
3770
4075
|
// Handle arrays of TACOs
|
|
3771
4076
|
if (_isA(taco)) {
|
|
3772
4077
|
return taco.map(t => bw.html(t, options)).join('');
|
|
@@ -3777,24 +4082,6 @@ bw.html = function(taco, options = {}) {
|
|
|
3777
4082
|
return taco.v;
|
|
3778
4083
|
}
|
|
3779
4084
|
|
|
3780
|
-
// Handle bw.when() markers
|
|
3781
|
-
if (taco && taco._bwWhen && options.state) {
|
|
3782
|
-
var whenExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
3783
|
-
var whenVal = options.compile
|
|
3784
|
-
? bw._resolveTemplate('${' + whenExpr + '}', options.state, true)
|
|
3785
|
-
: bw._evaluatePath(options.state, whenExpr);
|
|
3786
|
-
var branch = whenVal ? taco.branches[0] : (taco.branches[1] || null);
|
|
3787
|
-
return branch ? bw.html(branch, options) : '';
|
|
3788
|
-
}
|
|
3789
|
-
|
|
3790
|
-
// Handle bw.each() markers
|
|
3791
|
-
if (taco && taco._bwEach && options.state) {
|
|
3792
|
-
var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
3793
|
-
var arr = bw._evaluatePath(options.state, eachExpr);
|
|
3794
|
-
if (!_isA(arr)) return '';
|
|
3795
|
-
return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
|
|
3796
|
-
}
|
|
3797
|
-
|
|
3798
4085
|
// Handle primitives and non-TACO objects
|
|
3799
4086
|
if (!_is(taco, 'object') || !taco.t) {
|
|
3800
4087
|
var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
|
|
@@ -3858,14 +4145,14 @@ bw.html = function(taco, options = {}) {
|
|
|
3858
4145
|
}
|
|
3859
4146
|
}
|
|
3860
4147
|
|
|
3861
|
-
// Add
|
|
3862
|
-
if ((opts.mounted || opts.unmount) && !attrs.class
|
|
3863
|
-
const
|
|
4148
|
+
// Add bw_uuid + bw_lc classes if lifecycle hooks present
|
|
4149
|
+
if ((opts.mounted || opts.unmount) && !_UUID_RE.test(attrs.class || '')) {
|
|
4150
|
+
const uuid = bw.uuid('uuid');
|
|
3864
4151
|
attrStr = attrStr.replace(/class="([^"]*)"/, (_match, classes) => {
|
|
3865
|
-
return `class="${classes}
|
|
4152
|
+
return `class="${classes} ${uuid} ${_BW_LC}"`.trim();
|
|
3866
4153
|
});
|
|
3867
4154
|
if (!attrStr.includes('class=')) {
|
|
3868
|
-
attrStr += ` class="
|
|
4155
|
+
attrStr += ` class="${uuid} ${_BW_LC}"`;
|
|
3869
4156
|
}
|
|
3870
4157
|
}
|
|
3871
4158
|
|
|
@@ -3993,7 +4280,7 @@ bw.htmlPage = function(opts) {
|
|
|
3993
4280
|
? (THEME_PRESETS[theme.toLowerCase()] || null)
|
|
3994
4281
|
: theme;
|
|
3995
4282
|
if (themeConfig) {
|
|
3996
|
-
var themeResult = bw.
|
|
4283
|
+
var themeResult = bw.makeStyles(themeConfig);
|
|
3997
4284
|
themeCSS = themeResult.css;
|
|
3998
4285
|
}
|
|
3999
4286
|
}
|
|
@@ -4019,14 +4306,14 @@ bw.htmlPage = function(opts) {
|
|
|
4019
4306
|
// Combine all CSS
|
|
4020
4307
|
var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
|
|
4021
4308
|
|
|
4022
|
-
// Body-end script: registry entries + optional
|
|
4309
|
+
// Body-end script: registry entries + optional loadStyles
|
|
4023
4310
|
var bodyEndScript = '';
|
|
4024
4311
|
var bodyEndParts = [];
|
|
4025
4312
|
if (registryEntries) {
|
|
4026
4313
|
bodyEndParts.push(registryEntries);
|
|
4027
4314
|
}
|
|
4028
4315
|
if (runtime === 'inline' || runtime === 'cdn') {
|
|
4029
|
-
bodyEndParts.push('if(typeof bw!=="undefined"){bw.
|
|
4316
|
+
bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadStyles();}');
|
|
4030
4317
|
}
|
|
4031
4318
|
if (bodyEndParts.length > 0) {
|
|
4032
4319
|
bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
|
|
@@ -4093,11 +4380,6 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4093
4380
|
return frag;
|
|
4094
4381
|
}
|
|
4095
4382
|
|
|
4096
|
-
// Handle ComponentHandle — extract .taco for DOM creation
|
|
4097
|
-
if (taco && taco._bwComponent === true) {
|
|
4098
|
-
return bw.createDOM(taco.taco, options);
|
|
4099
|
-
}
|
|
4100
|
-
|
|
4101
4383
|
// Handle text nodes
|
|
4102
4384
|
if (!_is(taco, 'object') || !taco.t) {
|
|
4103
4385
|
return document.createTextNode(String(taco));
|
|
@@ -4138,24 +4420,19 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4138
4420
|
}
|
|
4139
4421
|
|
|
4140
4422
|
// Add children, building _bw_refs for fast parent→child access.
|
|
4141
|
-
// Children with
|
|
4423
|
+
// Children with id attributes or bw_uuid_* classes get local refs on the parent,
|
|
4142
4424
|
// so o.render functions can access them without any DOM lookup.
|
|
4143
4425
|
if (content != null) {
|
|
4144
4426
|
if (_isA(content)) {
|
|
4145
4427
|
content.forEach(child => {
|
|
4146
4428
|
if (child != null) {
|
|
4147
|
-
// Handle ComponentHandle in content arrays (Level 2 children)
|
|
4148
|
-
if (child._bwComponent === true) {
|
|
4149
|
-
child.mount(el);
|
|
4150
|
-
return;
|
|
4151
|
-
}
|
|
4152
4429
|
var childEl = bw.createDOM(child, options);
|
|
4153
4430
|
el.appendChild(childEl);
|
|
4154
4431
|
// Build local refs for addressable children
|
|
4155
|
-
var
|
|
4156
|
-
if (
|
|
4432
|
+
var childRefId = (child && child.a) ? (child.a.id || bw.getUUID(child)) : null;
|
|
4433
|
+
if (childRefId) {
|
|
4157
4434
|
if (!el._bw_refs) el._bw_refs = {};
|
|
4158
|
-
el._bw_refs[
|
|
4435
|
+
el._bw_refs[childRefId] = childEl;
|
|
4159
4436
|
}
|
|
4160
4437
|
// Bubble up grandchild refs (flatten one level)
|
|
4161
4438
|
if (childEl._bw_refs) {
|
|
@@ -4171,16 +4448,13 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4171
4448
|
} else if (_is(content, 'object') && content.__bw_raw) {
|
|
4172
4449
|
// Raw HTML content — inject via innerHTML
|
|
4173
4450
|
el.innerHTML = content.v;
|
|
4174
|
-
} else if (content._bwComponent === true) {
|
|
4175
|
-
// Single ComponentHandle as content
|
|
4176
|
-
content.mount(el);
|
|
4177
4451
|
} else if (_is(content, 'object') && content.t) {
|
|
4178
4452
|
var childEl = bw.createDOM(content, options);
|
|
4179
4453
|
el.appendChild(childEl);
|
|
4180
|
-
var
|
|
4181
|
-
if (
|
|
4454
|
+
var childRefId = content.a ? (content.a.id || bw.getUUID(content)) : null;
|
|
4455
|
+
if (childRefId) {
|
|
4182
4456
|
if (!el._bw_refs) el._bw_refs = {};
|
|
4183
|
-
el._bw_refs[
|
|
4457
|
+
el._bw_refs[childRefId] = childEl;
|
|
4184
4458
|
}
|
|
4185
4459
|
if (childEl._bw_refs) {
|
|
4186
4460
|
if (!el._bw_refs) el._bw_refs = {};
|
|
@@ -4200,59 +4474,98 @@ bw.createDOM = function(taco, options = {}) {
|
|
|
4200
4474
|
bw._registerNode(el, null);
|
|
4201
4475
|
}
|
|
4202
4476
|
|
|
4477
|
+
// Register UUID class in node cache (bw_uuid_* tokens in class string)
|
|
4478
|
+
if (el.className) {
|
|
4479
|
+
var uuidMatch = el.className.match(_UUID_RE);
|
|
4480
|
+
if (uuidMatch) {
|
|
4481
|
+
bw._nodeMap[uuidMatch[0]] = el;
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4203
4485
|
// Handle lifecycle hooks and state
|
|
4204
4486
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
4205
|
-
|
|
4206
|
-
el.
|
|
4487
|
+
// Ensure element has a UUID class for identity
|
|
4488
|
+
var uuid = bw.getUUID(el) || bw.uuid('uuid');
|
|
4489
|
+
el.classList.add(uuid);
|
|
4490
|
+
el.classList.add(_BW_LC);
|
|
4207
4491
|
|
|
4208
|
-
// Register in node cache under
|
|
4209
|
-
bw._registerNode(el,
|
|
4492
|
+
// Register in node cache under UUID class
|
|
4493
|
+
bw._registerNode(el, uuid);
|
|
4210
4494
|
|
|
4211
4495
|
// Store state
|
|
4212
4496
|
if (opts.state) {
|
|
4213
4497
|
el._bw_state = opts.state;
|
|
4214
4498
|
}
|
|
4215
4499
|
|
|
4216
|
-
// o.render —
|
|
4500
|
+
// o.render — store the render function for bw.update()
|
|
4217
4501
|
if (opts.render) {
|
|
4218
4502
|
el._bw_render = opts.render;
|
|
4503
|
+
}
|
|
4219
4504
|
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4505
|
+
// Determine what to call on mount:
|
|
4506
|
+
// - If o.mounted exists, call it (it can call el._bw_render() for initial render)
|
|
4507
|
+
// - Otherwise if o.render exists, auto-call it as a convenience shorthand
|
|
4508
|
+
var mountFn = opts.mounted || (opts.render ? function(mountEl) {
|
|
4509
|
+
opts.render(mountEl, mountEl._bw_state || {});
|
|
4510
|
+
} : null);
|
|
4223
4511
|
|
|
4224
|
-
|
|
4225
|
-
if (document.body.contains(el)) {
|
|
4226
|
-
opts.render(el, el._bw_state || {});
|
|
4227
|
-
} else {
|
|
4228
|
-
requestAnimationFrame(() => {
|
|
4229
|
-
if (document.body.contains(el)) {
|
|
4230
|
-
opts.render(el, el._bw_state || {});
|
|
4231
|
-
}
|
|
4232
|
-
});
|
|
4233
|
-
}
|
|
4234
|
-
} else if (opts.mounted) {
|
|
4235
|
-
// Queue mounted callback (legacy pattern)
|
|
4512
|
+
if (mountFn) {
|
|
4236
4513
|
if (document.body.contains(el)) {
|
|
4237
|
-
|
|
4514
|
+
mountFn(el, el._bw_state || {});
|
|
4238
4515
|
} else {
|
|
4239
4516
|
requestAnimationFrame(() => {
|
|
4240
4517
|
if (document.body.contains(el)) {
|
|
4241
|
-
|
|
4518
|
+
mountFn(el, el._bw_state || {});
|
|
4242
4519
|
}
|
|
4243
4520
|
});
|
|
4244
4521
|
}
|
|
4245
4522
|
}
|
|
4246
4523
|
|
|
4247
|
-
// Store unmount callback
|
|
4524
|
+
// Store unmount callback keyed by UUID class
|
|
4248
4525
|
if (opts.unmount) {
|
|
4249
|
-
bw._unmountCallbacks.set(
|
|
4526
|
+
bw._unmountCallbacks.set(uuid, () => {
|
|
4250
4527
|
opts.unmount(el, el._bw_state || {});
|
|
4251
4528
|
});
|
|
4252
4529
|
}
|
|
4253
|
-
}
|
|
4254
|
-
|
|
4255
|
-
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
// Component handle: attach methods to el.bw namespace
|
|
4533
|
+
if (opts.handle || opts.slots) {
|
|
4534
|
+
if (!el.bw) el.bw = {};
|
|
4535
|
+
|
|
4536
|
+
// Explicit handle methods: fn(el, ...args) -> el.bw.method(...args)
|
|
4537
|
+
if (opts.handle) {
|
|
4538
|
+
for (var hk in opts.handle) {
|
|
4539
|
+
if (_hop.call(opts.handle, hk)) {
|
|
4540
|
+
el.bw[hk] = opts.handle[hk].bind(null, el);
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
// Slot declarations: auto-generate setX/getX pairs
|
|
4546
|
+
if (opts.slots) {
|
|
4547
|
+
for (var sk in opts.slots) {
|
|
4548
|
+
if (_hop.call(opts.slots, sk)) {
|
|
4549
|
+
(function(name, selector) {
|
|
4550
|
+
var cap = name.charAt(0).toUpperCase() + name.slice(1);
|
|
4551
|
+
el.bw['set' + cap] = function(value) {
|
|
4552
|
+
var t = el.querySelector(selector);
|
|
4553
|
+
if (!t) return;
|
|
4554
|
+
if (value != null && typeof value === 'object' && value.t) {
|
|
4555
|
+
t.innerHTML = '';
|
|
4556
|
+
t.appendChild(bw.createDOM(value));
|
|
4557
|
+
} else {
|
|
4558
|
+
t.textContent = (value != null) ? String(value) : '';
|
|
4559
|
+
}
|
|
4560
|
+
};
|
|
4561
|
+
el.bw['get' + cap] = function() {
|
|
4562
|
+
var t = el.querySelector(selector);
|
|
4563
|
+
return t ? t.textContent : '';
|
|
4564
|
+
};
|
|
4565
|
+
})(sk, opts.slots[sk]);
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
}
|
|
4256
4569
|
}
|
|
4257
4570
|
|
|
4258
4571
|
return el;
|
|
@@ -4299,7 +4612,7 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
4299
4612
|
// the target is the mount point, not the content being replaced)
|
|
4300
4613
|
const savedState = targetEl._bw_state;
|
|
4301
4614
|
const savedRender = targetEl._bw_render;
|
|
4302
|
-
const
|
|
4615
|
+
const savedUuid = bw.getUUID(targetEl);
|
|
4303
4616
|
const savedSubs = targetEl._bw_subs;
|
|
4304
4617
|
|
|
4305
4618
|
// Temporarily remove _bw_subs so cleanup doesn't call them
|
|
@@ -4311,10 +4624,9 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
4311
4624
|
// Restore the target's own state/render/subs after cleanup
|
|
4312
4625
|
if (savedState !== undefined) targetEl._bw_state = savedState;
|
|
4313
4626
|
if (savedRender) targetEl._bw_render = savedRender;
|
|
4314
|
-
if (
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
bw._registerNode(targetEl, savedBwId);
|
|
4627
|
+
if (savedUuid) {
|
|
4628
|
+
// UUID class stays on element through cleanup; re-register in cache
|
|
4629
|
+
bw._registerNode(targetEl, savedUuid);
|
|
4318
4630
|
}
|
|
4319
4631
|
if (savedSubs) targetEl._bw_subs = savedSubs;
|
|
4320
4632
|
|
|
@@ -4322,25 +4634,11 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
4322
4634
|
targetEl.innerHTML = '';
|
|
4323
4635
|
|
|
4324
4636
|
if (taco != null) {
|
|
4325
|
-
// Handle ComponentHandle (reactive components from bw.component())
|
|
4326
|
-
if (taco._bwComponent === true) {
|
|
4327
|
-
taco.mount(targetEl);
|
|
4328
|
-
}
|
|
4329
|
-
// Handle component handles (objects with element property)
|
|
4330
|
-
else if (taco.element instanceof Element) {
|
|
4331
|
-
targetEl.appendChild(taco.element);
|
|
4332
|
-
}
|
|
4333
4637
|
// Handle arrays
|
|
4334
|
-
|
|
4638
|
+
if (_isA(taco)) {
|
|
4335
4639
|
taco.forEach(t => {
|
|
4336
4640
|
if (t != null) {
|
|
4337
|
-
|
|
4338
|
-
t.mount(targetEl);
|
|
4339
|
-
} else if (t.element instanceof Element) {
|
|
4340
|
-
targetEl.appendChild(t.element);
|
|
4341
|
-
} else {
|
|
4342
|
-
targetEl.appendChild(bw.createDOM(t, options));
|
|
4343
|
-
}
|
|
4641
|
+
targetEl.appendChild(bw.createDOM(t, options));
|
|
4344
4642
|
}
|
|
4345
4643
|
});
|
|
4346
4644
|
}
|
|
@@ -4353,240 +4651,80 @@ bw.DOM = function(target, taco, options = {}) {
|
|
|
4353
4651
|
return targetEl;
|
|
4354
4652
|
};
|
|
4355
4653
|
|
|
4654
|
+
// Deprecation stubs for removed ComponentHandle APIs
|
|
4655
|
+
bw.compileProps = function() { throw new Error('bw.compileProps() removed in v2.0.19. Use o.handle/o.slots instead.'); };
|
|
4656
|
+
bw.renderComponent = function() { throw new Error('bw.renderComponent() removed in v2.0.19. Use bw.mount() with o.handle/o.slots instead.'); };
|
|
4657
|
+
|
|
4356
4658
|
/**
|
|
4357
|
-
*
|
|
4358
|
-
*
|
|
4359
|
-
*
|
|
4360
|
-
* where setting a property triggers `handle.onPropChange()`.
|
|
4659
|
+
* Mount a TACO into a target element and return the created root element.
|
|
4660
|
+
* Like bw.DOM() but returns the root element of the TACO (not the container),
|
|
4661
|
+
* giving direct access to el.bw handle methods.
|
|
4361
4662
|
*
|
|
4362
|
-
* @param {
|
|
4363
|
-
* @param {Object}
|
|
4364
|
-
* @
|
|
4663
|
+
* @param {string|Element} target - CSS selector or DOM element
|
|
4664
|
+
* @param {Object} taco - TACO to render
|
|
4665
|
+
* @param {Object} [options] - Mount options
|
|
4666
|
+
* @returns {Element} The created root element
|
|
4365
4667
|
* @category DOM Generation
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
handle.onPropChange(key, value, oldValue);
|
|
4383
|
-
}
|
|
4384
|
-
}
|
|
4385
|
-
},
|
|
4386
|
-
enumerable: true,
|
|
4387
|
-
configurable: true
|
|
4388
|
-
});
|
|
4389
|
-
});
|
|
4390
|
-
|
|
4391
|
-
return compiledProps;
|
|
4668
|
+
* @example
|
|
4669
|
+
* var el = bw.mount('#app', bw.makeCarousel({ items: slides }));
|
|
4670
|
+
* el.bw.goToSlide(2);
|
|
4671
|
+
* el.bw.next();
|
|
4672
|
+
*/
|
|
4673
|
+
bw.mount = function(target, taco, options) {
|
|
4674
|
+
var container = _is(target, 'string') ? bw.$(target)[0] : target;
|
|
4675
|
+
if (!container) {
|
|
4676
|
+
_cw('bw.mount: target not found');
|
|
4677
|
+
return null;
|
|
4678
|
+
}
|
|
4679
|
+
bw.cleanup(container);
|
|
4680
|
+
container.innerHTML = '';
|
|
4681
|
+
var el = bw.createDOM(taco, options || {});
|
|
4682
|
+
container.appendChild(el);
|
|
4683
|
+
return el;
|
|
4392
4684
|
};
|
|
4393
4685
|
|
|
4394
4686
|
/**
|
|
4395
|
-
*
|
|
4687
|
+
* Clean up a DOM element and all its children by calling unmount callbacks,
|
|
4688
|
+
* removing pub/sub subscriptions, and clearing state/render references.
|
|
4396
4689
|
*
|
|
4397
|
-
*
|
|
4398
|
-
*
|
|
4690
|
+
* Called automatically by `bw.DOM()` before re-rendering. Call manually when
|
|
4691
|
+
* removing elements to prevent memory leaks from orphaned callbacks.
|
|
4399
4692
|
*
|
|
4400
|
-
* @param {
|
|
4401
|
-
* @param {Object} [options] - Render options
|
|
4402
|
-
* @returns {Object} Component handle with element, props, state, update(), destroy()
|
|
4693
|
+
* @param {Element} element - DOM element to clean up
|
|
4403
4694
|
* @category DOM Generation
|
|
4695
|
+
* @see bw.DOM
|
|
4696
|
+
* @example
|
|
4697
|
+
* var el = document.querySelector('#my-widget');
|
|
4698
|
+
* bw.cleanup(el); // runs unmount hooks, clears _bw_state, _bw_render
|
|
4699
|
+
* el.remove(); // safe to remove from DOM now
|
|
4404
4700
|
*/
|
|
4405
|
-
bw.
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
//
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4701
|
+
bw.cleanup = function(element) {
|
|
4702
|
+
if (!bw._isBrowser || !element) return;
|
|
4703
|
+
|
|
4704
|
+
// Deregister UUID classes from node cache for non-lifecycle UUID elements
|
|
4705
|
+
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
4706
|
+
uuidEls.forEach(function(uel) {
|
|
4707
|
+
var m = uel.className && uel.className.match(_UUID_RE);
|
|
4708
|
+
if (m) delete bw._nodeMap[m[0]];
|
|
4709
|
+
});
|
|
4710
|
+
|
|
4711
|
+
// Find all lifecycle-managed elements (have bw_lc marker class)
|
|
4712
|
+
const elements = element.querySelectorAll('.' + _BW_LC);
|
|
4713
|
+
|
|
4714
|
+
elements.forEach(el => {
|
|
4715
|
+
var uuid = bw.getUUID(el);
|
|
4716
|
+
|
|
4717
|
+
if (uuid) {
|
|
4718
|
+
const callback = bw._unmountCallbacks.get(uuid);
|
|
4719
|
+
if (callback) {
|
|
4720
|
+
callback();
|
|
4721
|
+
bw._unmountCallbacks.delete(uuid);
|
|
4420
4722
|
}
|
|
4421
|
-
return this._compiledProps;
|
|
4422
|
-
},
|
|
4423
|
-
|
|
4424
|
-
/**
|
|
4425
|
-
* Query all matching elements within this component
|
|
4426
|
-
* @param {string} selector - CSS selector
|
|
4427
|
-
* @returns {NodeList} Matching elements
|
|
4428
|
-
*/
|
|
4429
|
-
$(selector) {
|
|
4430
|
-
return this.element.querySelectorAll(selector);
|
|
4431
|
-
},
|
|
4432
|
-
|
|
4433
|
-
/**
|
|
4434
|
-
* Query the first matching element within this component
|
|
4435
|
-
* @param {string} selector - CSS selector
|
|
4436
|
-
* @returns {Element|null} First matching element or null
|
|
4437
|
-
*/
|
|
4438
|
-
$first(selector) {
|
|
4439
|
-
return this.element.querySelector(selector);
|
|
4440
|
-
},
|
|
4441
|
-
|
|
4442
|
-
/**
|
|
4443
|
-
* Update component with new props and re-render in place
|
|
4444
|
-
* @param {Object} newProps - Properties to merge into current props
|
|
4445
|
-
* @returns {Object} this handle (for chaining)
|
|
4446
|
-
*/
|
|
4447
|
-
update(newProps) {
|
|
4448
|
-
// Update internal props
|
|
4449
|
-
Object.assign(this._props, newProps);
|
|
4450
|
-
|
|
4451
|
-
// Rebuild TACO with new props
|
|
4452
|
-
const newTaco = { ...this.taco, a: { ...this.taco.a, ...newProps } };
|
|
4453
|
-
const newElement = bw.createDOM(newTaco, options);
|
|
4454
|
-
|
|
4455
|
-
// Replace in DOM
|
|
4456
|
-
this.element.replaceWith(newElement);
|
|
4457
|
-
this.element = newElement;
|
|
4458
|
-
this.taco = newTaco;
|
|
4459
|
-
|
|
4460
|
-
return this;
|
|
4461
|
-
},
|
|
4462
|
-
|
|
4463
|
-
/**
|
|
4464
|
-
* Re-render the component from its current TACO, replacing the DOM element
|
|
4465
|
-
* @returns {Object} this handle (for chaining)
|
|
4466
|
-
*/
|
|
4467
|
-
render() {
|
|
4468
|
-
const newElement = bw.createDOM(this.taco, options);
|
|
4469
|
-
this.element.replaceWith(newElement);
|
|
4470
|
-
this.element = newElement;
|
|
4471
|
-
return this;
|
|
4472
|
-
},
|
|
4473
|
-
|
|
4474
|
-
/**
|
|
4475
|
-
* Called when a compiled prop value changes. Override to customize behavior.
|
|
4476
|
-
* Default implementation triggers a full re-render.
|
|
4477
|
-
* @param {string} key - Property name that changed
|
|
4478
|
-
* @param {*} newValue - New property value
|
|
4479
|
-
* @param {*} oldValue - Previous property value
|
|
4480
|
-
*/
|
|
4481
|
-
onPropChange(_key, _newValue, _oldValue) {
|
|
4482
|
-
// Auto re-render on prop change by default
|
|
4483
|
-
this.render();
|
|
4484
|
-
},
|
|
4485
|
-
|
|
4486
|
-
// State management
|
|
4487
|
-
get state() {
|
|
4488
|
-
return this._state;
|
|
4489
|
-
},
|
|
4490
|
-
|
|
4491
|
-
set state(newState) {
|
|
4492
|
-
this._state = newState;
|
|
4493
|
-
this.render();
|
|
4494
|
-
},
|
|
4495
|
-
|
|
4496
|
-
/**
|
|
4497
|
-
* Merge state updates and re-render the component
|
|
4498
|
-
* @param {Object} updates - State properties to merge
|
|
4499
|
-
* @returns {Object} this handle (for chaining)
|
|
4500
|
-
*/
|
|
4501
|
-
setState(updates) {
|
|
4502
|
-
Object.assign(this._state, updates);
|
|
4503
|
-
this.render();
|
|
4504
|
-
return this;
|
|
4505
|
-
},
|
|
4506
|
-
|
|
4507
|
-
/**
|
|
4508
|
-
* Register a child component under a name for later retrieval
|
|
4509
|
-
* @param {string} name - Child name key
|
|
4510
|
-
* @param {Object} component - Child component handle
|
|
4511
|
-
* @returns {Object} this handle (for chaining)
|
|
4512
|
-
*/
|
|
4513
|
-
addChild(name, component) {
|
|
4514
|
-
this._children[name] = component;
|
|
4515
|
-
return this;
|
|
4516
|
-
},
|
|
4517
|
-
|
|
4518
|
-
/**
|
|
4519
|
-
* Retrieve a registered child component by name
|
|
4520
|
-
* @param {string} name - Child name key
|
|
4521
|
-
* @returns {Object|undefined} Child component handle
|
|
4522
|
-
*/
|
|
4523
|
-
getChild(name) {
|
|
4524
|
-
return this._children[name];
|
|
4525
|
-
},
|
|
4526
|
-
|
|
4527
|
-
/**
|
|
4528
|
-
* Destroy this component and all registered children
|
|
4529
|
-
*
|
|
4530
|
-
* Calls destroy() recursively on children, runs bw.cleanup(),
|
|
4531
|
-
* removes the element from DOM, and clears all internal references.
|
|
4532
|
-
*/
|
|
4533
|
-
destroy() {
|
|
4534
|
-
// Destroy children first
|
|
4535
|
-
Object.values(this._children).forEach(child => {
|
|
4536
|
-
if (child && child.destroy) child.destroy();
|
|
4537
|
-
});
|
|
4538
|
-
|
|
4539
|
-
// Clean up this component
|
|
4540
|
-
bw.cleanup(this.element);
|
|
4541
|
-
this.element.remove();
|
|
4542
|
-
|
|
4543
|
-
// Clear references
|
|
4544
|
-
this._children = {};
|
|
4545
|
-
this._props = {};
|
|
4546
|
-
this._state = {};
|
|
4547
|
-
this._compiledProps = null;
|
|
4548
|
-
}
|
|
4549
|
-
};
|
|
4550
|
-
|
|
4551
|
-
// Store handle reference on element
|
|
4552
|
-
element._bwHandle = handle;
|
|
4553
|
-
|
|
4554
|
-
return handle;
|
|
4555
|
-
};
|
|
4556
|
-
|
|
4557
|
-
/**
|
|
4558
|
-
* Clean up a DOM element and all its children by calling unmount callbacks,
|
|
4559
|
-
* removing pub/sub subscriptions, and clearing state/render references.
|
|
4560
|
-
*
|
|
4561
|
-
* Called automatically by `bw.DOM()` before re-rendering. Call manually when
|
|
4562
|
-
* removing elements to prevent memory leaks from orphaned callbacks.
|
|
4563
|
-
*
|
|
4564
|
-
* @param {Element} element - DOM element to clean up
|
|
4565
|
-
* @category DOM Generation
|
|
4566
|
-
* @see bw.DOM
|
|
4567
|
-
* @example
|
|
4568
|
-
* var el = document.querySelector('#my-widget');
|
|
4569
|
-
* bw.cleanup(el); // runs unmount hooks, clears _bw_state, _bw_render
|
|
4570
|
-
* el.remove(); // safe to remove from DOM now
|
|
4571
|
-
*/
|
|
4572
|
-
bw.cleanup = function(element) {
|
|
4573
|
-
if (!bw._isBrowser || !element) return;
|
|
4574
|
-
|
|
4575
|
-
// Find all elements with data-bw_id
|
|
4576
|
-
const elements = element.querySelectorAll('[data-bw_id]');
|
|
4577
|
-
|
|
4578
|
-
elements.forEach(el => {
|
|
4579
|
-
const id = el.getAttribute('data-bw_id');
|
|
4580
|
-
const callback = bw._unmountCallbacks.get(id);
|
|
4581
4723
|
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
bw._unmountCallbacks.delete(id);
|
|
4724
|
+
// Deregister from node cache
|
|
4725
|
+
bw._deregisterNode(el, uuid);
|
|
4585
4726
|
}
|
|
4586
4727
|
|
|
4587
|
-
// Deregister from node cache
|
|
4588
|
-
bw._deregisterNode(el, id);
|
|
4589
|
-
|
|
4590
4728
|
// Clean up pub/sub subscriptions tied to this element
|
|
4591
4729
|
if (el._bw_subs) {
|
|
4592
4730
|
el._bw_subs.forEach(function(unsub) { unsub(); });
|
|
@@ -4600,16 +4738,18 @@ bw.cleanup = function(element) {
|
|
|
4600
4738
|
});
|
|
4601
4739
|
|
|
4602
4740
|
// Check element itself
|
|
4603
|
-
|
|
4604
|
-
if (
|
|
4605
|
-
|
|
4741
|
+
var selfUuid = bw.getUUID(element);
|
|
4742
|
+
if (selfUuid) {
|
|
4743
|
+
delete bw._nodeMap[selfUuid];
|
|
4744
|
+
|
|
4745
|
+
const callback = bw._unmountCallbacks.get(selfUuid);
|
|
4606
4746
|
if (callback) {
|
|
4607
4747
|
callback();
|
|
4608
|
-
bw._unmountCallbacks.delete(
|
|
4748
|
+
bw._unmountCallbacks.delete(selfUuid);
|
|
4609
4749
|
}
|
|
4610
4750
|
|
|
4611
4751
|
// Deregister from node cache
|
|
4612
|
-
bw._deregisterNode(element,
|
|
4752
|
+
bw._deregisterNode(element, selfUuid);
|
|
4613
4753
|
|
|
4614
4754
|
// Clean up pub/sub subscriptions tied to element itself
|
|
4615
4755
|
if (element._bw_subs) {
|
|
@@ -4620,11 +4760,11 @@ bw.cleanup = function(element) {
|
|
|
4620
4760
|
delete element._bw_render;
|
|
4621
4761
|
delete element._bw_refs;
|
|
4622
4762
|
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
element.
|
|
4627
|
-
delete element.
|
|
4763
|
+
} else {
|
|
4764
|
+
// No UUID on element itself, but still check for _bw_subs (from bw.sub())
|
|
4765
|
+
if (element._bw_subs) {
|
|
4766
|
+
element._bw_subs.forEach(function(unsub) { unsub(); });
|
|
4767
|
+
delete element._bw_subs;
|
|
4628
4768
|
}
|
|
4629
4769
|
}
|
|
4630
4770
|
};
|
|
@@ -4640,7 +4780,7 @@ bw.cleanup = function(element) {
|
|
|
4640
4780
|
* Calls `el._bw_render(el, state)` and emits `bw:statechange` so other
|
|
4641
4781
|
* components can react without tight coupling.
|
|
4642
4782
|
*
|
|
4643
|
-
* @param {string|Element} target - Element ID,
|
|
4783
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element
|
|
4644
4784
|
* @returns {Element|null} The element, or null if not found / no render function
|
|
4645
4785
|
* @category State Management
|
|
4646
4786
|
* @see bw.patch
|
|
@@ -4665,7 +4805,7 @@ bw.update = function(target) {
|
|
|
4665
4805
|
* Use `bw.patch()` for lightweight value updates (scores, labels, counters)
|
|
4666
4806
|
* and `bw.update()` for full structural re-renders.
|
|
4667
4807
|
*
|
|
4668
|
-
* @param {string|Element} id - Element ID,
|
|
4808
|
+
* @param {string|Element} id - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
4669
4809
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
4670
4810
|
* @param {string|Object} content - New text content, or TACO object to replace children
|
|
4671
4811
|
* @param {string} [attr] - If provided, sets this attribute instead of content
|
|
@@ -4740,7 +4880,7 @@ bw.patchAll = function(patches) {
|
|
|
4740
4880
|
* bubble by default so ancestor elements can listen. Use with `bw.on()` for
|
|
4741
4881
|
* DOM-scoped communication between components.
|
|
4742
4882
|
*
|
|
4743
|
-
* @param {string|Element} target - Element ID,
|
|
4883
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
4744
4884
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
4745
4885
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
4746
4886
|
* @param {*} [detail] - Data to pass with the event
|
|
@@ -4767,7 +4907,7 @@ bw.emit = function(target, eventName, detail) {
|
|
|
4767
4907
|
* is the first argument so you don't need to destructure `e.detail`.
|
|
4768
4908
|
* Events bubble, so you can listen on an ancestor element.
|
|
4769
4909
|
*
|
|
4770
|
-
* @param {string|Element} target - Element ID,
|
|
4910
|
+
* @param {string|Element} target - Element ID, bw_uuid_* class, CSS selector, or DOM element.
|
|
4771
4911
|
* Uses node cache for O(1) lookup; falls back to DOM query on cache miss.
|
|
4772
4912
|
* @param {string} eventName - Event name (will be prefixed with 'bw:')
|
|
4773
4913
|
* @param {Function} handler - Called with (detail, event)
|
|
@@ -4865,10 +5005,12 @@ bw.sub = function(topic, handler, el) {
|
|
|
4865
5005
|
if (el) {
|
|
4866
5006
|
if (!el._bw_subs) el._bw_subs = [];
|
|
4867
5007
|
el._bw_subs.push(unsub);
|
|
4868
|
-
// Ensure element has
|
|
4869
|
-
if (!
|
|
4870
|
-
|
|
4871
|
-
|
|
5008
|
+
// Ensure element has UUID + bw_lc so bw.cleanup() finds it
|
|
5009
|
+
if (!bw.getUUID(el)) {
|
|
5010
|
+
el.classList.add(bw.uuid('uuid'));
|
|
5011
|
+
}
|
|
5012
|
+
if (!el.classList.contains(_BW_LC)) {
|
|
5013
|
+
el.classList.add(_BW_LC);
|
|
4872
5014
|
}
|
|
4873
5015
|
}
|
|
4874
5016
|
|
|
@@ -5075,1102 +5217,61 @@ bw._resolveTemplate = function(str, state, compile) {
|
|
|
5075
5217
|
}
|
|
5076
5218
|
try {
|
|
5077
5219
|
val = bw._compiledExprs[b.expr](state);
|
|
5078
|
-
} catch (e) {
|
|
5079
|
-
if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
|
|
5080
|
-
val = '';
|
|
5081
|
-
}
|
|
5082
|
-
} else {
|
|
5083
|
-
// Tier 1: dot-path only
|
|
5084
|
-
val = bw._evaluatePath(state, b.expr);
|
|
5085
|
-
}
|
|
5086
|
-
result += (val == null) ? '' : String(val);
|
|
5087
|
-
lastEnd = b.end;
|
|
5088
|
-
}
|
|
5089
|
-
result += str.slice(lastEnd);
|
|
5090
|
-
return result;
|
|
5091
|
-
};
|
|
5092
|
-
|
|
5093
|
-
/**
|
|
5094
|
-
* Extract top-level state keys that an expression depends on.
|
|
5095
|
-
* @param {string} expr - Expression string
|
|
5096
|
-
* @param {string[]} stateKeys - Declared state keys
|
|
5097
|
-
* @returns {string[]} Matching dependency keys
|
|
5098
|
-
* @private
|
|
5099
|
-
*/
|
|
5100
|
-
bw._extractDeps = function(expr, stateKeys) {
|
|
5101
|
-
var deps = [];
|
|
5102
|
-
for (var i = 0; i < stateKeys.length; i++) {
|
|
5103
|
-
var key = stateKeys[i];
|
|
5104
|
-
// Match word boundary: key must be preceded by start/non-word and followed by non-word/end
|
|
5105
|
-
var re = new RegExp('(?:^|[^\\w$.])' + key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:[^\\w$]|$)');
|
|
5106
|
-
if (re.test(expr) || expr === key || expr.indexOf(key + '.') === 0) {
|
|
5107
|
-
deps.push(key);
|
|
5108
|
-
}
|
|
5109
|
-
}
|
|
5110
|
-
return deps;
|
|
5111
|
-
};
|
|
5112
|
-
|
|
5113
|
-
// ===================================================================================
|
|
5114
|
-
// Microtask Batching
|
|
5115
|
-
// ===================================================================================
|
|
5116
|
-
|
|
5117
|
-
bw._dirtyComponents = [];
|
|
5118
|
-
bw._flushScheduled = false;
|
|
5119
|
-
|
|
5120
|
-
/**
|
|
5121
|
-
* Schedule a microtask flush for dirty components.
|
|
5122
|
-
* @private
|
|
5123
|
-
*/
|
|
5124
|
-
bw._scheduleFlush = function() {
|
|
5125
|
-
if (bw._flushScheduled) return;
|
|
5126
|
-
bw._flushScheduled = true;
|
|
5127
|
-
if (typeof Promise !== 'undefined') {
|
|
5128
|
-
Promise.resolve().then(bw._doFlush);
|
|
5129
|
-
} else {
|
|
5130
|
-
setTimeout(bw._doFlush, 0);
|
|
5131
|
-
}
|
|
5132
|
-
};
|
|
5133
|
-
|
|
5134
|
-
/**
|
|
5135
|
-
* Flush all dirty components. Deduplicates by _bwId.
|
|
5136
|
-
* @private
|
|
5137
|
-
*/
|
|
5138
|
-
bw._doFlush = function() {
|
|
5139
|
-
bw._flushScheduled = false;
|
|
5140
|
-
var queue = bw._dirtyComponents.slice();
|
|
5141
|
-
bw._dirtyComponents = [];
|
|
5142
|
-
// Deduplicate by _bwId
|
|
5143
|
-
var seen = {};
|
|
5144
|
-
for (var i = 0; i < queue.length; i++) {
|
|
5145
|
-
var comp = queue[i];
|
|
5146
|
-
if (!seen[comp._bwId]) {
|
|
5147
|
-
seen[comp._bwId] = true;
|
|
5148
|
-
comp._flush();
|
|
5149
|
-
}
|
|
5150
|
-
}
|
|
5151
|
-
};
|
|
5152
|
-
|
|
5153
|
-
/**
|
|
5154
|
-
* Synchronous flush for testing and imperative code.
|
|
5155
|
-
* Forces immediate re-render of all dirty components.
|
|
5156
|
-
*
|
|
5157
|
-
* @category Component
|
|
5158
|
-
*/
|
|
5159
|
-
bw.flush = function() {
|
|
5160
|
-
bw._doFlush();
|
|
5161
|
-
};
|
|
5162
|
-
|
|
5163
|
-
// ===================================================================================
|
|
5164
|
-
// ComponentHandle — unified reactive component (Phase 1)
|
|
5165
|
-
// ===================================================================================
|
|
5166
|
-
|
|
5167
|
-
/**
|
|
5168
|
-
* ComponentHandle constructor.
|
|
5169
|
-
* Wraps a TACO definition with reactive state, lifecycle hooks,
|
|
5170
|
-
* template bindings, and named actions.
|
|
5171
|
-
*
|
|
5172
|
-
* @param {Object} taco - TACO definition {t, a, c, o}
|
|
5173
|
-
* @constructor
|
|
5174
|
-
* @private
|
|
5175
|
-
*/
|
|
5176
|
-
function ComponentHandle(taco) {
|
|
5177
|
-
this._bwComponent = true; // duck-type marker
|
|
5178
|
-
this._bwId = bw.uuid('comp');
|
|
5179
|
-
this.taco = taco;
|
|
5180
|
-
this.element = null;
|
|
5181
|
-
this.mounted = false;
|
|
5182
|
-
|
|
5183
|
-
var o = taco.o || {};
|
|
5184
|
-
// Copy initial state
|
|
5185
|
-
this._state = {};
|
|
5186
|
-
if (o.state) {
|
|
5187
|
-
for (var k in o.state) {
|
|
5188
|
-
if (_hop.call(o.state, k)) {
|
|
5189
|
-
this._state[k] = o.state[k];
|
|
5190
|
-
}
|
|
5191
|
-
}
|
|
5192
|
-
}
|
|
5193
|
-
// Copy actions
|
|
5194
|
-
this._actions = {};
|
|
5195
|
-
if (o.actions) {
|
|
5196
|
-
for (var k2 in o.actions) {
|
|
5197
|
-
if (_hop.call(o.actions, k2)) {
|
|
5198
|
-
this._actions[k2] = o.actions[k2];
|
|
5199
|
-
}
|
|
5200
|
-
}
|
|
5201
|
-
}
|
|
5202
|
-
// Promote o.methods to handle API (MFC/Qt pattern: component owns its methods)
|
|
5203
|
-
this._methods = {};
|
|
5204
|
-
if (o.methods) {
|
|
5205
|
-
var self = this;
|
|
5206
|
-
for (var k3 in o.methods) {
|
|
5207
|
-
if (_hop.call(o.methods, k3)) {
|
|
5208
|
-
this._methods[k3] = o.methods[k3];
|
|
5209
|
-
(function(methodName, methodFn) {
|
|
5210
|
-
self[methodName] = function() {
|
|
5211
|
-
var args = [self].concat(Array.prototype.slice.call(arguments));
|
|
5212
|
-
return methodFn.apply(null, args);
|
|
5213
|
-
};
|
|
5214
|
-
})(k3, o.methods[k3]);
|
|
5215
|
-
}
|
|
5216
|
-
}
|
|
5217
|
-
}
|
|
5218
|
-
// User tag for addressing via bw.message()
|
|
5219
|
-
this._userTag = null;
|
|
5220
|
-
// Lifecycle hooks
|
|
5221
|
-
this._hooks = {
|
|
5222
|
-
willMount: o.willMount || null,
|
|
5223
|
-
mounted: o.mounted || null,
|
|
5224
|
-
willUpdate: o.willUpdate || null,
|
|
5225
|
-
onUpdate: o.onUpdate || null,
|
|
5226
|
-
unmount: o.unmount || null,
|
|
5227
|
-
willDestroy: o.willDestroy || null
|
|
5228
|
-
};
|
|
5229
|
-
// Binding tracking
|
|
5230
|
-
this._bindings = [];
|
|
5231
|
-
this._dirtyKeys = {};
|
|
5232
|
-
this._scheduled = false;
|
|
5233
|
-
this._subs = [];
|
|
5234
|
-
this._eventListeners = [];
|
|
5235
|
-
this._registeredActions = [];
|
|
5236
|
-
this._prevValues = {};
|
|
5237
|
-
this._compile = !!o.compile;
|
|
5238
|
-
this._bw_refs = {};
|
|
5239
|
-
this._refCounter = 0;
|
|
5240
|
-
// Child component ownership (Bug #5)
|
|
5241
|
-
this._children = [];
|
|
5242
|
-
this._parent = null;
|
|
5243
|
-
// Factory metadata for BCCL rebuild (Bug #6)
|
|
5244
|
-
this._factory = taco._bwFactory || null;
|
|
5245
|
-
}
|
|
5246
|
-
|
|
5247
|
-
// Short alias for ComponentHandle.prototype (see alias block at top of file).
|
|
5248
|
-
// 28 method definitions × 25 chars = ~700B raw savings in minified output.
|
|
5249
|
-
var _chp = ComponentHandle.prototype;
|
|
5250
|
-
|
|
5251
|
-
// ── State Methods ──
|
|
5252
|
-
|
|
5253
|
-
/**
|
|
5254
|
-
* Get a state value. Dot-path supported: `get('user.name')`
|
|
5255
|
-
*/
|
|
5256
|
-
_chp.get = function(key) {
|
|
5257
|
-
return bw._evaluatePath(this._state, key);
|
|
5258
|
-
};
|
|
5259
|
-
|
|
5260
|
-
/**
|
|
5261
|
-
* Set a state value. Dot-path supported. Schedules re-render.
|
|
5262
|
-
* @param {string} key - State key (dot-path)
|
|
5263
|
-
* @param {*} value - New value
|
|
5264
|
-
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
5265
|
-
*/
|
|
5266
|
-
_chp.set = function(key, value, opts) {
|
|
5267
|
-
// Dot-path set
|
|
5268
|
-
var parts = key.split('.');
|
|
5269
|
-
var obj = this._state;
|
|
5270
|
-
for (var i = 0; i < parts.length - 1; i++) {
|
|
5271
|
-
if (!_is(obj[parts[i]], 'object')) {
|
|
5272
|
-
if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
|
|
5273
|
-
obj[parts[i]] = {};
|
|
5274
|
-
}
|
|
5275
|
-
obj = obj[parts[i]];
|
|
5276
|
-
}
|
|
5277
|
-
obj[parts[parts.length - 1]] = value;
|
|
5278
|
-
// Mark top-level key dirty
|
|
5279
|
-
this._dirtyKeys[parts[0]] = true;
|
|
5280
|
-
if (this.mounted) {
|
|
5281
|
-
if (opts && opts.sync) {
|
|
5282
|
-
this._flush();
|
|
5283
|
-
} else {
|
|
5284
|
-
this._scheduleDirty();
|
|
5285
|
-
}
|
|
5286
|
-
}
|
|
5287
|
-
};
|
|
5288
|
-
|
|
5289
|
-
/**
|
|
5290
|
-
* Get a shallow clone of the full state.
|
|
5291
|
-
*/
|
|
5292
|
-
_chp.getState = function() {
|
|
5293
|
-
var clone = {};
|
|
5294
|
-
for (var k in this._state) {
|
|
5295
|
-
if (_hop.call(this._state, k)) {
|
|
5296
|
-
clone[k] = this._state[k];
|
|
5297
|
-
}
|
|
5298
|
-
}
|
|
5299
|
-
return clone;
|
|
5300
|
-
};
|
|
5301
|
-
|
|
5302
|
-
/**
|
|
5303
|
-
* Merge multiple state keys. Schedules re-render.
|
|
5304
|
-
* @param {Object} updates - Key-value pairs to merge
|
|
5305
|
-
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
5306
|
-
*/
|
|
5307
|
-
_chp.setState = function(updates, opts) {
|
|
5308
|
-
for (var k in updates) {
|
|
5309
|
-
if (_hop.call(updates, k)) {
|
|
5310
|
-
this._state[k] = updates[k];
|
|
5311
|
-
this._dirtyKeys[k] = true;
|
|
5312
|
-
}
|
|
5313
|
-
}
|
|
5314
|
-
if (this.mounted) {
|
|
5315
|
-
if (opts && opts.sync) {
|
|
5316
|
-
this._flush();
|
|
5317
|
-
} else {
|
|
5318
|
-
this._scheduleDirty();
|
|
5319
|
-
}
|
|
5320
|
-
}
|
|
5321
|
-
};
|
|
5322
|
-
|
|
5323
|
-
/**
|
|
5324
|
-
* Push a value onto an array in state. Clones the array.
|
|
5325
|
-
*/
|
|
5326
|
-
_chp.push = function(key, val) {
|
|
5327
|
-
var arr = this.get(key);
|
|
5328
|
-
var newArr = _isA(arr) ? arr.slice() : [];
|
|
5329
|
-
newArr.push(val);
|
|
5330
|
-
this.set(key, newArr);
|
|
5331
|
-
};
|
|
5332
|
-
|
|
5333
|
-
/**
|
|
5334
|
-
* Splice an array in state. Clones the array.
|
|
5335
|
-
*/
|
|
5336
|
-
_chp.splice = function(key, start, deleteCount) {
|
|
5337
|
-
var arr = this.get(key);
|
|
5338
|
-
var newArr = _isA(arr) ? arr.slice() : [];
|
|
5339
|
-
var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
|
|
5340
|
-
Array.prototype.splice.apply(newArr, args);
|
|
5341
|
-
this.set(key, newArr);
|
|
5342
|
-
};
|
|
5343
|
-
|
|
5344
|
-
// ── Scheduling ──
|
|
5345
|
-
|
|
5346
|
-
_chp._scheduleDirty = function() {
|
|
5347
|
-
if (!this._scheduled) {
|
|
5348
|
-
this._scheduled = true;
|
|
5349
|
-
bw._dirtyComponents.push(this);
|
|
5350
|
-
bw._scheduleFlush();
|
|
5351
|
-
}
|
|
5352
|
-
};
|
|
5353
|
-
|
|
5354
|
-
// ── Binding Compilation ──
|
|
5355
|
-
|
|
5356
|
-
/**
|
|
5357
|
-
* Walk the TACO tree and extract ${expr} bindings.
|
|
5358
|
-
* Creates binding descriptors with refIds for targeted DOM updates.
|
|
5359
|
-
* @private
|
|
5360
|
-
*/
|
|
5361
|
-
_chp._compileBindings = function() {
|
|
5362
|
-
this._bindings = [];
|
|
5363
|
-
this._refCounter = 0;
|
|
5364
|
-
var stateKeys = _keys(this._state);
|
|
5365
|
-
var self = this;
|
|
5366
|
-
|
|
5367
|
-
function walkTaco(taco, path) {
|
|
5368
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5369
|
-
|
|
5370
|
-
// Check content for bindings
|
|
5371
|
-
if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
|
|
5372
|
-
var refId = 'bw_ref_' + self._refCounter++;
|
|
5373
|
-
var parsed = bw._parseBindings(taco.c);
|
|
5374
|
-
var deps = [];
|
|
5375
|
-
for (var j = 0; j < parsed.length; j++) {
|
|
5376
|
-
deps = deps.concat(bw._extractDeps(parsed[j].expr, stateKeys));
|
|
5377
|
-
}
|
|
5378
|
-
self._bindings.push({
|
|
5379
|
-
expr: taco.c,
|
|
5380
|
-
type: 'content',
|
|
5381
|
-
refId: refId,
|
|
5382
|
-
deps: deps,
|
|
5383
|
-
template: taco.c
|
|
5384
|
-
});
|
|
5385
|
-
// Inject data-bw_ref on the TACO for createDOM to pick up
|
|
5386
|
-
if (!taco.a) taco.a = {};
|
|
5387
|
-
taco.a['data-bw_ref'] = refId;
|
|
5388
|
-
}
|
|
5389
|
-
|
|
5390
|
-
// Check attributes for bindings
|
|
5391
|
-
if (taco.a) {
|
|
5392
|
-
for (var attrName in taco.a) {
|
|
5393
|
-
if (!_hop.call(taco.a, attrName)) continue;
|
|
5394
|
-
if (attrName === 'data-bw_ref') continue;
|
|
5395
|
-
var attrVal = taco.a[attrName];
|
|
5396
|
-
if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
|
|
5397
|
-
var refId2 = 'bw_ref_' + self._refCounter++;
|
|
5398
|
-
var parsed2 = bw._parseBindings(attrVal);
|
|
5399
|
-
var deps2 = [];
|
|
5400
|
-
for (var j2 = 0; j2 < parsed2.length; j2++) {
|
|
5401
|
-
deps2 = deps2.concat(bw._extractDeps(parsed2[j2].expr, stateKeys));
|
|
5402
|
-
}
|
|
5403
|
-
self._bindings.push({
|
|
5404
|
-
expr: attrVal,
|
|
5405
|
-
type: 'attribute',
|
|
5406
|
-
attrName: attrName,
|
|
5407
|
-
refId: refId2,
|
|
5408
|
-
deps: deps2,
|
|
5409
|
-
template: attrVal
|
|
5410
|
-
});
|
|
5411
|
-
if (!taco.a) taco.a = {};
|
|
5412
|
-
taco.a['data-bw_ref'] = taco.a['data-bw_ref'] || refId2;
|
|
5413
|
-
// If multiple attribute bindings on same element, store additional marker
|
|
5414
|
-
if (taco.a['data-bw_ref'] !== refId2) {
|
|
5415
|
-
taco.a['data-bw_ref_' + attrName] = refId2;
|
|
5416
|
-
}
|
|
5417
|
-
}
|
|
5418
|
-
}
|
|
5419
|
-
}
|
|
5420
|
-
|
|
5421
|
-
// Recurse into children
|
|
5422
|
-
if (_isA(taco.c)) {
|
|
5423
|
-
for (var i = 0; i < taco.c.length; i++) {
|
|
5424
|
-
// Wrap string children with ${expr} in a span so patches target the span, not the parent
|
|
5425
|
-
if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
|
|
5426
|
-
var mixedRefId = 'bw_ref_' + self._refCounter++;
|
|
5427
|
-
var mixedParsed = bw._parseBindings(taco.c[i]);
|
|
5428
|
-
var mixedDeps = [];
|
|
5429
|
-
for (var mi = 0; mi < mixedParsed.length; mi++) {
|
|
5430
|
-
mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
|
|
5431
|
-
}
|
|
5432
|
-
self._bindings.push({
|
|
5433
|
-
expr: taco.c[i],
|
|
5434
|
-
type: 'content',
|
|
5435
|
-
refId: mixedRefId,
|
|
5436
|
-
deps: mixedDeps,
|
|
5437
|
-
template: taco.c[i]
|
|
5438
|
-
});
|
|
5439
|
-
// Replace string with a span wrapper so textContent targets the span only
|
|
5440
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
|
|
5441
|
-
}
|
|
5442
|
-
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
5443
|
-
walkTaco(taco.c[i], path.concat(i));
|
|
5444
|
-
}
|
|
5445
|
-
// Handle bw.when/bw.each markers
|
|
5446
|
-
if (taco.c[i] && taco.c[i]._bwWhen) {
|
|
5447
|
-
var whenRefId = 'bw_ref_' + self._refCounter++;
|
|
5448
|
-
var whenDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
|
|
5449
|
-
self._bindings.push({
|
|
5450
|
-
expr: taco.c[i].expr,
|
|
5451
|
-
type: 'structural',
|
|
5452
|
-
subtype: 'when',
|
|
5453
|
-
refId: whenRefId,
|
|
5454
|
-
deps: whenDeps,
|
|
5455
|
-
branches: taco.c[i].branches,
|
|
5456
|
-
index: i,
|
|
5457
|
-
parentPath: path
|
|
5458
|
-
});
|
|
5459
|
-
taco.c[i]._refId = whenRefId;
|
|
5460
|
-
}
|
|
5461
|
-
if (taco.c[i] && taco.c[i]._bwEach) {
|
|
5462
|
-
var eachRefId = 'bw_ref_' + self._refCounter++;
|
|
5463
|
-
var eachDeps = bw._extractDeps(taco.c[i].expr.replace(/^\$\{|\}$/g, ''), stateKeys);
|
|
5464
|
-
self._bindings.push({
|
|
5465
|
-
expr: taco.c[i].expr,
|
|
5466
|
-
type: 'structural',
|
|
5467
|
-
subtype: 'each',
|
|
5468
|
-
refId: eachRefId,
|
|
5469
|
-
deps: eachDeps,
|
|
5470
|
-
factory: taco.c[i].factory,
|
|
5471
|
-
index: i,
|
|
5472
|
-
parentPath: path
|
|
5473
|
-
});
|
|
5474
|
-
taco.c[i]._refId = eachRefId;
|
|
5475
|
-
}
|
|
5476
|
-
}
|
|
5477
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5478
|
-
walkTaco(taco.c, path.concat(0));
|
|
5479
|
-
}
|
|
5480
|
-
|
|
5481
|
-
return taco;
|
|
5482
|
-
}
|
|
5483
|
-
|
|
5484
|
-
walkTaco(this.taco, []);
|
|
5485
|
-
};
|
|
5486
|
-
|
|
5487
|
-
// ── DOM Reference Collection ──
|
|
5488
|
-
|
|
5489
|
-
/**
|
|
5490
|
-
* Build ref map from the live DOM after createDOM.
|
|
5491
|
-
* @private
|
|
5492
|
-
*/
|
|
5493
|
-
_chp._collectRefs = function() {
|
|
5494
|
-
this._bw_refs = {};
|
|
5495
|
-
if (!this.element) return;
|
|
5496
|
-
var els = this.element.querySelectorAll('[data-bw_ref]');
|
|
5497
|
-
for (var i = 0; i < els.length; i++) {
|
|
5498
|
-
this._bw_refs[els[i].getAttribute('data-bw_ref')] = els[i];
|
|
5499
|
-
}
|
|
5500
|
-
// Also check root element
|
|
5501
|
-
var rootRef = this.element.getAttribute && this.element.getAttribute('data-bw_ref');
|
|
5502
|
-
if (rootRef) {
|
|
5503
|
-
this._bw_refs[rootRef] = this.element;
|
|
5504
|
-
}
|
|
5505
|
-
};
|
|
5506
|
-
|
|
5507
|
-
// ── Lifecycle ──
|
|
5508
|
-
|
|
5509
|
-
/**
|
|
5510
|
-
* Mount the component into a parent DOM element.
|
|
5511
|
-
* Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
|
|
5512
|
-
* @param {Element} parentEl - DOM element to mount into
|
|
5513
|
-
*/
|
|
5514
|
-
_chp.mount = function(parentEl) {
|
|
5515
|
-
// willMount hook
|
|
5516
|
-
if (this._hooks.willMount) this._hooks.willMount(this);
|
|
5517
|
-
|
|
5518
|
-
// Save original TACO for re-renders (structural changes clone from this)
|
|
5519
|
-
if (!this._originalTaco) {
|
|
5520
|
-
this._originalTaco = this.taco;
|
|
5521
|
-
}
|
|
5522
|
-
|
|
5523
|
-
// Deep-clone TACO so binding annotations don't mutate original.
|
|
5524
|
-
// Custom clone to preserve _bwWhen/_bwEach markers and their factory functions.
|
|
5525
|
-
this.taco = this._deepCloneTaco(this._originalTaco);
|
|
5526
|
-
|
|
5527
|
-
// Compile bindings (annotates TACO with data-bw_ref attributes)
|
|
5528
|
-
this._compileBindings();
|
|
5529
|
-
|
|
5530
|
-
// Prepare TACO: resolve initial binding values, evaluate when/each
|
|
5531
|
-
this._prepareTaco(this.taco);
|
|
5532
|
-
|
|
5533
|
-
// Register named actions in function registry
|
|
5534
|
-
var self = this;
|
|
5535
|
-
for (var actionName in this._actions) {
|
|
5536
|
-
if (_hop.call(this._actions, actionName)) {
|
|
5537
|
-
var registeredName = this._bwId + '_' + actionName;
|
|
5538
|
-
(function(aName) {
|
|
5539
|
-
bw.funcRegister(function(evt) {
|
|
5540
|
-
self._actions[aName](self, evt);
|
|
5541
|
-
}, registeredName);
|
|
5542
|
-
})(actionName);
|
|
5543
|
-
this._registeredActions.push(registeredName);
|
|
5544
|
-
}
|
|
5545
|
-
}
|
|
5546
|
-
|
|
5547
|
-
// Wire action names in onclick etc. to dispatch strings
|
|
5548
|
-
this._wireActions(this.taco);
|
|
5549
|
-
|
|
5550
|
-
// Create DOM (strip o before createDOM to prevent double lifecycle)
|
|
5551
|
-
var tacoForDOM = this._tacoForDOM(this.taco);
|
|
5552
|
-
this.element = bw.createDOM(tacoForDOM);
|
|
5553
|
-
this.element._bwComponentHandle = this;
|
|
5554
|
-
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
5555
|
-
|
|
5556
|
-
// Restore o.render from original TACO (stripped by _tacoForDOM)
|
|
5557
|
-
if (this.taco.o && this.taco.o.render) {
|
|
5558
|
-
this.element._bw_render = this.taco.o.render;
|
|
5559
|
-
}
|
|
5560
|
-
if (this._userTag) {
|
|
5561
|
-
this.element.classList.add(this._userTag);
|
|
5562
|
-
}
|
|
5563
|
-
|
|
5564
|
-
// Append to parent
|
|
5565
|
-
parentEl.appendChild(this.element);
|
|
5566
|
-
|
|
5567
|
-
// Collect refs from live DOM
|
|
5568
|
-
this._collectRefs();
|
|
5569
|
-
|
|
5570
|
-
// Resolve initial bindings and apply to DOM
|
|
5571
|
-
this._resolveAndApplyAll();
|
|
5572
|
-
|
|
5573
|
-
this.mounted = true;
|
|
5574
|
-
|
|
5575
|
-
// Scan for child ComponentHandles and link parent/child (Bug #5)
|
|
5576
|
-
var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
|
|
5577
|
-
for (var ci = 0; ci < childEls.length; ci++) {
|
|
5578
|
-
var ch = childEls[ci]._bwComponentHandle;
|
|
5579
|
-
if (ch && ch !== this && !ch._parent) {
|
|
5580
|
-
ch._parent = this;
|
|
5581
|
-
this._children.push(ch);
|
|
5582
|
-
}
|
|
5583
|
-
}
|
|
5584
|
-
|
|
5585
|
-
// mounted hook (backward compat: fn.length === 2 wraps (el, state))
|
|
5586
|
-
if (this._hooks.mounted) {
|
|
5587
|
-
if (this._hooks.mounted.length === 2) {
|
|
5588
|
-
this._hooks.mounted(this.element, this.getState());
|
|
5589
|
-
} else {
|
|
5590
|
-
this._hooks.mounted(this);
|
|
5591
|
-
}
|
|
5592
|
-
}
|
|
5593
|
-
|
|
5594
|
-
// Invoke o.render on initial mount (if present)
|
|
5595
|
-
if (this.element._bw_render) {
|
|
5596
|
-
this.element._bw_render(this.element, this._state);
|
|
5597
|
-
}
|
|
5598
|
-
};
|
|
5599
|
-
|
|
5600
|
-
/**
|
|
5601
|
-
* Prepare TACO for initial render: resolve when/each markers.
|
|
5602
|
-
* @private
|
|
5603
|
-
*/
|
|
5604
|
-
_chp._prepareTaco = function(taco) {
|
|
5605
|
-
if (!_is(taco, 'object')) return;
|
|
5606
|
-
|
|
5607
|
-
if (_isA(taco.c)) {
|
|
5608
|
-
for (var i = taco.c.length - 1; i >= 0; i--) {
|
|
5609
|
-
var child = taco.c[i];
|
|
5610
|
-
if (child && child._bwWhen) {
|
|
5611
|
-
var exprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
5612
|
-
var val;
|
|
5613
|
-
if (this._compile) {
|
|
5614
|
-
try {
|
|
5615
|
-
val = (new Function('state', 'with(state){return (' + exprStr + ');}'))(this._state);
|
|
5616
|
-
} catch(e) { val = false; }
|
|
5617
|
-
} else {
|
|
5618
|
-
val = bw._evaluatePath(this._state, exprStr);
|
|
5619
|
-
}
|
|
5620
|
-
var branch = val ? child.branches[0] : (child.branches[1] || null);
|
|
5621
|
-
if (branch) {
|
|
5622
|
-
// Wrap in a container so we can track it
|
|
5623
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_when': child._refId, style: 'display:contents' }, c: branch };
|
|
5624
|
-
} else {
|
|
5625
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_when': child._refId, style: 'display:contents' }, c: '' };
|
|
5626
|
-
}
|
|
5627
|
-
}
|
|
5628
|
-
if (child && child._bwEach) {
|
|
5629
|
-
var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
5630
|
-
var arr = bw._evaluatePath(this._state, eachExprStr);
|
|
5631
|
-
var items = [];
|
|
5632
|
-
if (_isA(arr)) {
|
|
5633
|
-
for (var j = 0; j < arr.length; j++) {
|
|
5634
|
-
items.push(child.factory(arr[j], j));
|
|
5635
|
-
}
|
|
5636
|
-
}
|
|
5637
|
-
taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
|
|
5638
|
-
}
|
|
5639
|
-
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
5640
|
-
this._prepareTaco(taco.c[i]);
|
|
5641
|
-
}
|
|
5642
|
-
}
|
|
5643
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5644
|
-
this._prepareTaco(taco.c);
|
|
5645
|
-
}
|
|
5646
|
-
};
|
|
5647
|
-
|
|
5648
|
-
/**
|
|
5649
|
-
* Wire action name strings (in onclick etc.) to dispatch function calls.
|
|
5650
|
-
* @private
|
|
5651
|
-
*/
|
|
5652
|
-
_chp._wireActions = function(taco) {
|
|
5653
|
-
if (!_is(taco, 'object') || !taco.t) return;
|
|
5654
|
-
if (taco.a) {
|
|
5655
|
-
for (var key in taco.a) {
|
|
5656
|
-
if (!_hop.call(taco.a, key)) continue;
|
|
5657
|
-
if (key.startsWith('on') && _is(taco.a[key], 'string')) {
|
|
5658
|
-
var actionName = taco.a[key];
|
|
5659
|
-
if (actionName in this._actions) {
|
|
5660
|
-
var registeredName = this._bwId + '_' + actionName;
|
|
5661
|
-
// Replace string with actual function for createDOM event binding
|
|
5662
|
-
(function(rName) {
|
|
5663
|
-
taco.a[key] = function(evt) {
|
|
5664
|
-
bw.funcGetById(rName)(evt);
|
|
5665
|
-
};
|
|
5666
|
-
})(registeredName);
|
|
5667
|
-
}
|
|
5668
|
-
}
|
|
5669
|
-
}
|
|
5670
|
-
}
|
|
5671
|
-
if (_isA(taco.c)) {
|
|
5672
|
-
for (var i = 0; i < taco.c.length; i++) {
|
|
5673
|
-
this._wireActions(taco.c[i]);
|
|
5674
|
-
}
|
|
5675
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5676
|
-
this._wireActions(taco.c);
|
|
5677
|
-
}
|
|
5678
|
-
};
|
|
5679
|
-
|
|
5680
|
-
/**
|
|
5681
|
-
* Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
|
|
5682
|
-
* @private
|
|
5683
|
-
*/
|
|
5684
|
-
_chp._deepCloneTaco = function(taco) {
|
|
5685
|
-
if (taco == null) return taco;
|
|
5686
|
-
// Preserve _bwWhen / _bwEach markers (contain functions)
|
|
5687
|
-
if (taco._bwWhen) {
|
|
5688
|
-
return { _bwWhen: true, expr: taco.expr, branches: [
|
|
5689
|
-
this._deepCloneTaco(taco.branches[0]),
|
|
5690
|
-
taco.branches[1] ? this._deepCloneTaco(taco.branches[1]) : null
|
|
5691
|
-
], _refId: taco._refId };
|
|
5692
|
-
}
|
|
5693
|
-
if (taco._bwEach) {
|
|
5694
|
-
return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
|
|
5695
|
-
}
|
|
5696
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5697
|
-
var result = { t: taco.t };
|
|
5698
|
-
if (taco.a) {
|
|
5699
|
-
result.a = {};
|
|
5700
|
-
for (var k in taco.a) {
|
|
5701
|
-
if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
|
|
5702
|
-
}
|
|
5703
|
-
}
|
|
5704
|
-
if (taco.c != null) {
|
|
5705
|
-
if (_isA(taco.c)) {
|
|
5706
|
-
result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
|
|
5707
|
-
} else if (_is(taco.c, 'object')) {
|
|
5708
|
-
result.c = this._deepCloneTaco(taco.c);
|
|
5709
|
-
} else {
|
|
5710
|
-
result.c = taco.c;
|
|
5711
|
-
}
|
|
5712
|
-
}
|
|
5713
|
-
if (taco.o) result.o = taco.o; // Keep o reference (not deep-cloned; hooks are functions)
|
|
5714
|
-
return result;
|
|
5715
|
-
};
|
|
5716
|
-
|
|
5717
|
-
/**
|
|
5718
|
-
* Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
|
|
5719
|
-
* @private
|
|
5720
|
-
*/
|
|
5721
|
-
_chp._tacoForDOM = function(taco) {
|
|
5722
|
-
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5723
|
-
var result = { t: taco.t };
|
|
5724
|
-
if (taco.a) result.a = taco.a;
|
|
5725
|
-
if (taco.c != null) {
|
|
5726
|
-
if (_isA(taco.c)) {
|
|
5727
|
-
result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
|
|
5728
|
-
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5729
|
-
result.c = this._tacoForDOM(taco.c);
|
|
5730
|
-
} else {
|
|
5731
|
-
result.c = taco.c;
|
|
5732
|
-
}
|
|
5733
|
-
}
|
|
5734
|
-
// Intentionally strip o (no mounted/unmount/state/render on sub-elements)
|
|
5735
|
-
if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
|
|
5736
|
-
_cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
|
|
5737
|
-
'>. Use onclick attribute or bw.component() for child interactivity.');
|
|
5738
|
-
}
|
|
5739
|
-
return result;
|
|
5740
|
-
};
|
|
5741
|
-
|
|
5742
|
-
/**
|
|
5743
|
-
* Unmount: remove from DOM, deactivate, preserve state for re-mount.
|
|
5744
|
-
*/
|
|
5745
|
-
_chp.unmount = function() {
|
|
5746
|
-
if (!this.mounted) return;
|
|
5747
|
-
|
|
5748
|
-
// unmount hook
|
|
5749
|
-
if (this._hooks.unmount) {
|
|
5750
|
-
this._hooks.unmount(this);
|
|
5751
|
-
}
|
|
5752
|
-
|
|
5753
|
-
// Remove DOM event listeners
|
|
5754
|
-
for (var i = 0; i < this._eventListeners.length; i++) {
|
|
5755
|
-
var l = this._eventListeners[i];
|
|
5756
|
-
if (this.element) {
|
|
5757
|
-
this.element.removeEventListener(l.event, l.handler);
|
|
5758
|
-
}
|
|
5759
|
-
}
|
|
5760
|
-
this._eventListeners = [];
|
|
5761
|
-
|
|
5762
|
-
// Unsubscribe pub/sub
|
|
5763
|
-
for (var j = 0; j < this._subs.length; j++) {
|
|
5764
|
-
this._subs[j]();
|
|
5765
|
-
}
|
|
5766
|
-
this._subs = [];
|
|
5767
|
-
|
|
5768
|
-
// Remove from DOM
|
|
5769
|
-
if (this.element && this.element.parentNode) {
|
|
5770
|
-
this.element.parentNode.removeChild(this.element);
|
|
5771
|
-
}
|
|
5772
|
-
|
|
5773
|
-
this.mounted = false;
|
|
5774
|
-
// State preserved — can re-mount
|
|
5775
|
-
};
|
|
5776
|
-
|
|
5777
|
-
/**
|
|
5778
|
-
* Destroy: unmount + clear state + unregister actions.
|
|
5779
|
-
*/
|
|
5780
|
-
_chp.destroy = function() {
|
|
5781
|
-
// willDestroy hook
|
|
5782
|
-
if (this._hooks.willDestroy) {
|
|
5783
|
-
this._hooks.willDestroy(this);
|
|
5784
|
-
}
|
|
5785
|
-
|
|
5786
|
-
// Cascade destroy to children depth-first (Bug #5)
|
|
5787
|
-
for (var ci = this._children.length - 1; ci >= 0; ci--) {
|
|
5788
|
-
this._children[ci].destroy();
|
|
5789
|
-
}
|
|
5790
|
-
this._children = [];
|
|
5791
|
-
if (this._parent) {
|
|
5792
|
-
var idx = this._parent._children.indexOf(this);
|
|
5793
|
-
if (idx >= 0) this._parent._children.splice(idx, 1);
|
|
5794
|
-
this._parent = null;
|
|
5795
|
-
}
|
|
5796
|
-
|
|
5797
|
-
this.unmount();
|
|
5798
|
-
|
|
5799
|
-
// Unregister actions from function registry
|
|
5800
|
-
for (var i = 0; i < this._registeredActions.length; i++) {
|
|
5801
|
-
bw.funcUnregister(this._registeredActions[i]);
|
|
5802
|
-
}
|
|
5803
|
-
this._registeredActions = [];
|
|
5804
|
-
|
|
5805
|
-
// Clear state
|
|
5806
|
-
this._state = {};
|
|
5807
|
-
this._bindings = [];
|
|
5808
|
-
this._bw_refs = {};
|
|
5809
|
-
this._prevValues = {};
|
|
5810
|
-
this._dirtyKeys = {};
|
|
5811
|
-
if (this.element) {
|
|
5812
|
-
delete this.element._bwComponentHandle;
|
|
5813
|
-
this.element = null;
|
|
5814
|
-
}
|
|
5815
|
-
};
|
|
5816
|
-
|
|
5817
|
-
// ── Flush & Binding Resolution ──
|
|
5818
|
-
|
|
5819
|
-
/**
|
|
5820
|
-
* Flush dirty state: resolve changed bindings and apply to DOM.
|
|
5821
|
-
* @private
|
|
5822
|
-
*/
|
|
5823
|
-
_chp._flush = function() {
|
|
5824
|
-
this._scheduled = false;
|
|
5825
|
-
var changedKeys = _keys(this._dirtyKeys);
|
|
5826
|
-
this._dirtyKeys = {};
|
|
5827
|
-
if (changedKeys.length === 0 || !this.mounted) return;
|
|
5828
|
-
|
|
5829
|
-
// Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
|
|
5830
|
-
// rebuild the TACO from the factory with merged state (Bug #6)
|
|
5831
|
-
if (this._factory) {
|
|
5832
|
-
var rebuildNeeded = false;
|
|
5833
|
-
for (var fi = 0; fi < changedKeys.length; fi++) {
|
|
5834
|
-
if (_hop.call(this._factory.props, changedKeys[fi])) {
|
|
5835
|
-
rebuildNeeded = true; break;
|
|
5836
|
-
}
|
|
5837
|
-
}
|
|
5838
|
-
if (rebuildNeeded) {
|
|
5839
|
-
var merged = {};
|
|
5840
|
-
for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
|
|
5841
|
-
for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
|
|
5842
|
-
this._factory.props = merged;
|
|
5843
|
-
var newTaco = bw.make(this._factory.type, merged);
|
|
5844
|
-
newTaco._bwFactory = this._factory;
|
|
5845
|
-
this.taco = newTaco;
|
|
5846
|
-
this._originalTaco = this._deepCloneTaco(newTaco);
|
|
5847
|
-
this._render();
|
|
5848
|
-
if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
|
|
5849
|
-
return;
|
|
5850
|
-
}
|
|
5851
|
-
}
|
|
5852
|
-
|
|
5853
|
-
// willUpdate hook
|
|
5854
|
-
if (this._hooks.willUpdate) {
|
|
5855
|
-
this._hooks.willUpdate(this, changedKeys);
|
|
5856
|
-
}
|
|
5857
|
-
|
|
5858
|
-
// Check if any structural bindings are affected
|
|
5859
|
-
var needsFullRender = false;
|
|
5860
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
5861
|
-
var b = this._bindings[i];
|
|
5862
|
-
if (b.type === 'structural') {
|
|
5863
|
-
for (var j = 0; j < b.deps.length; j++) {
|
|
5864
|
-
if (changedKeys.indexOf(b.deps[j]) >= 0) {
|
|
5865
|
-
needsFullRender = true;
|
|
5866
|
-
break;
|
|
5867
|
-
}
|
|
5868
|
-
}
|
|
5869
|
-
if (needsFullRender) break;
|
|
5870
|
-
}
|
|
5871
|
-
}
|
|
5872
|
-
|
|
5873
|
-
if (needsFullRender) {
|
|
5874
|
-
this._render();
|
|
5875
|
-
} else {
|
|
5876
|
-
var patches = this._resolveBindings(changedKeys);
|
|
5877
|
-
this._applyPatches(patches);
|
|
5878
|
-
}
|
|
5879
|
-
|
|
5880
|
-
// onUpdate hook
|
|
5881
|
-
if (this._hooks.onUpdate) {
|
|
5882
|
-
this._hooks.onUpdate(this, changedKeys);
|
|
5883
|
-
}
|
|
5884
|
-
};
|
|
5885
|
-
|
|
5886
|
-
/**
|
|
5887
|
-
* Resolve bindings whose deps intersect with changedKeys.
|
|
5888
|
-
* Returns list of patches to apply.
|
|
5889
|
-
* @private
|
|
5890
|
-
*/
|
|
5891
|
-
_chp._resolveBindings = function(changedKeys) {
|
|
5892
|
-
var patches = [];
|
|
5893
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
5894
|
-
var b = this._bindings[i];
|
|
5895
|
-
if (b.type === 'structural') continue;
|
|
5896
|
-
|
|
5897
|
-
// Check if any dep matches
|
|
5898
|
-
var affected = false;
|
|
5899
|
-
for (var j = 0; j < b.deps.length; j++) {
|
|
5900
|
-
if (changedKeys.indexOf(b.deps[j]) >= 0) {
|
|
5901
|
-
affected = true;
|
|
5902
|
-
break;
|
|
5903
|
-
}
|
|
5904
|
-
}
|
|
5905
|
-
if (!affected) continue;
|
|
5906
|
-
|
|
5907
|
-
// Evaluate
|
|
5908
|
-
var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
|
|
5909
|
-
var prevKey = b.refId + '_' + (b.attrName || 'content');
|
|
5910
|
-
if (this._prevValues[prevKey] !== newVal) {
|
|
5911
|
-
this._prevValues[prevKey] = newVal;
|
|
5912
|
-
patches.push({
|
|
5913
|
-
refId: b.refId,
|
|
5914
|
-
type: b.type,
|
|
5915
|
-
attrName: b.attrName,
|
|
5916
|
-
value: newVal
|
|
5917
|
-
});
|
|
5918
|
-
}
|
|
5919
|
-
}
|
|
5920
|
-
return patches;
|
|
5921
|
-
};
|
|
5922
|
-
|
|
5923
|
-
/**
|
|
5924
|
-
* Apply patches to DOM.
|
|
5925
|
-
* @private
|
|
5926
|
-
*/
|
|
5927
|
-
_chp._applyPatches = function(patches) {
|
|
5928
|
-
for (var i = 0; i < patches.length; i++) {
|
|
5929
|
-
var p = patches[i];
|
|
5930
|
-
var el = this._bw_refs[p.refId];
|
|
5931
|
-
if (!el) {
|
|
5932
|
-
if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
|
|
5933
|
-
continue;
|
|
5934
|
-
}
|
|
5935
|
-
if (p.type === 'content') {
|
|
5936
|
-
el.textContent = p.value;
|
|
5937
|
-
} else if (p.type === 'attribute') {
|
|
5938
|
-
if (p.attrName === 'class') {
|
|
5939
|
-
el.className = p.value;
|
|
5940
|
-
} else {
|
|
5941
|
-
el.setAttribute(p.attrName, p.value);
|
|
5942
|
-
}
|
|
5943
|
-
}
|
|
5944
|
-
}
|
|
5945
|
-
};
|
|
5946
|
-
|
|
5947
|
-
/**
|
|
5948
|
-
* Resolve all bindings and apply (used for initial render).
|
|
5949
|
-
* @private
|
|
5950
|
-
*/
|
|
5951
|
-
_chp._resolveAndApplyAll = function() {
|
|
5952
|
-
var patches = [];
|
|
5953
|
-
for (var i = 0; i < this._bindings.length; i++) {
|
|
5954
|
-
var b = this._bindings[i];
|
|
5955
|
-
if (b.type === 'structural') continue;
|
|
5956
|
-
|
|
5957
|
-
var newVal = bw._resolveTemplate(b.template, this._state, this._compile);
|
|
5958
|
-
var prevKey = b.refId + '_' + (b.attrName || 'content');
|
|
5959
|
-
this._prevValues[prevKey] = newVal;
|
|
5960
|
-
patches.push({
|
|
5961
|
-
refId: b.refId,
|
|
5962
|
-
type: b.type,
|
|
5963
|
-
attrName: b.attrName,
|
|
5964
|
-
value: newVal
|
|
5965
|
-
});
|
|
5966
|
-
}
|
|
5967
|
-
this._applyPatches(patches);
|
|
5968
|
-
};
|
|
5969
|
-
|
|
5970
|
-
/**
|
|
5971
|
-
* Full re-render for structural changes (when/each branch switches).
|
|
5972
|
-
* @private
|
|
5973
|
-
*/
|
|
5974
|
-
_chp._render = function() {
|
|
5975
|
-
if (!this.element || !this.element.parentNode) return;
|
|
5976
|
-
var parent = this.element.parentNode;
|
|
5977
|
-
var nextSibling = this.element.nextSibling;
|
|
5978
|
-
|
|
5979
|
-
// Remove old DOM
|
|
5980
|
-
parent.removeChild(this.element);
|
|
5981
|
-
|
|
5982
|
-
// Re-prepare TACO with current state (deep clone preserving functions)
|
|
5983
|
-
this.taco = this._deepCloneTaco(this._originalTaco || this.taco);
|
|
5984
|
-
|
|
5985
|
-
// Re-compile bindings and prepare
|
|
5986
|
-
this._compileBindings();
|
|
5987
|
-
this._prepareTaco(this.taco);
|
|
5988
|
-
this._wireActions(this.taco);
|
|
5989
|
-
|
|
5990
|
-
var tacoForDOM = this._tacoForDOM(this.taco);
|
|
5991
|
-
this.element = bw.createDOM(tacoForDOM);
|
|
5992
|
-
this.element._bwComponentHandle = this;
|
|
5993
|
-
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
5994
|
-
|
|
5995
|
-
// Re-insert at same position
|
|
5996
|
-
if (nextSibling) {
|
|
5997
|
-
parent.insertBefore(this.element, nextSibling);
|
|
5998
|
-
} else {
|
|
5999
|
-
parent.appendChild(this.element);
|
|
6000
|
-
}
|
|
6001
|
-
|
|
6002
|
-
// Re-collect refs and apply all bindings
|
|
6003
|
-
this._collectRefs();
|
|
6004
|
-
this._resolveAndApplyAll();
|
|
6005
|
-
};
|
|
6006
|
-
|
|
6007
|
-
// ── Event & Pub/Sub Methods ──
|
|
6008
|
-
|
|
6009
|
-
/**
|
|
6010
|
-
* Add a DOM event listener on the component's root element.
|
|
6011
|
-
* @param {string} event - Event name (e.g., 'click')
|
|
6012
|
-
* @param {Function} handler - Event handler
|
|
6013
|
-
*/
|
|
6014
|
-
_chp.on = function(event, handler) {
|
|
6015
|
-
if (this.element) {
|
|
6016
|
-
this.element.addEventListener(event, handler);
|
|
6017
|
-
}
|
|
6018
|
-
this._eventListeners.push({ event: event, handler: handler });
|
|
6019
|
-
};
|
|
6020
|
-
|
|
6021
|
-
/**
|
|
6022
|
-
* Remove a DOM event listener.
|
|
6023
|
-
* @param {string} event - Event name
|
|
6024
|
-
* @param {Function} handler - Handler to remove
|
|
6025
|
-
*/
|
|
6026
|
-
_chp.off = function(event, handler) {
|
|
6027
|
-
if (this.element) {
|
|
6028
|
-
this.element.removeEventListener(event, handler);
|
|
6029
|
-
}
|
|
6030
|
-
this._eventListeners = this._eventListeners.filter(function(l) {
|
|
6031
|
-
return !(l.event === event && l.handler === handler);
|
|
6032
|
-
});
|
|
6033
|
-
};
|
|
6034
|
-
|
|
6035
|
-
/**
|
|
6036
|
-
* Subscribe to a pub/sub topic. Lifecycle-tied: auto-unsubs on destroy.
|
|
6037
|
-
* @param {string} topic - Topic name
|
|
6038
|
-
* @param {Function} handler - Handler function
|
|
6039
|
-
* @returns {Function} Unsubscribe function
|
|
6040
|
-
*/
|
|
6041
|
-
_chp.sub = function(topic, handler) {
|
|
6042
|
-
var unsub = bw.sub(topic, handler);
|
|
6043
|
-
this._subs.push(unsub);
|
|
6044
|
-
return unsub;
|
|
6045
|
-
};
|
|
6046
|
-
|
|
6047
|
-
/**
|
|
6048
|
-
* Call a named action.
|
|
6049
|
-
* @param {string} name - Action name
|
|
6050
|
-
* @param {...*} args - Arguments passed after comp
|
|
6051
|
-
*/
|
|
6052
|
-
_chp.action = function(name) {
|
|
6053
|
-
var fn = this._actions[name];
|
|
6054
|
-
if (!fn) {
|
|
6055
|
-
_cw('ComponentHandle.action: unknown action "' + name + '"');
|
|
6056
|
-
return;
|
|
6057
|
-
}
|
|
6058
|
-
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
|
|
6059
|
-
return fn.apply(null, args);
|
|
6060
|
-
};
|
|
6061
|
-
|
|
6062
|
-
/**
|
|
6063
|
-
* querySelector within the component's DOM.
|
|
6064
|
-
* @param {string} sel - CSS selector
|
|
6065
|
-
* @returns {Element|null}
|
|
6066
|
-
*/
|
|
6067
|
-
_chp.select = function(sel) {
|
|
6068
|
-
return this.element ? this.element.querySelector(sel) : null;
|
|
6069
|
-
};
|
|
6070
|
-
|
|
6071
|
-
/**
|
|
6072
|
-
* querySelectorAll within the component's DOM.
|
|
6073
|
-
* @param {string} sel - CSS selector
|
|
6074
|
-
* @returns {Element[]}
|
|
6075
|
-
*/
|
|
6076
|
-
_chp.selectAll = function(sel) {
|
|
6077
|
-
if (!this.element) return [];
|
|
6078
|
-
return Array.prototype.slice.call(this.element.querySelectorAll(sel));
|
|
6079
|
-
};
|
|
6080
|
-
|
|
6081
|
-
/**
|
|
6082
|
-
* Tag this component with a user-defined ID for addressing via bw.message().
|
|
6083
|
-
* The tag is added as a CSS class on the root element (DOM IS the registry).
|
|
6084
|
-
* @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
|
|
6085
|
-
* @returns {ComponentHandle} this (for chaining)
|
|
6086
|
-
*/
|
|
6087
|
-
_chp.userTag = function(tag) {
|
|
6088
|
-
this._userTag = tag;
|
|
6089
|
-
if (this.element) {
|
|
6090
|
-
this.element.classList.add(tag);
|
|
5220
|
+
} catch (e) {
|
|
5221
|
+
if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
|
|
5222
|
+
val = '';
|
|
5223
|
+
}
|
|
5224
|
+
} else {
|
|
5225
|
+
// Tier 1: dot-path only
|
|
5226
|
+
val = bw._evaluatePath(state, b.expr);
|
|
5227
|
+
}
|
|
5228
|
+
result += (val == null) ? '' : String(val);
|
|
5229
|
+
lastEnd = b.end;
|
|
6091
5230
|
}
|
|
6092
|
-
|
|
5231
|
+
result += str.slice(lastEnd);
|
|
5232
|
+
return result;
|
|
6093
5233
|
};
|
|
6094
5234
|
|
|
6095
|
-
// Expose ComponentHandle on bw (for testing and advanced use)
|
|
6096
|
-
bw._ComponentHandle = ComponentHandle;
|
|
6097
|
-
|
|
6098
5235
|
// ===================================================================================
|
|
6099
|
-
//
|
|
5236
|
+
// Deprecation stubs for removed ComponentHandle APIs (v2.0.19)
|
|
6100
5237
|
// ===================================================================================
|
|
6101
5238
|
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
* @param {Object} tacoTrue - TACO to render when truthy
|
|
6109
|
-
* @param {Object} [tacoFalse] - TACO to render when falsy
|
|
6110
|
-
* @returns {Object} Marker object with _bwWhen flag
|
|
6111
|
-
* @category Component
|
|
6112
|
-
*/
|
|
6113
|
-
bw.when = function(expr, tacoTrue, tacoFalse) {
|
|
6114
|
-
return { _bwWhen: true, expr: expr, branches: [tacoTrue, tacoFalse || null] };
|
|
6115
|
-
};
|
|
5239
|
+
bw._extractDeps = undefined;
|
|
5240
|
+
bw._dirtyComponents = undefined;
|
|
5241
|
+
bw._flushScheduled = undefined;
|
|
5242
|
+
bw._scheduleFlush = undefined;
|
|
5243
|
+
bw._doFlush = undefined;
|
|
5244
|
+
bw._ComponentHandle = undefined;
|
|
6116
5245
|
|
|
6117
5246
|
/**
|
|
6118
|
-
*
|
|
6119
|
-
*
|
|
6120
|
-
*
|
|
6121
|
-
* @param {string} expr - Expression string like '${items}'
|
|
6122
|
-
* @param {Function} fn - Factory function(item, index) returning TACO
|
|
6123
|
-
* @returns {Object} Marker object with _bwEach flag
|
|
5247
|
+
* No-op flush (ComponentHandle removed in v2.0.19).
|
|
5248
|
+
* Kept as no-op for backward compatibility.
|
|
6124
5249
|
* @category Component
|
|
6125
5250
|
*/
|
|
6126
|
-
bw.
|
|
6127
|
-
return { _bwEach: true, expr: expr, factory: fn };
|
|
6128
|
-
};
|
|
5251
|
+
bw.flush = function() {};
|
|
6129
5252
|
|
|
6130
|
-
// ===================================================================================
|
|
6131
|
-
// bw.component() — Factory for ComponentHandle
|
|
6132
|
-
// ===================================================================================
|
|
6133
5253
|
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
* @param {Object} taco - TACO definition with {t, a, c, o}
|
|
6139
|
-
* @returns {ComponentHandle} Reactive component handle
|
|
6140
|
-
* @category Component
|
|
6141
|
-
* @see bw.DOM
|
|
6142
|
-
* @example
|
|
6143
|
-
* var counter = bw.component({
|
|
6144
|
-
* t: 'div', c: [{ t: 'h3', c: 'Count: ${count}' }],
|
|
6145
|
-
* o: { state: { count: 0 } }
|
|
6146
|
-
* });
|
|
6147
|
-
* bw.DOM('#app', counter);
|
|
6148
|
-
* counter.set('count', 42); // DOM auto-updates
|
|
6149
|
-
*/
|
|
6150
|
-
bw.component = function(taco) {
|
|
6151
|
-
return new ComponentHandle(taco);
|
|
6152
|
-
};
|
|
5254
|
+
bw.when = function() { throw new Error('bw.when() removed in v2.0.19. Use conditional logic in o.render instead.'); };
|
|
5255
|
+
bw.each = function() { throw new Error('bw.each() removed in v2.0.19. Use array mapping in o.render instead.'); };
|
|
5256
|
+
bw.component = function() { throw new Error('bw.component() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
5257
|
+
|
|
6153
5258
|
|
|
6154
5259
|
// ===================================================================================
|
|
6155
5260
|
// bw.message() — SendMessage() for the web
|
|
6156
5261
|
// ===================================================================================
|
|
6157
5262
|
|
|
6158
5263
|
/**
|
|
6159
|
-
* Dispatch a message to a component by UUID or
|
|
6160
|
-
* Finds the
|
|
6161
|
-
*
|
|
6162
|
-
* Win32 SendMessage(hwnd, msg, wParam, lParam).
|
|
5264
|
+
* Dispatch a message to a component by UUID, CSS class, or selector.
|
|
5265
|
+
* Finds the element, looks up el.bw, and calls the named method.
|
|
5266
|
+
* This is the bitwrench equivalent of Win32 SendMessage(hwnd, msg, wParam, lParam).
|
|
6163
5267
|
*
|
|
6164
|
-
* @param {string} target - Component UUID (
|
|
6165
|
-
* @param {string} action - Method name to call on
|
|
5268
|
+
* @param {string} target - Component UUID (bw_uuid_*), CSS class, or selector
|
|
5269
|
+
* @param {string} action - Method name to call on el.bw
|
|
6166
5270
|
* @param {*} data - Data to pass to the method
|
|
6167
5271
|
* @returns {boolean} True if message was dispatched successfully
|
|
6168
5272
|
* @category Component
|
|
6169
5273
|
* @example
|
|
6170
|
-
*
|
|
6171
|
-
* myDash.userTag('dashboard_prod');
|
|
6172
|
-
* // Dispatch locally
|
|
6173
|
-
* bw.message('dashboard_prod', 'addAlert', { severity: 'warning', text: 'CPU spike' });
|
|
5274
|
+
* bw.message('my_carousel', 'goToSlide', 2);
|
|
6174
5275
|
* // Or from SSE handler:
|
|
6175
5276
|
* es.onmessage = function(e) {
|
|
6176
5277
|
* var msg = JSON.parse(e.data);
|
|
@@ -6178,75 +5279,35 @@ bw.component = function(taco) {
|
|
|
6178
5279
|
* };
|
|
6179
5280
|
*/
|
|
6180
5281
|
bw.message = function(target, action, data) {
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
if (!el) {
|
|
6184
|
-
|
|
6185
|
-
}
|
|
6186
|
-
if (!el || !el._bwComponentHandle) return false;
|
|
6187
|
-
var comp = el._bwComponentHandle;
|
|
6188
|
-
if (!_is(comp[action], 'function')) {
|
|
6189
|
-
_cw('bw.message: unknown action "' + action + '" on component ' + target);
|
|
5282
|
+
var el = bw._el(target);
|
|
5283
|
+
if (!el) el = bw.$('.' + target)[0];
|
|
5284
|
+
if (!el || !el.bw || typeof el.bw[action] !== 'function') {
|
|
5285
|
+
_cw('bw.message: no handle method "' + action + '" on ' + target);
|
|
6190
5286
|
return false;
|
|
6191
5287
|
}
|
|
6192
|
-
|
|
5288
|
+
el.bw[action](data);
|
|
6193
5289
|
return true;
|
|
6194
5290
|
};
|
|
6195
5291
|
|
|
6196
5292
|
// ===================================================================================
|
|
6197
|
-
// bw.
|
|
5293
|
+
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
6198
5294
|
// ===================================================================================
|
|
6199
5295
|
|
|
6200
5296
|
/**
|
|
6201
5297
|
* Registry of named functions sent via register messages.
|
|
6202
|
-
* Populated by
|
|
6203
|
-
* Invoked by
|
|
5298
|
+
* Populated by bw.apply({ type: 'register', name, body }).
|
|
5299
|
+
* Invoked by bw.apply({ type: 'call', name, args }).
|
|
6204
5300
|
* @private
|
|
6205
5301
|
*/
|
|
6206
5302
|
bw._clientFunctions = {};
|
|
6207
5303
|
|
|
6208
5304
|
/**
|
|
6209
|
-
* Whether exec messages are allowed. Set by
|
|
5305
|
+
* Whether exec messages are allowed. Set by bwclient connect opts.allowExec.
|
|
6210
5306
|
* Default false — exec messages are rejected unless explicitly opted in.
|
|
6211
5307
|
* @private
|
|
6212
5308
|
*/
|
|
6213
5309
|
bw._allowExec = false;
|
|
6214
5310
|
|
|
6215
|
-
/**
|
|
6216
|
-
* Built-in client functions available via call() without registration.
|
|
6217
|
-
* @private
|
|
6218
|
-
*/
|
|
6219
|
-
bw._builtinClientFunctions = {
|
|
6220
|
-
scrollTo: function(selector) {
|
|
6221
|
-
var el = bw._el(selector);
|
|
6222
|
-
if (el) el.scrollTop = el.scrollHeight;
|
|
6223
|
-
},
|
|
6224
|
-
focus: function(selector) {
|
|
6225
|
-
var el = bw._el(selector);
|
|
6226
|
-
if (el && _is(el.focus, 'function')) el.focus();
|
|
6227
|
-
},
|
|
6228
|
-
download: function(filename, content, mimeType) {
|
|
6229
|
-
if (typeof document === 'undefined') return;
|
|
6230
|
-
var blob = new Blob([content], { type: mimeType || 'text/plain' });
|
|
6231
|
-
var a = document.createElement('a');
|
|
6232
|
-
a.href = URL.createObjectURL(blob);
|
|
6233
|
-
a.download = filename;
|
|
6234
|
-
a.click();
|
|
6235
|
-
URL.revokeObjectURL(a.href);
|
|
6236
|
-
},
|
|
6237
|
-
clipboard: function(text) {
|
|
6238
|
-
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
6239
|
-
navigator.clipboard.writeText(text);
|
|
6240
|
-
}
|
|
6241
|
-
},
|
|
6242
|
-
redirect: function(url) {
|
|
6243
|
-
if (typeof window !== 'undefined') window.location.href = url;
|
|
6244
|
-
},
|
|
6245
|
-
log: function() {
|
|
6246
|
-
console.log.apply(console, arguments);
|
|
6247
|
-
}
|
|
6248
|
-
};
|
|
6249
|
-
|
|
6250
5311
|
/**
|
|
6251
5312
|
* Parse a bwserve protocol message string, supporting both strict JSON
|
|
6252
5313
|
* and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
|
|
@@ -6261,9 +5322,9 @@ bw._builtinClientFunctions = {
|
|
|
6261
5322
|
* @param {string} str - JSON or r-prefixed relaxed JSON string
|
|
6262
5323
|
* @returns {Object} Parsed message object
|
|
6263
5324
|
* @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
|
|
6264
|
-
* @category
|
|
5325
|
+
* @category Core
|
|
6265
5326
|
*/
|
|
6266
|
-
bw.
|
|
5327
|
+
bw.parseJSONFlex = function(str) {
|
|
6267
5328
|
str = (str || '').trim();
|
|
6268
5329
|
if (str.charAt(0) !== 'r') return JSON.parse(str);
|
|
6269
5330
|
str = str.slice(1);
|
|
@@ -6348,10 +5409,10 @@ bw.clientParse = function(str) {
|
|
|
6348
5409
|
* append — target.appendChild(bw.createDOM(node))
|
|
6349
5410
|
* remove — bw.cleanup(target); target.remove()
|
|
6350
5411
|
* patch — bw.patch(target, content, attr)
|
|
6351
|
-
* batch — iterate ops, call
|
|
5412
|
+
* batch — iterate ops, call bw.apply for each
|
|
6352
5413
|
* message — bw.message(target, action, data)
|
|
6353
5414
|
* register — store a named function for later call()
|
|
6354
|
-
* call — invoke a registered
|
|
5415
|
+
* call — invoke a registered function
|
|
6355
5416
|
* exec — execute arbitrary JS (requires allowExec)
|
|
6356
5417
|
*
|
|
6357
5418
|
* Target resolution:
|
|
@@ -6360,9 +5421,9 @@ bw.clientParse = function(str) {
|
|
|
6360
5421
|
*
|
|
6361
5422
|
* @param {Object} msg - Protocol message
|
|
6362
5423
|
* @returns {boolean} true if the message was applied successfully
|
|
6363
|
-
* @category
|
|
5424
|
+
* @category Core
|
|
6364
5425
|
*/
|
|
6365
|
-
bw.
|
|
5426
|
+
bw.apply = function(msg) {
|
|
6366
5427
|
if (!msg || !msg.type) return false;
|
|
6367
5428
|
|
|
6368
5429
|
var type = msg.type;
|
|
@@ -6396,7 +5457,7 @@ bw.clientApply = function(msg) {
|
|
|
6396
5457
|
if (!_isA(msg.ops)) return false;
|
|
6397
5458
|
var allOk = true;
|
|
6398
5459
|
msg.ops.forEach(function(op) {
|
|
6399
|
-
if (!bw.
|
|
5460
|
+
if (!bw.apply(op)) allOk = false;
|
|
6400
5461
|
});
|
|
6401
5462
|
return allOk;
|
|
6402
5463
|
|
|
@@ -6415,7 +5476,7 @@ bw.clientApply = function(msg) {
|
|
|
6415
5476
|
|
|
6416
5477
|
} else if (type === 'call') {
|
|
6417
5478
|
if (!msg.name) return false;
|
|
6418
|
-
var fn = bw._clientFunctions[msg.name]
|
|
5479
|
+
var fn = bw._clientFunctions[msg.name];
|
|
6419
5480
|
if (!_is(fn, 'function')) return false;
|
|
6420
5481
|
try {
|
|
6421
5482
|
var args = _isA(msg.args) ? msg.args : [];
|
|
@@ -6444,271 +5505,35 @@ bw.clientApply = function(msg) {
|
|
|
6444
5505
|
return false;
|
|
6445
5506
|
};
|
|
6446
5507
|
|
|
6447
|
-
/**
|
|
6448
|
-
* Connect to a bwserve SSE endpoint and apply protocol messages automatically.
|
|
6449
|
-
*
|
|
6450
|
-
* Returns a connection object with sendAction(), on(), and close() methods.
|
|
6451
|
-
*
|
|
6452
|
-
* @param {string} url - SSE endpoint URL (e.g., '/__bw/events/client-1')
|
|
6453
|
-
* @param {Object} [opts] - Connection options
|
|
6454
|
-
* @param {string} [opts.transport='sse'] - Transport type: 'sse' (default) or 'poll'
|
|
6455
|
-
* @param {number} [opts.interval=2000] - Poll interval in ms (only for 'poll' transport)
|
|
6456
|
-
* @param {string} [opts.actionUrl] - POST endpoint for actions (default: derived from url)
|
|
6457
|
-
* @param {boolean} [opts.reconnect=true] - Auto-reconnect on disconnect
|
|
6458
|
-
* @param {boolean} [opts.allowExec=false] - Enable exec message type (arbitrary JS execution)
|
|
6459
|
-
* @param {Function} [opts.onStatus] - Status callback: 'connecting'|'connected'|'disconnected'
|
|
6460
|
-
* @param {Function} [opts.onMessage] - Raw message callback (before clientApply)
|
|
6461
|
-
* @returns {Object} Connection object { sendAction, on, close, status }
|
|
6462
|
-
* @category Server
|
|
6463
|
-
*/
|
|
6464
|
-
bw.clientConnect = function(url, opts) {
|
|
6465
|
-
opts = opts || {};
|
|
6466
|
-
var transport = opts.transport || 'sse';
|
|
6467
|
-
var actionUrl = opts.actionUrl || url.replace(/\/events\//, '/action/');
|
|
6468
|
-
var reconnect = opts.reconnect !== false;
|
|
6469
|
-
var onStatus = opts.onStatus || function() {};
|
|
6470
|
-
var onMessage = opts.onMessage || null;
|
|
6471
|
-
var handlers = {};
|
|
6472
|
-
// Set the global allowExec flag from connection options
|
|
6473
|
-
bw._allowExec = !!opts.allowExec;
|
|
6474
|
-
var conn = {
|
|
6475
|
-
status: 'connecting',
|
|
6476
|
-
_es: null,
|
|
6477
|
-
_pollTimer: null
|
|
6478
|
-
};
|
|
6479
|
-
|
|
6480
|
-
function setStatus(s) {
|
|
6481
|
-
conn.status = s;
|
|
6482
|
-
onStatus(s);
|
|
6483
|
-
}
|
|
6484
|
-
|
|
6485
|
-
function handleMessage(data) {
|
|
6486
|
-
try {
|
|
6487
|
-
var msg = _is(data, 'string') ? bw.clientParse(data) : data;
|
|
6488
|
-
if (onMessage) onMessage(msg);
|
|
6489
|
-
if (handlers.message) handlers.message(msg);
|
|
6490
|
-
bw.clientApply(msg);
|
|
6491
|
-
} catch (e) {
|
|
6492
|
-
if (handlers.error) handlers.error(e);
|
|
6493
|
-
}
|
|
6494
|
-
}
|
|
6495
|
-
|
|
6496
|
-
if (transport === 'sse' && typeof EventSource !== 'undefined') {
|
|
6497
|
-
setStatus('connecting');
|
|
6498
|
-
var es = new EventSource(url);
|
|
6499
|
-
conn._es = es;
|
|
6500
|
-
|
|
6501
|
-
es.onopen = function() {
|
|
6502
|
-
setStatus('connected');
|
|
6503
|
-
if (handlers.open) handlers.open();
|
|
6504
|
-
};
|
|
6505
|
-
|
|
6506
|
-
es.onmessage = function(e) {
|
|
6507
|
-
handleMessage(e.data);
|
|
6508
|
-
};
|
|
6509
|
-
|
|
6510
|
-
es.onerror = function() {
|
|
6511
|
-
if (conn.status === 'connected') {
|
|
6512
|
-
setStatus('disconnected');
|
|
6513
|
-
}
|
|
6514
|
-
if (handlers.error) handlers.error(new Error('SSE connection error'));
|
|
6515
|
-
if (!reconnect) {
|
|
6516
|
-
es.close();
|
|
6517
|
-
}
|
|
6518
|
-
// EventSource auto-reconnects by default when reconnect=true
|
|
6519
|
-
};
|
|
6520
|
-
} else if (transport === 'poll') {
|
|
6521
|
-
var interval = opts.interval || 2000;
|
|
6522
|
-
setStatus('connected');
|
|
6523
|
-
conn._pollTimer = setInterval(function() {
|
|
6524
|
-
fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
|
|
6525
|
-
if (_isA(msgs)) {
|
|
6526
|
-
msgs.forEach(handleMessage);
|
|
6527
|
-
} else if (msgs && msgs.type) {
|
|
6528
|
-
handleMessage(msgs);
|
|
6529
|
-
}
|
|
6530
|
-
}).catch(function(e) {
|
|
6531
|
-
if (handlers.error) handlers.error(e);
|
|
6532
|
-
});
|
|
6533
|
-
}, interval);
|
|
6534
|
-
}
|
|
6535
|
-
|
|
6536
|
-
/**
|
|
6537
|
-
* Send an action to the server via POST.
|
|
6538
|
-
* @param {string} action - Action name
|
|
6539
|
-
* @param {Object} [data] - Action payload
|
|
6540
|
-
*/
|
|
6541
|
-
conn.sendAction = function(action, data) {
|
|
6542
|
-
var body = JSON.stringify({ type: 'action', action: action, data: data || {} });
|
|
6543
|
-
fetch(actionUrl, {
|
|
6544
|
-
method: 'POST',
|
|
6545
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6546
|
-
body: body
|
|
6547
|
-
}).catch(function(e) {
|
|
6548
|
-
if (handlers.error) handlers.error(e);
|
|
6549
|
-
});
|
|
6550
|
-
};
|
|
6551
|
-
|
|
6552
|
-
/**
|
|
6553
|
-
* Register an event handler.
|
|
6554
|
-
* @param {string} event - 'open'|'message'|'error'|'close'
|
|
6555
|
-
* @param {Function} handler
|
|
6556
|
-
*/
|
|
6557
|
-
conn.on = function(event, handler) {
|
|
6558
|
-
handlers[event] = handler;
|
|
6559
|
-
return conn;
|
|
6560
|
-
};
|
|
6561
|
-
|
|
6562
|
-
/**
|
|
6563
|
-
* Close the connection.
|
|
6564
|
-
*/
|
|
6565
|
-
conn.close = function() {
|
|
6566
|
-
if (conn._es) {
|
|
6567
|
-
conn._es.close();
|
|
6568
|
-
conn._es = null;
|
|
6569
|
-
}
|
|
6570
|
-
if (conn._pollTimer) {
|
|
6571
|
-
clearInterval(conn._pollTimer);
|
|
6572
|
-
conn._pollTimer = null;
|
|
6573
|
-
}
|
|
6574
|
-
setStatus('disconnected');
|
|
6575
|
-
if (handlers.close) handlers.close();
|
|
6576
|
-
};
|
|
6577
|
-
|
|
6578
|
-
return conn;
|
|
6579
|
-
};
|
|
6580
5508
|
|
|
6581
5509
|
// ===================================================================================
|
|
6582
5510
|
// bw.inspect() — Debug utility
|
|
6583
5511
|
// ===================================================================================
|
|
6584
5512
|
|
|
6585
5513
|
/**
|
|
6586
|
-
* Inspect a
|
|
6587
|
-
* Works with DOM elements
|
|
6588
|
-
* Returns the ComponentHandle for console chaining.
|
|
5514
|
+
* Inspect a DOM element's bitwrench state, handle methods, and metadata.
|
|
5515
|
+
* Works with DOM elements or CSS selectors.
|
|
6589
5516
|
*
|
|
6590
|
-
* @param {string|Element
|
|
6591
|
-
* @returns {
|
|
5517
|
+
* @param {string|Element} target - Selector or DOM element
|
|
5518
|
+
* @returns {Element|null} The element, or null if not found
|
|
6592
5519
|
* @category Component
|
|
6593
5520
|
* @example
|
|
6594
|
-
*
|
|
5521
|
+
* bw.inspect('#my-carousel');
|
|
6595
5522
|
* bw.inspect($0);
|
|
6596
|
-
* // Or by selector:
|
|
6597
|
-
* var h = bw.inspect('#my-dashboard');
|
|
6598
|
-
* h.set('count', 99); // chain from returned handle
|
|
6599
5523
|
*/
|
|
6600
5524
|
bw.inspect = function(target) {
|
|
6601
|
-
var el = target;
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
el = bw.$(target)[0];
|
|
6609
|
-
}
|
|
6610
|
-
if (!el) {
|
|
6611
|
-
_cw('bw.inspect: element not found');
|
|
6612
|
-
return null;
|
|
6613
|
-
}
|
|
6614
|
-
comp = el._bwComponentHandle;
|
|
6615
|
-
}
|
|
6616
|
-
if (!comp) {
|
|
6617
|
-
_cl('bw.inspect: no ComponentHandle on this element');
|
|
6618
|
-
_cl(' Tag:', el.tagName);
|
|
6619
|
-
_cl(' Classes:', el.className);
|
|
6620
|
-
_cl(' _bw_state:', el._bw_state || '(none)');
|
|
6621
|
-
return null;
|
|
6622
|
-
}
|
|
6623
|
-
var deps = comp._bindings.reduce(function(s, b) {
|
|
6624
|
-
return s.concat(b.deps || []);
|
|
6625
|
-
}, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
|
|
6626
|
-
console.group('Component: ' + comp._bwId);
|
|
6627
|
-
_cl('State:', comp._state);
|
|
6628
|
-
_cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
|
|
6629
|
-
_cl('Methods:', _keys(comp._methods));
|
|
6630
|
-
_cl('Actions:', _keys(comp._actions));
|
|
6631
|
-
_cl('User tag:', comp._userTag || '(none)');
|
|
6632
|
-
_cl('Mounted:', comp.mounted);
|
|
6633
|
-
_cl('Element:', comp.element);
|
|
5525
|
+
var el = _is(target, 'string') ? bw.$(target)[0] : target;
|
|
5526
|
+
if (!el) { _cw('bw.inspect: element not found'); return null; }
|
|
5527
|
+
console.group('Element: ' + (bw.getUUID(el) || el.id || el.tagName));
|
|
5528
|
+
_cl('State:', el._bw_state || '(none)');
|
|
5529
|
+
_cl('Handle:', el.bw ? _keys(el.bw) : '(none)');
|
|
5530
|
+
_cl('Classes:', el.className);
|
|
5531
|
+
_cl('Refs:', el._bw_refs || '(none)');
|
|
6634
5532
|
console.groupEnd();
|
|
6635
|
-
return
|
|
5533
|
+
return el;
|
|
6636
5534
|
};
|
|
6637
5535
|
|
|
6638
|
-
|
|
6639
|
-
// bw.compile() — Pre-compile TACO into optimized factory
|
|
6640
|
-
// ===================================================================================
|
|
6641
|
-
|
|
6642
|
-
/**
|
|
6643
|
-
* Pre-compile a TACO definition into a factory function.
|
|
6644
|
-
* The factory produces ComponentHandles with pre-compiled binding evaluators.
|
|
6645
|
-
*
|
|
6646
|
-
* Phase 1: validates API surface. Template cloning optimization deferred.
|
|
6647
|
-
*
|
|
6648
|
-
* @param {Object} taco - TACO definition
|
|
6649
|
-
* @returns {Function} Factory function(initialState?) → ComponentHandle
|
|
6650
|
-
* @category Component
|
|
6651
|
-
*/
|
|
6652
|
-
bw.compile = function(taco) {
|
|
6653
|
-
// Pre-extract all binding expressions
|
|
6654
|
-
var precompiled = [];
|
|
6655
|
-
function walkExpressions(node) {
|
|
6656
|
-
if (!_is(node, 'object')) return;
|
|
6657
|
-
if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
|
|
6658
|
-
var parsed = bw._parseBindings(node.c);
|
|
6659
|
-
for (var i = 0; i < parsed.length; i++) {
|
|
6660
|
-
try {
|
|
6661
|
-
precompiled.push({
|
|
6662
|
-
expr: parsed[i].expr,
|
|
6663
|
-
fn: new Function('state', 'with(state){return (' + parsed[i].expr + ');}')
|
|
6664
|
-
});
|
|
6665
|
-
} catch(e) {
|
|
6666
|
-
precompiled.push({ expr: parsed[i].expr, fn: function() { return ''; } });
|
|
6667
|
-
}
|
|
6668
|
-
}
|
|
6669
|
-
}
|
|
6670
|
-
if (node.a) {
|
|
6671
|
-
for (var key in node.a) {
|
|
6672
|
-
if (_hop.call(node.a, key)) {
|
|
6673
|
-
var v = node.a[key];
|
|
6674
|
-
if (_is(v, 'string') && v.indexOf('${') >= 0) {
|
|
6675
|
-
var parsed2 = bw._parseBindings(v);
|
|
6676
|
-
for (var j = 0; j < parsed2.length; j++) {
|
|
6677
|
-
try {
|
|
6678
|
-
precompiled.push({
|
|
6679
|
-
expr: parsed2[j].expr,
|
|
6680
|
-
fn: new Function('state', 'with(state){return (' + parsed2[j].expr + ');}')
|
|
6681
|
-
});
|
|
6682
|
-
} catch(e2) {
|
|
6683
|
-
precompiled.push({ expr: parsed2[j].expr, fn: function() { return ''; } });
|
|
6684
|
-
}
|
|
6685
|
-
}
|
|
6686
|
-
}
|
|
6687
|
-
}
|
|
6688
|
-
}
|
|
6689
|
-
}
|
|
6690
|
-
if (_isA(node.c)) {
|
|
6691
|
-
for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
|
|
6692
|
-
} else if (_is(node.c, 'object') && node.c.t) {
|
|
6693
|
-
walkExpressions(node.c);
|
|
6694
|
-
}
|
|
6695
|
-
}
|
|
6696
|
-
walkExpressions(taco);
|
|
6697
|
-
|
|
6698
|
-
return function(initialState) {
|
|
6699
|
-
var handle = new ComponentHandle(taco);
|
|
6700
|
-
handle._compile = true;
|
|
6701
|
-
handle._precompiledBindings = precompiled;
|
|
6702
|
-
if (initialState) {
|
|
6703
|
-
for (var k in initialState) {
|
|
6704
|
-
if (_hop.call(initialState, k)) {
|
|
6705
|
-
handle._state[k] = initialState[k];
|
|
6706
|
-
}
|
|
6707
|
-
}
|
|
6708
|
-
}
|
|
6709
|
-
return handle;
|
|
6710
|
-
};
|
|
6711
|
-
};
|
|
5536
|
+
bw.compile = function() { throw new Error('bw.compile() removed in v2.0.19. Use o.handle/o.slots on TACO options instead.'); };
|
|
6712
5537
|
|
|
6713
5538
|
/**
|
|
6714
5539
|
* Generate CSS from JavaScript objects.
|
|
@@ -6785,7 +5610,7 @@ bw.css = function(rules, options = {}) {
|
|
|
6785
5610
|
* @returns {Element} The style element
|
|
6786
5611
|
* @category CSS & Styling
|
|
6787
5612
|
* @see bw.css
|
|
6788
|
-
* @see bw.
|
|
5613
|
+
* @see bw.loadStyles
|
|
6789
5614
|
* @example
|
|
6790
5615
|
* bw.injectCSS('.my-class { color: red; }');
|
|
6791
5616
|
* bw.injectCSS({ '.card': { padding: '1rem' } }, { id: 'card-styles' });
|
|
@@ -6830,9 +5655,8 @@ bw.injectCSS = function(css, options = {}) {
|
|
|
6830
5655
|
* @param {...Object} styles - Style objects to merge (left-to-right)
|
|
6831
5656
|
* @returns {Object} Merged style object
|
|
6832
5657
|
* @category CSS & Styling
|
|
6833
|
-
* @see bw.u
|
|
6834
5658
|
* @example
|
|
6835
|
-
* var style = bw.s(
|
|
5659
|
+
* var style = bw.s({ display: 'flex' }, { gap: '1rem' }, { color: 'red' });
|
|
6836
5660
|
* // => { display: 'flex', gap: '1rem', color: 'red' }
|
|
6837
5661
|
*/
|
|
6838
5662
|
bw.s = function() {
|
|
@@ -6844,99 +5668,6 @@ bw.s = function() {
|
|
|
6844
5668
|
return result;
|
|
6845
5669
|
};
|
|
6846
5670
|
|
|
6847
|
-
/**
|
|
6848
|
-
* Pre-built CSS utility objects (like Tailwind utilities, but in JS).
|
|
6849
|
-
*
|
|
6850
|
-
* Compose with `bw.s()` to build inline styles without writing raw CSS strings.
|
|
6851
|
-
* Includes flex, padding, margin, typography, color, border, and transition utilities.
|
|
6852
|
-
*
|
|
6853
|
-
* @category CSS & Styling
|
|
6854
|
-
* @see bw.s
|
|
6855
|
-
* @example
|
|
6856
|
-
* { t: 'div', a: { style: bw.s(bw.u.flex, bw.u.gap4, bw.u.p4) },
|
|
6857
|
-
* c: 'Flexbox with 1rem gap and padding' }
|
|
6858
|
-
*/
|
|
6859
|
-
bw.u = {
|
|
6860
|
-
// Display
|
|
6861
|
-
flex: { display: 'flex' },
|
|
6862
|
-
flexCol: { display: 'flex', flexDirection: 'column' },
|
|
6863
|
-
flexRow: { display: 'flex', flexDirection: 'row' },
|
|
6864
|
-
flexWrap: { display: 'flex', flexWrap: 'wrap' },
|
|
6865
|
-
block: { display: 'block' },
|
|
6866
|
-
inline: { display: 'inline' },
|
|
6867
|
-
hidden: { display: 'none' },
|
|
6868
|
-
|
|
6869
|
-
// Flex alignment
|
|
6870
|
-
justifyCenter: { justifyContent: 'center' },
|
|
6871
|
-
justifyBetween: { justifyContent: 'space-between' },
|
|
6872
|
-
justifyEnd: { justifyContent: 'flex-end' },
|
|
6873
|
-
alignCenter: { alignItems: 'center' },
|
|
6874
|
-
alignStart: { alignItems: 'flex-start' },
|
|
6875
|
-
alignEnd: { alignItems: 'flex-end' },
|
|
6876
|
-
|
|
6877
|
-
// Gap (0.25rem increments)
|
|
6878
|
-
gap1: { gap: '0.25rem' },
|
|
6879
|
-
gap2: { gap: '0.5rem' },
|
|
6880
|
-
gap3: { gap: '0.75rem' },
|
|
6881
|
-
gap4: { gap: '1rem' },
|
|
6882
|
-
gap6: { gap: '1.5rem' },
|
|
6883
|
-
gap8: { gap: '2rem' },
|
|
6884
|
-
|
|
6885
|
-
// Padding
|
|
6886
|
-
p0: { padding: '0' },
|
|
6887
|
-
p1: { padding: '0.25rem' },
|
|
6888
|
-
p2: { padding: '0.5rem' },
|
|
6889
|
-
p3: { padding: '0.75rem' },
|
|
6890
|
-
p4: { padding: '1rem' },
|
|
6891
|
-
p6: { padding: '1.5rem' },
|
|
6892
|
-
p8: { padding: '2rem' },
|
|
6893
|
-
px4: { paddingLeft: '1rem', paddingRight: '1rem' },
|
|
6894
|
-
py2: { paddingTop: '0.5rem', paddingBottom: '0.5rem' },
|
|
6895
|
-
py4: { paddingTop: '1rem', paddingBottom: '1rem' },
|
|
6896
|
-
|
|
6897
|
-
// Margin (same scale)
|
|
6898
|
-
m0: { margin: '0' },
|
|
6899
|
-
m4: { margin: '1rem' },
|
|
6900
|
-
mt2: { marginTop: '0.5rem' },
|
|
6901
|
-
mt4: { marginTop: '1rem' },
|
|
6902
|
-
mb2: { marginBottom: '0.5rem' },
|
|
6903
|
-
mb4: { marginBottom: '1rem' },
|
|
6904
|
-
mx_auto: { marginLeft: 'auto', marginRight: 'auto' },
|
|
6905
|
-
|
|
6906
|
-
// Typography
|
|
6907
|
-
textSm: { fontSize: '0.875rem' },
|
|
6908
|
-
textBase: { fontSize: '1rem' },
|
|
6909
|
-
textLg: { fontSize: '1.125rem' },
|
|
6910
|
-
textXl: { fontSize: '1.25rem' },
|
|
6911
|
-
text2xl: { fontSize: '1.5rem' },
|
|
6912
|
-
text3xl: { fontSize: '1.875rem' },
|
|
6913
|
-
bold: { fontWeight: '700' },
|
|
6914
|
-
semibold: { fontWeight: '600' },
|
|
6915
|
-
italic: { fontStyle: 'italic' },
|
|
6916
|
-
textCenter: { textAlign: 'center' },
|
|
6917
|
-
textRight: { textAlign: 'right' },
|
|
6918
|
-
|
|
6919
|
-
// Colors (from design tokens)
|
|
6920
|
-
bgWhite: { background: '#ffffff' },
|
|
6921
|
-
bgTeal: { background: '#006666', color: '#ffffff' },
|
|
6922
|
-
textWhite: { color: '#ffffff' },
|
|
6923
|
-
textTeal: { color: '#006666' },
|
|
6924
|
-
textMuted: { color: '#888' },
|
|
6925
|
-
|
|
6926
|
-
// Borders
|
|
6927
|
-
rounded: { borderRadius: '0.375rem' },
|
|
6928
|
-
roundedLg: { borderRadius: '0.5rem' },
|
|
6929
|
-
roundedFull: { borderRadius: '9999px' },
|
|
6930
|
-
border: { border: '1px solid #d8d8d8' },
|
|
6931
|
-
|
|
6932
|
-
// Sizing
|
|
6933
|
-
wFull: { width: '100%' },
|
|
6934
|
-
hFull: { height: '100%' },
|
|
6935
|
-
|
|
6936
|
-
// Transitions
|
|
6937
|
-
transition: { transition: 'all 0.2s ease' }
|
|
6938
|
-
};
|
|
6939
|
-
|
|
6940
5671
|
/**
|
|
6941
5672
|
* Generate responsive CSS with media query breakpoints.
|
|
6942
5673
|
*
|
|
@@ -7058,103 +5789,49 @@ if (bw._isBrowser) {
|
|
|
7058
5789
|
};
|
|
7059
5790
|
}
|
|
7060
5791
|
|
|
7061
|
-
/**
|
|
7062
|
-
* Load the built-in Bootstrap-inspired default stylesheet.
|
|
7063
|
-
*
|
|
7064
|
-
* Injects bitwrench's batteries-included CSS (buttons, cards, grids, forms,
|
|
7065
|
-
* alerts, badges, nav, tabs, etc.) into the document head. Call once at app startup.
|
|
7066
|
-
* Returns null in Node.js (no DOM).
|
|
7067
|
-
*
|
|
7068
|
-
* @param {Object} [options] - Style loading options
|
|
7069
|
-
* @param {boolean} [options.minify=true] - Minify the CSS output
|
|
7070
|
-
* @returns {Element|null} Style element if in browser, null in Node.js
|
|
7071
|
-
* @category CSS & Styling
|
|
7072
|
-
* @see bw.setTheme
|
|
7073
|
-
* @see bw.applyTheme
|
|
7074
|
-
* @see bw.toggleTheme
|
|
7075
|
-
* @example
|
|
7076
|
-
* bw.loadDefaultStyles(); // inject all default CSS
|
|
7077
|
-
*/
|
|
7078
|
-
bw.loadDefaultStyles = function(options = {}) {
|
|
7079
|
-
const { minify = true, palette } = options;
|
|
7080
|
-
|
|
7081
|
-
// 1. Inject structural CSS (layout, sizing — never changes with theme)
|
|
7082
|
-
if (bw._isBrowser) {
|
|
7083
|
-
var structuralCSS = bw.css(getStructuralStyles());
|
|
7084
|
-
bw.injectCSS(structuralCSS, { id: 'bw_structural', append: false, minify: minify });
|
|
7085
|
-
}
|
|
7086
5792
|
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
return result;
|
|
7091
|
-
};
|
|
5793
|
+
// =========================================================================
|
|
5794
|
+
// v2.0.18 Clean Styles API — makeStyles / applyStyles / loadStyles / etc.
|
|
5795
|
+
// =========================================================================
|
|
7092
5796
|
|
|
5797
|
+
/**
|
|
5798
|
+
* Convert a scope selector to a <style> element id.
|
|
5799
|
+
* @private
|
|
5800
|
+
* @param {string} [scope] - Scope selector (e.g. '#my-dashboard', '.preview')
|
|
5801
|
+
* @returns {string} Style element id (e.g. 'bw_style_my_dashboard')
|
|
5802
|
+
*/
|
|
5803
|
+
function _scopeToStyleId(scope) {
|
|
5804
|
+
if (!scope || scope === '' || scope === 'global') return 'bw_style_global';
|
|
5805
|
+
if (scope === 'reset') return 'bw_style_reset';
|
|
5806
|
+
// Strip leading # or . and convert - to _
|
|
5807
|
+
var clean = scope.replace(/^[#.]/, '').replace(/-/g, '_');
|
|
5808
|
+
return 'bw_style_' + clean;
|
|
5809
|
+
}
|
|
7093
5810
|
|
|
7094
5811
|
/**
|
|
7095
|
-
* Generate a complete
|
|
7096
|
-
*
|
|
7097
|
-
*
|
|
7098
|
-
*
|
|
7099
|
-
*
|
|
7100
|
-
*
|
|
7101
|
-
*
|
|
7102
|
-
* @param {string}
|
|
7103
|
-
* @param {
|
|
7104
|
-
* @param {string} config.primary - Primary brand color hex
|
|
7105
|
-
* @param {string} config.secondary - Secondary color hex
|
|
7106
|
-
* @param {string} [config.tertiary] - Tertiary/accent color hex (defaults to primary)
|
|
7107
|
-
* @param {string} [config.success='#198754'] - Success color hex
|
|
7108
|
-
* @param {string} [config.danger='#dc3545'] - Danger color hex
|
|
7109
|
-
* @param {string} [config.warning='#ffc107'] - Warning color hex
|
|
7110
|
-
* @param {string} [config.info='#0dcaf0'] - Info color hex
|
|
7111
|
-
* @param {string} [config.light='#f8f9fa'] - Light color hex
|
|
7112
|
-
* @param {string} [config.dark='#212529'] - Dark color hex
|
|
7113
|
-
* @param {string} [config.background] - Page background hex (default: '#ffffff' light, derived dark)
|
|
7114
|
-
* @param {string} [config.surface] - Surface/card background hex (default: '#f8f9fa' light, derived dark)
|
|
5812
|
+
* Generate a complete styles object from seed colors and layout config.
|
|
5813
|
+
* Pure function — no DOM, no state, no side effects.
|
|
5814
|
+
*
|
|
5815
|
+
* All parameters are optional. Defaults to the bitwrench default palette.
|
|
5816
|
+
*
|
|
5817
|
+
* @param {Object} [config] - Style configuration
|
|
5818
|
+
* @param {string} [config.primary='#006666'] - Primary brand color hex
|
|
5819
|
+
* @param {string} [config.secondary='#6c757d'] - Secondary color hex
|
|
5820
|
+
* @param {string} [config.tertiary] - Tertiary color hex (defaults to primary)
|
|
7115
5821
|
* @param {string} [config.spacing='normal'] - 'compact' | 'normal' | 'spacious'
|
|
7116
5822
|
* @param {string} [config.radius='md'] - 'none' | 'sm' | 'md' | 'lg' | 'pill'
|
|
7117
|
-
* @
|
|
7118
|
-
* @param {string|number} [config.typeRatio='normal'] - 'tight' | 'normal' | 'relaxed' | 'dramatic' or a number
|
|
7119
|
-
* @param {string} [config.elevation='md'] - 'flat' | 'sm' | 'md' | 'lg'
|
|
7120
|
-
* @param {string} [config.motion='standard'] - 'reduced' | 'standard' | 'expressive'
|
|
7121
|
-
* @param {number} [config.harmonize=0.20] - 0-1, semantic color hue shift toward primary
|
|
7122
|
-
* @param {boolean} [config.inject=true] - Inject into DOM (browser only)
|
|
7123
|
-
* @returns {Object} { css, palette, name, isLightPrimary, alternate: { css, palette } }
|
|
5823
|
+
* @returns {Object} { css, alternateCss, rules, alternateRules, palette, alternatePalette, isLightPrimary }
|
|
7124
5824
|
* @category CSS & Styling
|
|
7125
|
-
* @see bw.
|
|
7126
|
-
* @see bw.
|
|
7127
|
-
* @see bw.loadDefaultStyles
|
|
5825
|
+
* @see bw.applyStyles
|
|
5826
|
+
* @see bw.loadStyles
|
|
7128
5827
|
* @example
|
|
7129
|
-
*
|
|
7130
|
-
*
|
|
7131
|
-
*
|
|
7132
|
-
* secondary: '#90e0ef',
|
|
7133
|
-
* tertiary: '#00b4d8'
|
|
7134
|
-
* });
|
|
7135
|
-
*
|
|
7136
|
-
* // Apply to a container
|
|
7137
|
-
* document.getElementById('app').classList.add('ocean');
|
|
7138
|
-
*
|
|
7139
|
-
* // Toggle to alternate palette
|
|
7140
|
-
* bw.toggleTheme();
|
|
7141
|
-
*
|
|
7142
|
-
* // Generate CSS for static export (Node.js)
|
|
7143
|
-
* var result = bw.generateTheme('sunset', {
|
|
7144
|
-
* primary: '#e76f51',
|
|
7145
|
-
* secondary: '#264653',
|
|
7146
|
-
* inject: false
|
|
7147
|
-
* });
|
|
7148
|
-
* fs.writeFileSync('sunset.css', result.css + result.alternate.css);
|
|
5828
|
+
* var styles = bw.makeStyles({ primary: '#4f46e5', secondary: '#d97706' });
|
|
5829
|
+
* console.log(styles.palette.primary.base); // '#4f46e5'
|
|
5830
|
+
* // styles.css contains all themed CSS — nothing injected
|
|
7149
5831
|
*/
|
|
7150
|
-
bw.
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
}
|
|
7154
|
-
|
|
7155
|
-
// Merge with defaults; if user didn't supply tertiary, default to their primary
|
|
7156
|
-
var fullConfig = Object.assign({}, DEFAULT_PALETTE_CONFIG, config);
|
|
7157
|
-
if (!config.tertiary) fullConfig.tertiary = fullConfig.primary;
|
|
5832
|
+
bw.makeStyles = function(config) {
|
|
5833
|
+
var fullConfig = Object.assign({}, DEFAULT_PALETTE_CONFIG, config || {});
|
|
5834
|
+
if (config && !config.tertiary) fullConfig.tertiary = fullConfig.primary;
|
|
7158
5835
|
|
|
7159
5836
|
// Derive primary palette
|
|
7160
5837
|
var palette = derivePalette(fullConfig);
|
|
@@ -7162,131 +5839,207 @@ bw.generateTheme = function(name, config) {
|
|
|
7162
5839
|
// Resolve layout
|
|
7163
5840
|
var layout = resolveLayout(fullConfig);
|
|
7164
5841
|
|
|
7165
|
-
// Generate primary themed CSS rules
|
|
7166
|
-
var themedRules = generateThemedCSS(
|
|
5842
|
+
// Generate primary themed CSS rules (unscoped)
|
|
5843
|
+
var themedRules = generateThemedCSS('', palette, layout);
|
|
7167
5844
|
var cssStr = bw.css(themedRules);
|
|
7168
5845
|
|
|
7169
5846
|
// Derive alternate palette (luminance-inverted)
|
|
7170
5847
|
var altConfig = deriveAlternateConfig(fullConfig);
|
|
7171
5848
|
var altPalette = derivePalette(altConfig);
|
|
7172
5849
|
|
|
7173
|
-
// Generate alternate CSS
|
|
7174
|
-
|
|
7175
|
-
var
|
|
5850
|
+
// Generate alternate CSS rules WITHOUT .bw_theme_alt prefix (raw rules)
|
|
5851
|
+
// applyStyles() wraps them appropriately based on scope
|
|
5852
|
+
var altRawRules = generateThemedCSS('', altPalette, layout);
|
|
5853
|
+
|
|
5854
|
+
// Add body-level surface overrides for the alternate palette.
|
|
5855
|
+
// When .bw_theme_alt is on <html>, ".bw_theme_alt body" correctly matches.
|
|
5856
|
+
altRawRules['body'] = {
|
|
5857
|
+
'color': altPalette.dark.base,
|
|
5858
|
+
'background-color': altPalette.surface || altPalette.light.base
|
|
5859
|
+
};
|
|
5860
|
+
|
|
5861
|
+
var altCssStr = bw.css(altRawRules);
|
|
7176
5862
|
|
|
7177
5863
|
// Determine if primary is light-flavored
|
|
7178
5864
|
var lightPrimary = isLightPalette(fullConfig);
|
|
7179
5865
|
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7187
|
-
|
|
7188
|
-
|
|
5866
|
+
return {
|
|
5867
|
+
css: cssStr,
|
|
5868
|
+
alternateCss: altCssStr,
|
|
5869
|
+
rules: themedRules,
|
|
5870
|
+
alternateRules: altRawRules,
|
|
5871
|
+
palette: palette,
|
|
5872
|
+
alternatePalette: altPalette,
|
|
5873
|
+
isLightPrimary: lightPrimary
|
|
5874
|
+
};
|
|
5875
|
+
};
|
|
7189
5876
|
|
|
7190
|
-
|
|
5877
|
+
/**
|
|
5878
|
+
* Inject styles into the DOM with optional scoping.
|
|
5879
|
+
*
|
|
5880
|
+
* Takes a styles object from `makeStyles()` and creates a single `<style>`
|
|
5881
|
+
* element in `<head>`. If a scope selector is provided, all CSS rules are
|
|
5882
|
+
* wrapped under that selector. Alternate CSS is wrapped under `.bw_theme_alt`.
|
|
5883
|
+
*
|
|
5884
|
+
* @param {Object} styles - Result of `bw.makeStyles()`
|
|
5885
|
+
* @param {string} [scope] - Scope selector (e.g. '#my-dashboard', '.preview'). Omit for global.
|
|
5886
|
+
* @returns {Element|null} The `<style>` element, or null in Node.js
|
|
5887
|
+
* @category CSS & Styling
|
|
5888
|
+
* @see bw.makeStyles
|
|
5889
|
+
* @see bw.loadStyles
|
|
5890
|
+
* @see bw.clearStyles
|
|
5891
|
+
* @example
|
|
5892
|
+
* var styles = bw.makeStyles({ primary: '#4f46e5' });
|
|
5893
|
+
* bw.applyStyles(styles); // global
|
|
5894
|
+
* bw.applyStyles(styles, '#my-dashboard'); // scoped
|
|
5895
|
+
*/
|
|
5896
|
+
bw.applyStyles = function(styles, scope) {
|
|
5897
|
+
if (!bw._isBrowser) return null;
|
|
5898
|
+
if (!styles || !styles.rules) {
|
|
5899
|
+
_cw('bw.applyStyles: invalid styles object');
|
|
5900
|
+
return null;
|
|
7191
5901
|
}
|
|
7192
5902
|
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
5903
|
+
var styleId = _scopeToStyleId(scope);
|
|
5904
|
+
|
|
5905
|
+
// Scope the primary rules if a scope is provided
|
|
5906
|
+
var primaryRules = styles.rules;
|
|
5907
|
+
if (scope) {
|
|
5908
|
+
primaryRules = scopeRulesUnder(primaryRules, scope);
|
|
7199
5909
|
}
|
|
7200
5910
|
|
|
7201
|
-
//
|
|
7202
|
-
var
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
5911
|
+
// Wrap alternate rules with .bw_theme_alt
|
|
5912
|
+
var altRules = styles.alternateRules;
|
|
5913
|
+
if (altRules) {
|
|
5914
|
+
if (scope) {
|
|
5915
|
+
// Scoped compound: #scope.bw_theme_alt .bw_card
|
|
5916
|
+
altRules = scopeRulesUnder(altRules, scope + '.bw_theme_alt');
|
|
5917
|
+
} else {
|
|
5918
|
+
// Global: .bw_theme_alt .bw_card
|
|
5919
|
+
altRules = scopeRulesUnder(altRules, '.bw_theme_alt');
|
|
7210
5920
|
}
|
|
7211
|
-
}
|
|
7212
|
-
bw._activeTheme = result;
|
|
7213
|
-
bw._activeThemeMode = 'primary';
|
|
5921
|
+
}
|
|
7214
5922
|
|
|
7215
|
-
|
|
5923
|
+
// Combine primary + alternate into one CSS string
|
|
5924
|
+
var combined = bw.css(primaryRules);
|
|
5925
|
+
if (altRules) {
|
|
5926
|
+
combined += '\n' + bw.css(altRules);
|
|
5927
|
+
}
|
|
5928
|
+
|
|
5929
|
+
return bw.injectCSS(combined, { id: styleId, append: false });
|
|
7216
5930
|
};
|
|
7217
5931
|
|
|
7218
5932
|
/**
|
|
7219
|
-
*
|
|
7220
|
-
*
|
|
5933
|
+
* Generate and apply styles in one call. Convenience wrapper.
|
|
5934
|
+
*
|
|
5935
|
+
* Equivalent to: `bw.applyStyles(bw.makeStyles(config), scope)`
|
|
7221
5936
|
*
|
|
7222
|
-
* @param {
|
|
7223
|
-
* @
|
|
5937
|
+
* @param {Object} [config] - Style configuration (same as `makeStyles`)
|
|
5938
|
+
* @param {string} [scope] - Scope selector (same as `applyStyles`)
|
|
5939
|
+
* @returns {Element|null} The `<style>` element, or null in Node.js
|
|
7224
5940
|
* @category CSS & Styling
|
|
7225
|
-
* @see bw.
|
|
7226
|
-
* @see bw.
|
|
5941
|
+
* @see bw.makeStyles
|
|
5942
|
+
* @see bw.applyStyles
|
|
7227
5943
|
* @example
|
|
7228
|
-
* bw.
|
|
7229
|
-
* bw.
|
|
7230
|
-
* bw.
|
|
5944
|
+
* bw.loadStyles(); // defaults, global
|
|
5945
|
+
* bw.loadStyles({ primary: '#4f46e5' }); // custom, global
|
|
5946
|
+
* bw.loadStyles({ primary: '#4f46e5' }, '#my-dashboard'); // custom, scoped
|
|
7231
5947
|
*/
|
|
7232
|
-
bw.
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7240
|
-
else if (mode === 'light') wantAlt = !isLight;
|
|
7241
|
-
else if (mode === 'dark') wantAlt = isLight;
|
|
7242
|
-
else wantAlt = false;
|
|
7243
|
-
|
|
7244
|
-
if (wantAlt) {
|
|
7245
|
-
root.classList.add('bw_theme_alt');
|
|
7246
|
-
} else {
|
|
7247
|
-
root.classList.remove('bw_theme_alt');
|
|
5948
|
+
bw.loadStyles = function(config, scope) {
|
|
5949
|
+
// Also inject structural CSS first (only once)
|
|
5950
|
+
if (bw._isBrowser) {
|
|
5951
|
+
var existing = document.getElementById('bw_structural');
|
|
5952
|
+
if (!existing) {
|
|
5953
|
+
var structuralCSS = bw.css(getStructuralStyles());
|
|
5954
|
+
bw.injectCSS(structuralCSS, { id: 'bw_structural', append: false });
|
|
5955
|
+
}
|
|
7248
5956
|
}
|
|
5957
|
+
return bw.applyStyles(bw.makeStyles(config), scope);
|
|
5958
|
+
};
|
|
7249
5959
|
|
|
7250
|
-
|
|
7251
|
-
|
|
5960
|
+
/**
|
|
5961
|
+
* Inject the CSS reset (box-sizing, html/body font, reduced-motion).
|
|
5962
|
+
* Idempotent — if already injected, returns the existing `<style>` element.
|
|
5963
|
+
*
|
|
5964
|
+
* @returns {Element|null} The `<style>` element, or null in Node.js
|
|
5965
|
+
* @category CSS & Styling
|
|
5966
|
+
* @see bw.loadStyles
|
|
5967
|
+
* @see bw.clearStyles
|
|
5968
|
+
* @example
|
|
5969
|
+
* bw.loadReset(); // inject once, safe to call multiple times
|
|
5970
|
+
*/
|
|
5971
|
+
bw.loadReset = function() {
|
|
5972
|
+
if (!bw._isBrowser) return null;
|
|
5973
|
+
var existing = document.getElementById('bw_style_reset');
|
|
5974
|
+
if (existing) return existing;
|
|
5975
|
+
return bw.injectCSS(bw.css(getResetStyles()), { id: 'bw_style_reset', append: false });
|
|
7252
5976
|
};
|
|
7253
5977
|
|
|
7254
5978
|
/**
|
|
7255
|
-
* Toggle between primary and alternate
|
|
5979
|
+
* Toggle between primary and alternate palettes.
|
|
7256
5980
|
*
|
|
5981
|
+
* Adds/removes the `bw_theme_alt` class on the scoping element.
|
|
5982
|
+
* Without a scope, toggles on `<html>` (global).
|
|
5983
|
+
* With a scope, toggles on the first matching element.
|
|
5984
|
+
*
|
|
5985
|
+
* @param {string} [scope] - Scope selector (e.g. '#my-dashboard'). Omit for global.
|
|
7257
5986
|
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
7258
5987
|
* @category CSS & Styling
|
|
7259
|
-
* @see bw.
|
|
7260
|
-
* @see bw.
|
|
5988
|
+
* @see bw.applyStyles
|
|
5989
|
+
* @see bw.clearStyles
|
|
7261
5990
|
* @example
|
|
7262
|
-
* bw.
|
|
5991
|
+
* bw.toggleStyles(); // global toggle on <html>
|
|
5992
|
+
* bw.toggleStyles('#my-dashboard'); // scoped toggle
|
|
7263
5993
|
*/
|
|
7264
|
-
bw.
|
|
7265
|
-
|
|
7266
|
-
|
|
5994
|
+
bw.toggleStyles = function(scope) {
|
|
5995
|
+
if (!bw._isBrowser) return 'primary';
|
|
5996
|
+
var target;
|
|
5997
|
+
if (scope) {
|
|
5998
|
+
var els = bw.$(scope);
|
|
5999
|
+
target = els[0];
|
|
6000
|
+
} else {
|
|
6001
|
+
target = document.documentElement;
|
|
6002
|
+
}
|
|
6003
|
+
if (!target) return 'primary';
|
|
6004
|
+
|
|
6005
|
+
var hasAlt = target.classList.contains('bw_theme_alt');
|
|
6006
|
+
if (hasAlt) {
|
|
6007
|
+
target.classList.remove('bw_theme_alt');
|
|
6008
|
+
return 'primary';
|
|
6009
|
+
} else {
|
|
6010
|
+
target.classList.add('bw_theme_alt');
|
|
6011
|
+
return 'alternate';
|
|
6012
|
+
}
|
|
7267
6013
|
};
|
|
7268
6014
|
|
|
7269
6015
|
/**
|
|
7270
|
-
* Remove
|
|
7271
|
-
*
|
|
7272
|
-
*
|
|
6016
|
+
* Remove injected styles for a given scope.
|
|
6017
|
+
*
|
|
6018
|
+
* Finds the `<style>` element by id and removes it. Also removes
|
|
6019
|
+
* the `bw_theme_alt` class from the relevant element.
|
|
7273
6020
|
*
|
|
6021
|
+
* @param {string} [scope] - Scope selector. Omit to remove global styles.
|
|
7274
6022
|
* @category CSS & Styling
|
|
7275
|
-
* @see bw.
|
|
6023
|
+
* @see bw.applyStyles
|
|
6024
|
+
* @see bw.loadStyles
|
|
7276
6025
|
* @example
|
|
7277
|
-
* bw.
|
|
7278
|
-
* bw.
|
|
6026
|
+
* bw.clearStyles(); // remove global styles
|
|
6027
|
+
* bw.clearStyles('#my-dashboard'); // remove scoped styles
|
|
6028
|
+
* bw.clearStyles('reset'); // remove the CSS reset
|
|
7279
6029
|
*/
|
|
7280
|
-
bw.
|
|
7281
|
-
if (bw.
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
6030
|
+
bw.clearStyles = function(scope) {
|
|
6031
|
+
if (!bw._isBrowser) return;
|
|
6032
|
+
var styleId = _scopeToStyleId(scope);
|
|
6033
|
+
var el = document.getElementById(styleId);
|
|
6034
|
+
if (el) el.remove();
|
|
6035
|
+
|
|
6036
|
+
// Also remove bw_theme_alt from the relevant element
|
|
6037
|
+
if (scope && scope !== 'reset' && scope !== 'global') {
|
|
6038
|
+
var targets = bw.$(scope);
|
|
6039
|
+
if (targets[0]) targets[0].classList.remove('bw_theme_alt');
|
|
6040
|
+
} else if (!scope || scope === 'global') {
|
|
6041
|
+
document.documentElement.classList.remove('bw_theme_alt');
|
|
7287
6042
|
}
|
|
7288
|
-
bw._activeTheme = null;
|
|
7289
|
-
bw._activeThemeMode = 'primary';
|
|
7290
6043
|
};
|
|
7291
6044
|
|
|
7292
6045
|
// Expose color utility functions on bw namespace
|
|
@@ -7509,10 +6262,15 @@ bw.copyToClipboard = function(text) {
|
|
|
7509
6262
|
* @param {Object} config - Table configuration
|
|
7510
6263
|
* @param {Array<Object>} config.data - Array of row objects to display
|
|
7511
6264
|
* @param {Array<Object>} [config.columns] - Column definitions with key, label, render
|
|
7512
|
-
* @param {string} [config.className='
|
|
6265
|
+
* @param {string} [config.className=''] - Additional CSS classes for table element
|
|
7513
6266
|
* @param {boolean} [config.sortable=true] - Enable click-to-sort headers
|
|
7514
6267
|
* @param {Function} [config.onSort] - Sort callback (column, direction)
|
|
7515
|
-
* @
|
|
6268
|
+
* @param {boolean} [config.selectable=false] - Enable row selection on click
|
|
6269
|
+
* @param {Function} [config.onRowClick] - Row click callback (row, index, event)
|
|
6270
|
+
* @param {number} [config.pageSize] - Rows per page (enables pagination when set)
|
|
6271
|
+
* @param {number} [config.currentPage=1] - Current page number (1-based)
|
|
6272
|
+
* @param {Function} [config.onPageChange] - Page change callback (newPage)
|
|
6273
|
+
* @returns {Object} TACO object for table (with optional pagination controls)
|
|
7516
6274
|
* @category Component Builders
|
|
7517
6275
|
* @see bw.makeDataTable
|
|
7518
6276
|
* @example
|
|
@@ -7524,7 +6282,12 @@ bw.copyToClipboard = function(text) {
|
|
|
7524
6282
|
* columns: [
|
|
7525
6283
|
* { key: 'name', label: 'Name' },
|
|
7526
6284
|
* { key: 'age', label: 'Age' }
|
|
7527
|
-
* ]
|
|
6285
|
+
* ],
|
|
6286
|
+
* selectable: true,
|
|
6287
|
+
* onRowClick: function(row, i) { console.log('clicked', row.name); },
|
|
6288
|
+
* pageSize: 10,
|
|
6289
|
+
* currentPage: 1,
|
|
6290
|
+
* onPageChange: function(page) { console.log('page', page); }
|
|
7528
6291
|
* });
|
|
7529
6292
|
*/
|
|
7530
6293
|
bw.makeTable = function(config) {
|
|
@@ -7537,41 +6300,47 @@ bw.makeTable = function(config) {
|
|
|
7537
6300
|
sortable = true,
|
|
7538
6301
|
onSort,
|
|
7539
6302
|
sortColumn,
|
|
7540
|
-
sortDirection = 'asc'
|
|
6303
|
+
sortDirection = 'asc',
|
|
6304
|
+
selectable = false,
|
|
6305
|
+
onRowClick,
|
|
6306
|
+
pageSize,
|
|
6307
|
+
currentPage = 1,
|
|
6308
|
+
onPageChange
|
|
7541
6309
|
} = config;
|
|
7542
6310
|
|
|
7543
|
-
// Build class list: always include bw_table, add striped/hover, append user className
|
|
6311
|
+
// Build class list: always include bw_table, add striped/hover/selectable, append user className
|
|
7544
6312
|
let cls = 'bw_table';
|
|
7545
6313
|
if (striped) cls += ' bw_table_striped';
|
|
7546
|
-
if (hover) cls += ' bw_table_hover';
|
|
6314
|
+
if (hover || selectable) cls += ' bw_table_hover';
|
|
6315
|
+
if (selectable) cls += ' bw_table_selectable';
|
|
7547
6316
|
if (className) cls += ' ' + className;
|
|
7548
6317
|
cls = cls.trim();
|
|
7549
|
-
|
|
6318
|
+
|
|
7550
6319
|
// Auto-detect columns if not provided
|
|
7551
|
-
const cols = columns || (data.length > 0
|
|
6320
|
+
const cols = columns || (data.length > 0
|
|
7552
6321
|
? _keys(data[0]).map(key => ({ key, label: key }))
|
|
7553
6322
|
: []);
|
|
7554
|
-
|
|
6323
|
+
|
|
7555
6324
|
// Current sort state
|
|
7556
6325
|
let currentSortColumn = sortColumn || null;
|
|
7557
6326
|
let currentSortDirection = sortDirection;
|
|
7558
|
-
|
|
6327
|
+
|
|
7559
6328
|
// Sort data if column specified
|
|
7560
6329
|
let sortedData = [...data];
|
|
7561
6330
|
if (currentSortColumn) {
|
|
7562
6331
|
sortedData.sort((a, b) => {
|
|
7563
6332
|
const aVal = a[currentSortColumn];
|
|
7564
6333
|
const bVal = b[currentSortColumn];
|
|
7565
|
-
|
|
6334
|
+
|
|
7566
6335
|
// Handle different types
|
|
7567
6336
|
if (_is(aVal, 'number') && _is(bVal, 'number')) {
|
|
7568
6337
|
return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
7569
6338
|
}
|
|
7570
|
-
|
|
6339
|
+
|
|
7571
6340
|
// String comparison
|
|
7572
6341
|
const aStr = String(aVal || '').toLowerCase();
|
|
7573
6342
|
const bStr = String(bVal || '').toLowerCase();
|
|
7574
|
-
|
|
6343
|
+
|
|
7575
6344
|
if (currentSortDirection === 'asc') {
|
|
7576
6345
|
return aStr.localeCompare(bStr);
|
|
7577
6346
|
} else {
|
|
@@ -7579,23 +6348,32 @@ bw.makeTable = function(config) {
|
|
|
7579
6348
|
}
|
|
7580
6349
|
});
|
|
7581
6350
|
}
|
|
7582
|
-
|
|
6351
|
+
|
|
6352
|
+
// Pagination
|
|
6353
|
+
const totalRows = sortedData.length;
|
|
6354
|
+
const totalPages = pageSize ? Math.max(1, Math.ceil(totalRows / pageSize)) : 1;
|
|
6355
|
+
const page = Math.max(1, Math.min(currentPage, totalPages));
|
|
6356
|
+
if (pageSize) {
|
|
6357
|
+
const start = (page - 1) * pageSize;
|
|
6358
|
+
sortedData = sortedData.slice(start, start + pageSize);
|
|
6359
|
+
}
|
|
6360
|
+
|
|
7583
6361
|
// Create sort handler
|
|
7584
6362
|
const handleSort = (column) => {
|
|
7585
6363
|
if (!sortable) return;
|
|
7586
|
-
|
|
6364
|
+
|
|
7587
6365
|
if (currentSortColumn === column) {
|
|
7588
6366
|
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
|
|
7589
6367
|
} else {
|
|
7590
6368
|
currentSortColumn = column;
|
|
7591
6369
|
currentSortDirection = 'asc';
|
|
7592
6370
|
}
|
|
7593
|
-
|
|
6371
|
+
|
|
7594
6372
|
if (onSort) {
|
|
7595
6373
|
onSort(column, currentSortDirection);
|
|
7596
6374
|
}
|
|
7597
6375
|
};
|
|
7598
|
-
|
|
6376
|
+
|
|
7599
6377
|
// Build table header
|
|
7600
6378
|
const thead = {
|
|
7601
6379
|
t: 'thead',
|
|
@@ -7618,24 +6396,87 @@ bw.makeTable = function(config) {
|
|
|
7618
6396
|
}))
|
|
7619
6397
|
}
|
|
7620
6398
|
};
|
|
7621
|
-
|
|
7622
|
-
// Build table body
|
|
6399
|
+
|
|
6400
|
+
// Build table body with selectable/onRowClick support
|
|
7623
6401
|
const tbody = {
|
|
7624
6402
|
t: 'tbody',
|
|
7625
|
-
c: sortedData.map(row =>
|
|
7626
|
-
|
|
7627
|
-
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
6403
|
+
c: sortedData.map((row, idx) => {
|
|
6404
|
+
const globalIdx = pageSize ? (page - 1) * pageSize + idx : idx;
|
|
6405
|
+
const rowAttrs = {};
|
|
6406
|
+
if (selectable || onRowClick) {
|
|
6407
|
+
rowAttrs.style = 'cursor:pointer;';
|
|
6408
|
+
rowAttrs.onclick = function(e) {
|
|
6409
|
+
if (selectable) {
|
|
6410
|
+
// Toggle selected class on this row
|
|
6411
|
+
var tr = e.currentTarget;
|
|
6412
|
+
tr.classList.toggle('bw_table_row_selected');
|
|
6413
|
+
}
|
|
6414
|
+
if (onRowClick) {
|
|
6415
|
+
onRowClick(row, globalIdx, e);
|
|
6416
|
+
}
|
|
6417
|
+
};
|
|
6418
|
+
}
|
|
6419
|
+
return {
|
|
6420
|
+
t: 'tr',
|
|
6421
|
+
a: rowAttrs,
|
|
6422
|
+
c: cols.map(col => ({
|
|
6423
|
+
t: 'td',
|
|
6424
|
+
c: col.render ? col.render(row[col.key], row) : String(row[col.key] || '')
|
|
6425
|
+
}))
|
|
6426
|
+
};
|
|
6427
|
+
})
|
|
7632
6428
|
};
|
|
7633
|
-
|
|
7634
|
-
|
|
6429
|
+
|
|
6430
|
+
const table = {
|
|
7635
6431
|
t: 'table',
|
|
7636
6432
|
a: { class: cls },
|
|
7637
6433
|
c: [thead, tbody]
|
|
7638
6434
|
};
|
|
6435
|
+
|
|
6436
|
+
// If no pagination, return table directly
|
|
6437
|
+
if (!pageSize) return table;
|
|
6438
|
+
|
|
6439
|
+
// Build pagination controls
|
|
6440
|
+
const pageButtons = [];
|
|
6441
|
+
// Previous button
|
|
6442
|
+
pageButtons.push({
|
|
6443
|
+
t: 'button',
|
|
6444
|
+
a: {
|
|
6445
|
+
class: 'bw_btn bw_btn_sm',
|
|
6446
|
+
disabled: page <= 1 ? 'disabled' : undefined,
|
|
6447
|
+
onclick: page > 1 && onPageChange ? function() { onPageChange(page - 1); } : undefined
|
|
6448
|
+
},
|
|
6449
|
+
c: 'Prev'
|
|
6450
|
+
});
|
|
6451
|
+
// Page info
|
|
6452
|
+
pageButtons.push({
|
|
6453
|
+
t: 'span',
|
|
6454
|
+
a: { style: 'margin:0 0.5rem;font-size:0.875rem;' },
|
|
6455
|
+
c: 'Page ' + page + ' of ' + totalPages
|
|
6456
|
+
});
|
|
6457
|
+
// Next button
|
|
6458
|
+
pageButtons.push({
|
|
6459
|
+
t: 'button',
|
|
6460
|
+
a: {
|
|
6461
|
+
class: 'bw_btn bw_btn_sm',
|
|
6462
|
+
disabled: page >= totalPages ? 'disabled' : undefined,
|
|
6463
|
+
onclick: page < totalPages && onPageChange ? function() { onPageChange(page + 1); } : undefined
|
|
6464
|
+
},
|
|
6465
|
+
c: 'Next'
|
|
6466
|
+
});
|
|
6467
|
+
|
|
6468
|
+
return {
|
|
6469
|
+
t: 'div',
|
|
6470
|
+
a: { class: 'bw_table_paginated' },
|
|
6471
|
+
c: [
|
|
6472
|
+
table,
|
|
6473
|
+
{
|
|
6474
|
+
t: 'div',
|
|
6475
|
+
a: { class: 'bw_table_pagination', style: 'display:flex;align-items:center;justify-content:flex-end;padding:0.5rem 0;gap:0.25rem;' },
|
|
6476
|
+
c: pageButtons
|
|
6477
|
+
}
|
|
6478
|
+
]
|
|
6479
|
+
};
|
|
7639
6480
|
};
|
|
7640
6481
|
|
|
7641
6482
|
/**
|
|
@@ -7918,8 +6759,8 @@ bw.render = function(element, position, taco) {
|
|
|
7918
6759
|
};
|
|
7919
6760
|
}
|
|
7920
6761
|
|
|
7921
|
-
// Generate unique
|
|
7922
|
-
const componentId = taco.o?.id || bw.uuid();
|
|
6762
|
+
// Generate unique UUID class if not provided
|
|
6763
|
+
const componentId = taco.o?.id || bw.uuid('uuid');
|
|
7923
6764
|
|
|
7924
6765
|
// Create DOM element
|
|
7925
6766
|
let domElement;
|
|
@@ -7934,9 +6775,10 @@ bw.render = function(element, position, taco) {
|
|
|
7934
6775
|
};
|
|
7935
6776
|
}
|
|
7936
6777
|
|
|
7937
|
-
// Add component ID
|
|
7938
|
-
domElement.
|
|
7939
|
-
|
|
6778
|
+
// Add component ID as class + lifecycle marker
|
|
6779
|
+
domElement.classList.add(componentId);
|
|
6780
|
+
domElement.classList.add(_BW_LC);
|
|
6781
|
+
|
|
7940
6782
|
// Insert into DOM based on position
|
|
7941
6783
|
try {
|
|
7942
6784
|
switch(position) {
|
|
@@ -8010,7 +6852,8 @@ bw.render = function(element, position, taco) {
|
|
|
8010
6852
|
|
|
8011
6853
|
// Re-render
|
|
8012
6854
|
const newElement = bw.createDOM(this._taco);
|
|
8013
|
-
newElement.
|
|
6855
|
+
newElement.classList.add(componentId);
|
|
6856
|
+
newElement.classList.add(_BW_LC);
|
|
8014
6857
|
|
|
8015
6858
|
// Replace in DOM
|
|
8016
6859
|
parent.replaceChild(newElement, this.element);
|
|
@@ -8197,13 +7040,12 @@ bw.BCCL = BCCL;
|
|
|
8197
7040
|
// Variant class helper: bw.variantClass('primary') → 'bw_primary'
|
|
8198
7041
|
bw.variantClass = variantClass;
|
|
8199
7042
|
|
|
8200
|
-
// Create functions that return
|
|
7043
|
+
// Create functions that return DOM elements (createCard, createTable, etc.)
|
|
8201
7044
|
Object.entries(components).forEach(([name, fn]) => {
|
|
8202
7045
|
if (name.startsWith('make')) {
|
|
8203
|
-
const createName = 'create' + name.substring(4);
|
|
7046
|
+
const createName = 'create' + name.substring(4);
|
|
8204
7047
|
bw[createName] = function(props) {
|
|
8205
|
-
|
|
8206
|
-
return bw.renderComponent(taco);
|
|
7048
|
+
return bw.createDOM(fn(props));
|
|
8207
7049
|
};
|
|
8208
7050
|
}
|
|
8209
7051
|
});
|