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