bitwrench 2.0.16 → 2.0.18
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 +127 -38
- package/dist/bitwrench-bccl.cjs.js +13 -9
- package/dist/bitwrench-bccl.cjs.min.js +2 -2
- package/dist/bitwrench-bccl.esm.js +13 -9
- package/dist/bitwrench-bccl.esm.min.js +2 -2
- package/dist/bitwrench-bccl.umd.js +13 -9
- package/dist/bitwrench-bccl.umd.min.js +2 -2
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.cjs.min.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +1438 -920
- package/dist/bitwrench-lean.cjs.min.js +20 -20
- package/dist/bitwrench-lean.es5.js +1518 -1105
- package/dist/bitwrench-lean.es5.min.js +18 -18
- package/dist/bitwrench-lean.esm.js +1437 -920
- package/dist/bitwrench-lean.esm.min.js +20 -20
- package/dist/bitwrench-lean.umd.js +1438 -920
- 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 +1450 -928
- package/dist/bitwrench.cjs.min.js +21 -21
- package/dist/bitwrench.css +456 -132
- package/dist/bitwrench.es5.js +1563 -1140
- package/dist/bitwrench.es5.min.js +19 -19
- package/dist/bitwrench.esm.js +1450 -929
- package/dist/bitwrench.esm.min.js +21 -21
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +1450 -928
- package/dist/bitwrench.umd.min.js +21 -21
- package/dist/builds.json +178 -90
- package/dist/bwserve.cjs.js +528 -68
- package/dist/bwserve.esm.js +527 -69
- package/dist/sri.json +44 -36
- package/package.json +5 -2
- package/readme.html +136 -49
- package/src/bitwrench-bccl.js +12 -8
- package/src/bitwrench-color-utils.js +31 -9
- package/src/bitwrench-esm-entry.js +11 -0
- package/src/bitwrench-styles.js +439 -232
- package/src/bitwrench-util-css.js +229 -0
- package/src/bitwrench.js +979 -630
- 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 +139 -29
- package/src/cli/attach.js +555 -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/layout-default.js +47 -32
- package/src/cli/serve.js +6 -2
- 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 -103
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
/*! bitwrench-lean v2.0.
|
|
1
|
+
/*! bitwrench-lean v2.0.18 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
(function (global, factory) {
|
|
3
3
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
4
4
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
5
5
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bw = factory());
|
|
6
6
|
})(this, (function () { 'use strict';
|
|
7
7
|
|
|
8
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
8
9
|
/**
|
|
9
10
|
* Auto-generated version file from package.json
|
|
10
11
|
* DO NOT EDIT DIRECTLY - Use npm run generate-version
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
const VERSION_INFO = {
|
|
14
|
-
version: '2.0.
|
|
15
|
+
version: '2.0.18',
|
|
15
16
|
name: 'bitwrench',
|
|
16
17
|
description: 'A library for javascript UI functions.',
|
|
17
18
|
license: 'BSD-2-Clause',
|
|
18
19
|
homepage: 'https://deftio.github.com/bitwrench/pages',
|
|
19
20
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
20
21
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
21
|
-
buildDate: '2026-03-
|
|
22
|
+
buildDate: '2026-03-17T00:50:09.505Z'
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -312,13 +313,18 @@
|
|
|
312
313
|
*/
|
|
313
314
|
function deriveShades(hex) {
|
|
314
315
|
var rgb = colorParse(hex);
|
|
316
|
+
// For light input colors (L > 75), mixing toward white produces invisible borders.
|
|
317
|
+
// Darken instead so borders remain visible against light backgrounds.
|
|
318
|
+
var borderColor = hexToHsl(hex)[2] > 75
|
|
319
|
+
? adjustLightness(hex, -18)
|
|
320
|
+
: mixColor(hex, '#ffffff', 0.60);
|
|
315
321
|
return {
|
|
316
322
|
base: hex,
|
|
317
323
|
hover: adjustLightness(hex, -10),
|
|
318
324
|
active: adjustLightness(hex, -15),
|
|
319
325
|
light: mixColor(hex, '#ffffff', 0.85),
|
|
320
326
|
darkText: adjustLightness(hex, -40),
|
|
321
|
-
border:
|
|
327
|
+
border: borderColor,
|
|
322
328
|
focus: 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',0.25)',
|
|
323
329
|
textOn: textOnColor(hex)
|
|
324
330
|
};
|
|
@@ -377,19 +383,27 @@
|
|
|
377
383
|
alt.secondary = deriveAlternateSeed(config.secondary);
|
|
378
384
|
alt.tertiary = config.tertiary ? deriveAlternateSeed(config.tertiary) : alt.primary;
|
|
379
385
|
|
|
380
|
-
// Derive alternate surface colors from primary hue
|
|
386
|
+
// Derive alternate surface colors from primary hue.
|
|
387
|
+
// Check actual page surface brightness (not seed color brightness) to decide
|
|
388
|
+
// whether alternate should be dark or light. The page surface is what the
|
|
389
|
+
// user sees; seeds can be dark while the page is still light (default L=96).
|
|
381
390
|
var priHsl = hexToHsl(config.primary);
|
|
382
391
|
var h = priHsl[0];
|
|
383
|
-
var
|
|
392
|
+
var primarySurface = config.surface || hslToHex([h, 8, 96]);
|
|
393
|
+
var isLight = relativeLuminance(primarySurface) > 0.179;
|
|
384
394
|
|
|
385
395
|
if (isLight) {
|
|
386
|
-
//
|
|
396
|
+
// Page surface is light → alternate needs dark surfaces
|
|
387
397
|
alt.light = hslToHex([h, Math.min(priHsl[1], 15), 15]);
|
|
388
398
|
alt.dark = hslToHex([h, 5, 88]);
|
|
399
|
+
alt.surface = hslToHex([h, 12, 18]);
|
|
400
|
+
alt.background = hslToHex([h, 10, 14]);
|
|
389
401
|
} else {
|
|
390
|
-
//
|
|
402
|
+
// Page surface is dark → alternate needs light surfaces
|
|
391
403
|
alt.light = hslToHex([h, Math.min(priHsl[1], 10), 96]);
|
|
392
404
|
alt.dark = hslToHex([h, 10, 18]);
|
|
405
|
+
alt.surface = hslToHex([h, 8, 96]);
|
|
406
|
+
alt.background = hslToHex([h, 6, 98]);
|
|
393
407
|
}
|
|
394
408
|
|
|
395
409
|
// Semantic colors: harmonize toward primary, then invert for alternate
|
|
@@ -437,10 +451,18 @@
|
|
|
437
451
|
var darkBase = config.dark || hslToHex([h, 10, 13]);
|
|
438
452
|
|
|
439
453
|
// Background & surface tokens — tinted with primary hue for theme personality.
|
|
440
|
-
//
|
|
454
|
+
// Saturation high enough that the hue is visible (each theme feels distinct)
|
|
455
|
+
// but low enough to stay neutral and readable.
|
|
441
456
|
// User can override with config.background / config.surface.
|
|
442
|
-
var bgBase = config.background || hslToHex([h,
|
|
443
|
-
var surfBase = config.surface || hslToHex([h,
|
|
457
|
+
var bgBase = config.background || hslToHex([h, 22, 96]);
|
|
458
|
+
var surfBase = config.surface || hslToHex([h, 25, 94]);
|
|
459
|
+
|
|
460
|
+
// surfaceAlt: subtle background variant for striped rows, hover states, headers.
|
|
461
|
+
// Slightly lighter than surface in dark mode, slightly darker in light mode.
|
|
462
|
+
var surfHsl = hexToHsl(surfBase);
|
|
463
|
+
var surfAlt = surfHsl[2] <= 50
|
|
464
|
+
? hslToHex([surfHsl[0], surfHsl[1], Math.min(surfHsl[2] + 8, 100)])
|
|
465
|
+
: hslToHex([surfHsl[0], surfHsl[1], Math.max(surfHsl[2] - 3, 0)]);
|
|
444
466
|
|
|
445
467
|
var palette = {
|
|
446
468
|
primary: deriveShades(config.primary),
|
|
@@ -453,7 +475,8 @@
|
|
|
453
475
|
light: deriveShades(lightBase),
|
|
454
476
|
dark: deriveShades(darkBase),
|
|
455
477
|
background: bgBase,
|
|
456
|
-
surface: surfBase
|
|
478
|
+
surface: surfBase,
|
|
479
|
+
surfaceAlt: surfAlt
|
|
457
480
|
};
|
|
458
481
|
|
|
459
482
|
return palette;
|
|
@@ -500,10 +523,12 @@
|
|
|
500
523
|
5: '1.5rem', // 24px
|
|
501
524
|
6: '2rem'};
|
|
502
525
|
|
|
526
|
+
let _S=SPACING_SCALE;
|
|
527
|
+
|
|
503
528
|
var SPACING_PRESETS = {
|
|
504
|
-
compact: { btn:
|
|
505
|
-
normal: { btn:
|
|
506
|
-
spacious: { btn:
|
|
529
|
+
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] },
|
|
530
|
+
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] },
|
|
531
|
+
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] }
|
|
507
532
|
};
|
|
508
533
|
|
|
509
534
|
var RADIUS_PRESETS = {
|
|
@@ -615,20 +640,14 @@
|
|
|
615
640
|
* Built-in theme presets — named color combinations
|
|
616
641
|
* Each preset provides primary, secondary, and tertiary seed colors.
|
|
617
642
|
*/
|
|
618
|
-
var THEME_PRESETS =
|
|
619
|
-
teal
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
amber: { primary: '#d97706', secondary: '#fbbf24', tertiary: '#f59e0b' },
|
|
627
|
-
emerald: { primary: '#059669', secondary: '#6ee7b7', tertiary: '#34d399' },
|
|
628
|
-
nord: { primary: '#5e81ac', secondary: '#88c0d0', tertiary: '#81a1c1' },
|
|
629
|
-
coral: { primary: '#ef6461', secondary: '#4a7c7e', tertiary: '#e8a87c' },
|
|
630
|
-
midnight: { primary: '#1e3a5f', secondary: '#7c8db5', tertiary: '#3d5a80' }
|
|
631
|
-
};
|
|
643
|
+
var THEME_PRESETS = Object.fromEntries([
|
|
644
|
+
['teal','#006666','#6c757d','#006666'],['ocean','#0077b6','#90e0ef','#00b4d8'],
|
|
645
|
+
['sunset','#e76f51','#264653','#e9c46a'],['forest','#2d6a4f','#95d5b2','#52b788'],
|
|
646
|
+
['slate','#343a40','#adb5bd','#6c757d'],['rose','#e11d48','#fda4af','#fb7185'],
|
|
647
|
+
['indigo','#4f46e5','#a5b4fc','#818cf8'],['amber','#d97706','#fbbf24','#f59e0b'],
|
|
648
|
+
['emerald','#059669','#6ee7b7','#34d399'],['nord','#5e81ac','#88c0d0','#81a1c1'],
|
|
649
|
+
['coral','#ef6461','#4a7c7e','#e8a87c'],['midnight','#1e3a5f','#7c8db5','#3d5a80']
|
|
650
|
+
].map(function(e) { return [e[0], {primary:e[1],secondary:e[2],tertiary:e[3]}]; }));
|
|
632
651
|
|
|
633
652
|
/**
|
|
634
653
|
* Resolve layout config to spacing, radius, typeScale, elevation, and motion objects.
|
|
@@ -675,6 +694,7 @@
|
|
|
675
694
|
if (sel.includes(',')) return sel.split(',').map(function(s) { return '.' + name + ' ' + s.trim(); }).join(', ');
|
|
676
695
|
return '.' + name + ' ' + sel;
|
|
677
696
|
}
|
|
697
|
+
var _sx=scopeSelector;
|
|
678
698
|
|
|
679
699
|
// =========================================================================
|
|
680
700
|
// Themed CSS generators
|
|
@@ -683,12 +703,12 @@
|
|
|
683
703
|
function generateTypographyThemed(scope, palette, layout) {
|
|
684
704
|
var mot = layout.motion;
|
|
685
705
|
var rules = {};
|
|
686
|
-
rules[
|
|
706
|
+
rules[_sx(scope, 'a')] = {
|
|
687
707
|
'color': palette.primary.base,
|
|
688
708
|
'text-decoration': 'none',
|
|
689
709
|
'transition': 'color ' + mot.fast + ' ' + mot.easing
|
|
690
710
|
};
|
|
691
|
-
rules[
|
|
711
|
+
rules[_sx(scope, 'a:hover')] = {
|
|
692
712
|
'color': palette.primary.hover,
|
|
693
713
|
'text-decoration': 'underline'
|
|
694
714
|
};
|
|
@@ -701,11 +721,11 @@
|
|
|
701
721
|
var rd = layout.radius;
|
|
702
722
|
|
|
703
723
|
// Base button (only when scoped — unscoped uses defaultStyles)
|
|
704
|
-
rules[
|
|
724
|
+
rules[_sx(scope, '.bw_btn')] = {
|
|
705
725
|
'padding': sp.btn,
|
|
706
726
|
'border-radius': rd.btn
|
|
707
727
|
};
|
|
708
|
-
rules[
|
|
728
|
+
rules[_sx(scope, '.bw_btn:focus-visible')] = {
|
|
709
729
|
'outline': '2px solid currentColor',
|
|
710
730
|
'outline-offset': '2px',
|
|
711
731
|
'box-shadow': '0 0 0 3px ' + palette.primary.focus
|
|
@@ -714,12 +734,12 @@
|
|
|
714
734
|
// Variant colors handled by palette class on component root
|
|
715
735
|
|
|
716
736
|
// Size variants (structural, reuse layout radius)
|
|
717
|
-
rules[
|
|
737
|
+
rules[_sx(scope, '.bw_btn_lg')] = {
|
|
718
738
|
'padding': '0.625rem 1.5rem',
|
|
719
739
|
'font-size': '1rem',
|
|
720
740
|
'border-radius': rd.btn === '50rem' ? '50rem' : (parseInt(rd.btn) + 2) + 'px'
|
|
721
741
|
};
|
|
722
|
-
rules[
|
|
742
|
+
rules[_sx(scope, '.bw_btn_sm')] = {
|
|
723
743
|
'padding': '0.25rem 0.75rem',
|
|
724
744
|
'font-size': '0.8125rem',
|
|
725
745
|
'border-radius': rd.btn === '50rem' ? '50rem' : (Math.max(parseInt(rd.btn) - 1, 0)) + 'px'
|
|
@@ -733,7 +753,7 @@
|
|
|
733
753
|
var sp = layout.spacing;
|
|
734
754
|
var rd = layout.radius;
|
|
735
755
|
|
|
736
|
-
rules[
|
|
756
|
+
rules[_sx(scope, '.bw_alert')] = {
|
|
737
757
|
'padding': sp.alert,
|
|
738
758
|
'border-radius': rd.alert
|
|
739
759
|
};
|
|
@@ -752,36 +772,36 @@
|
|
|
752
772
|
|
|
753
773
|
var elev = layout.elevation;
|
|
754
774
|
var motion = layout.motion;
|
|
755
|
-
rules[
|
|
775
|
+
rules[_sx(scope, '.bw_card')] = {
|
|
756
776
|
'background-color': palette.surface || '#fff',
|
|
757
777
|
'border': '1px solid ' + palette.light.border,
|
|
758
778
|
'border-radius': rd.card,
|
|
759
779
|
'box-shadow': elev.sm,
|
|
760
780
|
'transition': 'box-shadow ' + motion.normal + ' ' + motion.easing + ', transform ' + motion.normal + ' ' + motion.easing
|
|
761
781
|
};
|
|
762
|
-
rules[
|
|
782
|
+
rules[_sx(scope, '.bw_card:hover')] = {
|
|
763
783
|
'box-shadow': elev.md
|
|
764
784
|
};
|
|
765
|
-
rules[
|
|
785
|
+
rules[_sx(scope, '.bw_card_hoverable:hover')] = {
|
|
766
786
|
'box-shadow': elev.lg
|
|
767
787
|
};
|
|
768
|
-
rules[
|
|
788
|
+
rules[_sx(scope, '.bw_card_body')] = {
|
|
769
789
|
'padding': sp.card
|
|
770
790
|
};
|
|
771
|
-
rules[
|
|
791
|
+
rules[_sx(scope, '.bw_card_header')] = {
|
|
772
792
|
'padding': sp.card.split(' ').map(function(v) { return (parseFloat(v) * 0.7).toFixed(3).replace(/\.?0+$/, '') + 'rem'; }).join(' '),
|
|
773
|
-
'background-color': palette.
|
|
793
|
+
'background-color': palette.surfaceAlt,
|
|
774
794
|
'border-bottom': '1px solid ' + palette.light.border
|
|
775
795
|
};
|
|
776
|
-
rules[
|
|
777
|
-
'background-color': palette.
|
|
796
|
+
rules[_sx(scope, '.bw_card_footer')] = {
|
|
797
|
+
'background-color': palette.surfaceAlt,
|
|
778
798
|
'border-top': '1px solid ' + palette.light.border,
|
|
779
799
|
'color': palette.secondary.base
|
|
780
800
|
};
|
|
781
|
-
rules[
|
|
801
|
+
rules[_sx(scope, '.bw_card_title')] = {
|
|
782
802
|
'color': palette.dark.base
|
|
783
803
|
};
|
|
784
|
-
rules[
|
|
804
|
+
rules[_sx(scope, '.bw_card_subtitle')] = {
|
|
785
805
|
'color': palette.secondary.base
|
|
786
806
|
};
|
|
787
807
|
|
|
@@ -795,55 +815,55 @@
|
|
|
795
815
|
var sp = layout.spacing;
|
|
796
816
|
var rd = layout.radius;
|
|
797
817
|
|
|
798
|
-
rules[
|
|
818
|
+
rules[_sx(scope, '.bw_form_control')] = {
|
|
799
819
|
'padding': sp.input,
|
|
800
820
|
'border-radius': rd.input,
|
|
801
821
|
'color': palette.dark.base,
|
|
802
822
|
'background-color': palette.surface || '#fff',
|
|
803
823
|
'border-color': palette.light.border
|
|
804
824
|
};
|
|
805
|
-
rules[
|
|
825
|
+
rules[_sx(scope, '.bw_form_control:focus')] = {
|
|
806
826
|
'border-color': palette.primary.border,
|
|
807
827
|
'outline': '2px solid ' + palette.primary.base,
|
|
808
828
|
'outline-offset': '-1px',
|
|
809
829
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
810
830
|
};
|
|
811
|
-
rules[
|
|
831
|
+
rules[_sx(scope, '.bw_form_control::placeholder')] = {
|
|
812
832
|
'color': palette.secondary.base
|
|
813
833
|
};
|
|
814
|
-
rules[
|
|
834
|
+
rules[_sx(scope, '.bw_form_label')] = {
|
|
815
835
|
'color': palette.dark.base
|
|
816
836
|
};
|
|
817
|
-
rules[
|
|
837
|
+
rules[_sx(scope, '.bw_form_text')] = {
|
|
818
838
|
'color': palette.secondary.base
|
|
819
839
|
};
|
|
820
|
-
rules[
|
|
840
|
+
rules[_sx(scope, '.bw_form_check_input:checked')] = {
|
|
821
841
|
'background-color': palette.primary.base,
|
|
822
842
|
'border-color': palette.primary.base
|
|
823
843
|
};
|
|
824
|
-
rules[
|
|
844
|
+
rules[_sx(scope, '.bw_form_check_input:focus')] = {
|
|
825
845
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
826
846
|
};
|
|
827
847
|
// Validation states
|
|
828
|
-
rules[
|
|
829
|
-
rules[
|
|
848
|
+
rules[_sx(scope, '.bw_form_control.bw_is_valid')] = { 'border-color': palette.success.base };
|
|
849
|
+
rules[_sx(scope, '.bw_form_control.bw_is_valid:focus')] = {
|
|
830
850
|
'border-color': palette.success.base,
|
|
831
851
|
'box-shadow': '0 0 0 0.2rem ' + palette.success.focus
|
|
832
852
|
};
|
|
833
|
-
rules[
|
|
834
|
-
rules[
|
|
853
|
+
rules[_sx(scope, '.bw_form_control.bw_is_invalid')] = { 'border-color': palette.danger.base };
|
|
854
|
+
rules[_sx(scope, '.bw_form_control.bw_is_invalid:focus')] = {
|
|
835
855
|
'border-color': palette.danger.base,
|
|
836
856
|
'box-shadow': '0 0 0 0.2rem ' + palette.danger.focus
|
|
837
857
|
};
|
|
838
858
|
// Form select
|
|
839
|
-
rules[
|
|
859
|
+
rules[_sx(scope, '.bw_form_select')] = {
|
|
840
860
|
'padding': sp.input,
|
|
841
861
|
'border-radius': rd.input,
|
|
842
862
|
'color': palette.dark.base,
|
|
843
863
|
'background-color': palette.surface || '#fff',
|
|
844
864
|
'border-color': palette.light.border
|
|
845
865
|
};
|
|
846
|
-
rules[
|
|
866
|
+
rules[_sx(scope, '.bw_form_select:focus')] = {
|
|
847
867
|
'border-color': palette.primary.border,
|
|
848
868
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
849
869
|
};
|
|
@@ -851,43 +871,46 @@
|
|
|
851
871
|
return rules;
|
|
852
872
|
}
|
|
853
873
|
|
|
854
|
-
function generateNavigation(scope, palette) {
|
|
874
|
+
function generateNavigation(scope, palette, layout) {
|
|
855
875
|
var rules = {};
|
|
856
|
-
rules[
|
|
857
|
-
'background-color': palette.
|
|
876
|
+
rules[_sx(scope, '.bw_navbar')] = {
|
|
877
|
+
'background-color': palette.surfaceAlt,
|
|
858
878
|
'border-bottom-color': palette.light.border
|
|
859
879
|
};
|
|
860
|
-
rules[
|
|
880
|
+
rules[_sx(scope, '.bw_navbar_brand')] = {
|
|
861
881
|
'color': palette.dark.base
|
|
862
882
|
};
|
|
863
|
-
rules[
|
|
864
|
-
'color': palette.secondary.base
|
|
883
|
+
rules[_sx(scope, '.bw_navbar_nav .bw_nav_link')] = {
|
|
884
|
+
'color': palette.secondary.base,
|
|
885
|
+
'border-radius': layout.radius.btn
|
|
865
886
|
};
|
|
866
|
-
rules[
|
|
867
|
-
'color': palette.dark.base
|
|
887
|
+
rules[_sx(scope, '.bw_navbar_nav .bw_nav_link:hover')] = {
|
|
888
|
+
'color': palette.dark.base,
|
|
889
|
+
'background-color': palette.surfaceAlt
|
|
868
890
|
};
|
|
869
|
-
rules[
|
|
891
|
+
rules[_sx(scope, '.bw_navbar_nav .bw_nav_link.active')] = {
|
|
870
892
|
'color': palette.primary.base,
|
|
871
|
-
'background-color': palette.primary.focus
|
|
893
|
+
'background-color': palette.primary.focus,
|
|
894
|
+
'font-weight': '600'
|
|
872
895
|
};
|
|
873
|
-
rules[
|
|
896
|
+
rules[_sx(scope, '.bw_navbar_dark')] = {
|
|
874
897
|
'background-color': palette.dark.base,
|
|
875
898
|
'border-bottom-color': palette.dark.hover
|
|
876
899
|
};
|
|
877
|
-
rules[
|
|
900
|
+
rules[_sx(scope, '.bw_navbar_dark .bw_navbar_brand')] = {
|
|
878
901
|
'color': palette.light.base
|
|
879
902
|
};
|
|
880
|
-
rules[
|
|
881
|
-
'color':
|
|
903
|
+
rules[_sx(scope, '.bw_navbar_dark .bw_nav_link')] = {
|
|
904
|
+
'color': palette.light.border
|
|
882
905
|
};
|
|
883
|
-
rules[
|
|
884
|
-
'color':
|
|
906
|
+
rules[_sx(scope, '.bw_navbar_dark .bw_nav_link:hover')] = {
|
|
907
|
+
'color': palette.light.base
|
|
885
908
|
};
|
|
886
|
-
rules[
|
|
887
|
-
'color':
|
|
909
|
+
rules[_sx(scope, '.bw_navbar_dark .bw_nav_link.active')] = {
|
|
910
|
+
'color': palette.light.base,
|
|
888
911
|
'font-weight': '600'
|
|
889
912
|
};
|
|
890
|
-
rules[
|
|
913
|
+
rules[_sx(scope, '.bw_nav_pills .bw_nav_link.active')] = {
|
|
891
914
|
'color': palette.primary.textOn,
|
|
892
915
|
'background-color': palette.primary.base
|
|
893
916
|
};
|
|
@@ -898,49 +921,58 @@
|
|
|
898
921
|
var rules = {};
|
|
899
922
|
var sp = layout.spacing;
|
|
900
923
|
|
|
901
|
-
rules[
|
|
924
|
+
rules[_sx(scope, '.bw_table')] = {
|
|
902
925
|
'color': palette.dark.base,
|
|
903
926
|
'border-color': palette.light.border
|
|
904
927
|
};
|
|
905
|
-
rules[
|
|
928
|
+
rules[_sx(scope, '.bw_table > :not(caption) > * > *')] = {
|
|
906
929
|
'padding': sp.cell,
|
|
907
930
|
'border-bottom-color': palette.light.border
|
|
908
931
|
};
|
|
909
|
-
rules[
|
|
932
|
+
rules[_sx(scope, '.bw_table > thead > tr > *')] = {
|
|
910
933
|
'color': palette.secondary.base,
|
|
911
934
|
'border-bottom-color': palette.light.border,
|
|
912
|
-
'background-color': palette.
|
|
935
|
+
'background-color': palette.surfaceAlt
|
|
913
936
|
};
|
|
914
|
-
rules[
|
|
915
|
-
'background-color':
|
|
937
|
+
rules[_sx(scope, '.bw_table_striped > tbody > tr:nth-of-type(odd) > *')] = {
|
|
938
|
+
'background-color': palette.surfaceAlt
|
|
916
939
|
};
|
|
917
|
-
rules[
|
|
940
|
+
rules[_sx(scope, '.bw_table_hover > tbody > tr:hover > *')] = {
|
|
918
941
|
'background-color': palette.primary.focus
|
|
919
942
|
};
|
|
920
|
-
rules[
|
|
943
|
+
rules[_sx(scope, '.bw_table_selectable > tbody > tr')] = {
|
|
944
|
+
'cursor': 'pointer'
|
|
945
|
+
};
|
|
946
|
+
rules[_sx(scope, '.bw_table > tbody > tr.bw_table_row_selected > *')] = {
|
|
947
|
+
'background-color': palette.primary.light
|
|
948
|
+
};
|
|
949
|
+
rules[_sx(scope, '.bw_table_bordered')] = {
|
|
921
950
|
'border-color': palette.light.border
|
|
922
951
|
};
|
|
923
|
-
rules[
|
|
952
|
+
rules[_sx(scope, '.bw_table caption')] = {
|
|
924
953
|
'color': palette.secondary.base
|
|
925
954
|
};
|
|
926
955
|
|
|
927
956
|
return rules;
|
|
928
957
|
}
|
|
929
958
|
|
|
930
|
-
function generateTabs(scope, palette) {
|
|
931
|
-
var rules = {};
|
|
932
|
-
rules[
|
|
959
|
+
function generateTabs(scope, palette, layout) {
|
|
960
|
+
var rules = {}, mo = layout.motion;
|
|
961
|
+
rules[_sx(scope, '.bw_nav_tabs')] = {
|
|
933
962
|
'border-bottom-color': palette.light.border
|
|
934
963
|
};
|
|
935
|
-
rules[
|
|
936
|
-
'color': palette.secondary.base
|
|
964
|
+
rules[_sx(scope, '.bw_nav_link')] = {
|
|
965
|
+
'color': palette.secondary.base,
|
|
966
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', border-color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
937
967
|
};
|
|
938
|
-
rules[
|
|
968
|
+
rules[_sx(scope, '.bw_nav_tabs .bw_nav_link:hover')] = {
|
|
939
969
|
'color': palette.dark.base,
|
|
970
|
+
'background-color': palette.surfaceAlt,
|
|
940
971
|
'border-bottom-color': palette.light.border
|
|
941
972
|
};
|
|
942
|
-
rules[
|
|
973
|
+
rules[_sx(scope, '.bw_nav_tabs .bw_nav_link.active')] = {
|
|
943
974
|
'color': palette.primary.base,
|
|
975
|
+
'background-color': palette.primary.focus,
|
|
944
976
|
'border-bottom': '2px solid ' + palette.primary.base
|
|
945
977
|
};
|
|
946
978
|
return rules;
|
|
@@ -949,23 +981,25 @@
|
|
|
949
981
|
function generateListGroups(scope, palette, layout) {
|
|
950
982
|
var rules = {};
|
|
951
983
|
var sp = layout.spacing;
|
|
984
|
+
var mo = layout.motion;
|
|
952
985
|
|
|
953
|
-
rules[
|
|
986
|
+
rules[_sx(scope, '.bw_list_group_item')] = {
|
|
954
987
|
'padding': sp.cell,
|
|
955
988
|
'color': palette.dark.base,
|
|
956
989
|
'background-color': palette.surface || '#fff',
|
|
957
|
-
'border-color': palette.light.border
|
|
990
|
+
'border-color': palette.light.border,
|
|
991
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
958
992
|
};
|
|
959
|
-
rules[
|
|
960
|
-
'background-color': palette.
|
|
993
|
+
rules[_sx(scope, 'a.bw_list_group_item:hover')] = {
|
|
994
|
+
'background-color': palette.surfaceAlt,
|
|
961
995
|
'color': palette.dark.hover
|
|
962
996
|
};
|
|
963
|
-
rules[
|
|
997
|
+
rules[_sx(scope, '.bw_list_group_item.active')] = {
|
|
964
998
|
'color': palette.primary.textOn,
|
|
965
999
|
'background-color': palette.primary.base,
|
|
966
1000
|
'border-color': palette.primary.base
|
|
967
1001
|
};
|
|
968
|
-
rules[
|
|
1002
|
+
rules[_sx(scope, '.bw_list_group_item.disabled')] = {
|
|
969
1003
|
'color': palette.secondary.base,
|
|
970
1004
|
'background-color': palette.surface || '#fff'
|
|
971
1005
|
};
|
|
@@ -973,28 +1007,37 @@
|
|
|
973
1007
|
return rules;
|
|
974
1008
|
}
|
|
975
1009
|
|
|
976
|
-
function generatePagination(scope, palette) {
|
|
977
|
-
var rules = {};
|
|
978
|
-
rules[
|
|
1010
|
+
function generatePagination(scope, palette, layout) {
|
|
1011
|
+
var rules = {}, mo = layout.motion, rd = layout.radius;
|
|
1012
|
+
rules[_sx(scope, '.bw_page_item:first-child .bw_page_link')] = {
|
|
1013
|
+
'border-top-left-radius': rd.btn,
|
|
1014
|
+
'border-bottom-left-radius': rd.btn
|
|
1015
|
+
};
|
|
1016
|
+
rules[_sx(scope, '.bw_page_item:last-child .bw_page_link')] = {
|
|
1017
|
+
'border-top-right-radius': rd.btn,
|
|
1018
|
+
'border-bottom-right-radius': rd.btn
|
|
1019
|
+
};
|
|
1020
|
+
rules[_sx(scope, '.bw_page_link')] = {
|
|
979
1021
|
'color': palette.primary.base,
|
|
980
1022
|
'background-color': palette.surface || '#fff',
|
|
981
|
-
'border-color': palette.light.border
|
|
1023
|
+
'border-color': palette.light.border,
|
|
1024
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
982
1025
|
};
|
|
983
|
-
rules[
|
|
1026
|
+
rules[_sx(scope, '.bw_page_link:hover')] = {
|
|
984
1027
|
'color': palette.primary.hover,
|
|
985
|
-
'background-color': palette.
|
|
1028
|
+
'background-color': palette.surfaceAlt,
|
|
986
1029
|
'border-color': palette.light.border
|
|
987
1030
|
};
|
|
988
|
-
rules[
|
|
1031
|
+
rules[_sx(scope, '.bw_page_link:focus')] = {
|
|
989
1032
|
'outline': '2px solid ' + palette.primary.base,
|
|
990
1033
|
'outline-offset': '-2px'
|
|
991
1034
|
};
|
|
992
|
-
rules[
|
|
1035
|
+
rules[_sx(scope, '.bw_page_item.bw_active .bw_page_link')] = {
|
|
993
1036
|
'color': palette.primary.textOn,
|
|
994
1037
|
'background-color': palette.primary.base,
|
|
995
1038
|
'border-color': palette.primary.base
|
|
996
1039
|
};
|
|
997
|
-
rules[
|
|
1040
|
+
rules[_sx(scope, '.bw_page_item.bw_disabled .bw_page_link')] = {
|
|
998
1041
|
'color': palette.secondary.base,
|
|
999
1042
|
'background-color': palette.surface || '#fff',
|
|
1000
1043
|
'border-color': palette.light.border
|
|
@@ -1004,12 +1047,12 @@
|
|
|
1004
1047
|
|
|
1005
1048
|
function generateProgress(scope, palette) {
|
|
1006
1049
|
var rules = {};
|
|
1007
|
-
rules[
|
|
1008
|
-
'background-color': palette.
|
|
1050
|
+
rules[_sx(scope, '.bw_progress')] = {
|
|
1051
|
+
'background-color': palette.surfaceAlt,
|
|
1009
1052
|
'box-shadow': 'inset 0 1px 2px rgba(0,0,0,.1)'
|
|
1010
1053
|
};
|
|
1011
|
-
rules[
|
|
1012
|
-
'color':
|
|
1054
|
+
rules[_sx(scope, '.bw_progress_bar')] = {
|
|
1055
|
+
'color': palette.primary.textOn,
|
|
1013
1056
|
'background-color': palette.primary.base,
|
|
1014
1057
|
'box-shadow': 'inset 0 -1px 0 rgba(0,0,0,.15)'
|
|
1015
1058
|
};
|
|
@@ -1028,26 +1071,31 @@
|
|
|
1028
1071
|
'color': palette.dark.base,
|
|
1029
1072
|
'background-color': bg
|
|
1030
1073
|
};
|
|
1031
|
-
rules[
|
|
1032
|
-
// Also apply to the scope element itself so themes work on any container, not just body
|
|
1033
|
-
if (scope) {
|
|
1034
|
-
rules['.' + scope] = baseReset;
|
|
1035
|
-
}
|
|
1074
|
+
rules[_sx(scope, 'body')] = baseReset;
|
|
1036
1075
|
return rules;
|
|
1037
1076
|
}
|
|
1038
1077
|
|
|
1039
|
-
function generateBreadcrumbThemed(scope, palette) {
|
|
1040
|
-
var rules = {};
|
|
1041
|
-
rules[
|
|
1042
|
-
'color': palette.
|
|
1078
|
+
function generateBreadcrumbThemed(scope, palette, layout) {
|
|
1079
|
+
var rules = {}, mo = layout.motion;
|
|
1080
|
+
rules[_sx(scope, '.bw_breadcrumb')] = {
|
|
1081
|
+
'background-color': palette.surfaceAlt,
|
|
1082
|
+
'padding': '0.625rem 1rem',
|
|
1083
|
+
'border-radius': layout.radius.btn
|
|
1043
1084
|
};
|
|
1044
|
-
rules[
|
|
1085
|
+
rules[_sx(scope, '.bw_breadcrumb_item + .bw_breadcrumb_item::before')] = {
|
|
1045
1086
|
'color': palette.secondary.base
|
|
1046
1087
|
};
|
|
1047
|
-
rules[
|
|
1088
|
+
rules[_sx(scope, '.bw_breadcrumb_item a')] = {
|
|
1089
|
+
'color': palette.primary.base,
|
|
1090
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing
|
|
1091
|
+
};
|
|
1092
|
+
rules[_sx(scope, '.bw_breadcrumb_item a:hover')] = {
|
|
1048
1093
|
'color': palette.primary.hover,
|
|
1049
1094
|
'text-decoration': 'underline'
|
|
1050
1095
|
};
|
|
1096
|
+
rules[_sx(scope, '.bw_breadcrumb_item.active')] = {
|
|
1097
|
+
'color': palette.dark.base
|
|
1098
|
+
};
|
|
1051
1099
|
return rules;
|
|
1052
1100
|
}
|
|
1053
1101
|
|
|
@@ -1055,11 +1103,11 @@
|
|
|
1055
1103
|
|
|
1056
1104
|
function generateCloseButtonThemed(scope, palette) {
|
|
1057
1105
|
var rules = {};
|
|
1058
|
-
rules[
|
|
1106
|
+
rules[_sx(scope, '.bw_close')] = {
|
|
1059
1107
|
'color': palette.dark.base,
|
|
1060
1108
|
'opacity': '0.5'
|
|
1061
1109
|
};
|
|
1062
|
-
rules[
|
|
1110
|
+
rules[_sx(scope, '.bw_close:focus')] = {
|
|
1063
1111
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
1064
1112
|
};
|
|
1065
1113
|
return rules;
|
|
@@ -1067,82 +1115,94 @@
|
|
|
1067
1115
|
|
|
1068
1116
|
function generateSectionsThemed(scope, palette) {
|
|
1069
1117
|
var rules = {};
|
|
1070
|
-
rules[
|
|
1118
|
+
rules[_sx(scope, '.bw_section_subtitle')] = {
|
|
1071
1119
|
'color': palette.secondary.base
|
|
1072
1120
|
};
|
|
1073
|
-
rules[
|
|
1121
|
+
rules[_sx(scope, '.bw_feature_description')] = {
|
|
1074
1122
|
'color': palette.secondary.base
|
|
1075
1123
|
};
|
|
1076
|
-
rules[
|
|
1124
|
+
rules[_sx(scope, '.bw_cta_description')] = {
|
|
1077
1125
|
'color': palette.secondary.base
|
|
1078
1126
|
};
|
|
1079
1127
|
return rules;
|
|
1080
1128
|
}
|
|
1081
1129
|
|
|
1082
|
-
function generateAccordionThemed(scope, palette) {
|
|
1130
|
+
function generateAccordionThemed(scope, palette, layout) {
|
|
1083
1131
|
var rules = {};
|
|
1084
|
-
|
|
1132
|
+
var rd = layout ? layout.radius : { card: '8px' };
|
|
1133
|
+
rules[_sx(scope, '.bw_accordion_item')] = {
|
|
1085
1134
|
'background-color': palette.surface || '#fff',
|
|
1086
1135
|
'border-color': palette.light.border
|
|
1087
1136
|
};
|
|
1088
|
-
rules[
|
|
1137
|
+
rules[_sx(scope, '.bw_accordion_item:first-child')] = {
|
|
1138
|
+
'border-top-left-radius': rd.card,
|
|
1139
|
+
'border-top-right-radius': rd.card
|
|
1140
|
+
};
|
|
1141
|
+
rules[_sx(scope, '.bw_accordion_item:last-child')] = {
|
|
1142
|
+
'border-bottom-left-radius': rd.card,
|
|
1143
|
+
'border-bottom-right-radius': rd.card
|
|
1144
|
+
};
|
|
1145
|
+
rules[_sx(scope, '.bw_accordion_button')] = {
|
|
1089
1146
|
'color': palette.dark.base
|
|
1090
1147
|
};
|
|
1091
|
-
rules[
|
|
1148
|
+
rules[_sx(scope, '.bw_accordion_button:not(.bw_collapsed)')] = {
|
|
1092
1149
|
'color': palette.primary.darkText,
|
|
1093
|
-
'background-color': palette.primary.light
|
|
1150
|
+
'background-color': palette.primary.light,
|
|
1151
|
+
'border-left': '3px solid ' + palette.primary.base
|
|
1094
1152
|
};
|
|
1095
|
-
rules[
|
|
1096
|
-
'background-color': palette.
|
|
1153
|
+
rules[_sx(scope, '.bw_accordion_button:hover')] = {
|
|
1154
|
+
'background-color': palette.surfaceAlt
|
|
1097
1155
|
};
|
|
1098
|
-
rules[
|
|
1099
|
-
'background-color': palette.primary.
|
|
1156
|
+
rules[_sx(scope, '.bw_accordion_button:not(.bw_collapsed):hover')] = {
|
|
1157
|
+
'background-color': palette.primary.base,
|
|
1158
|
+
'color': palette.primary.textOn
|
|
1100
1159
|
};
|
|
1101
|
-
rules[
|
|
1160
|
+
rules[_sx(scope, '.bw_accordion_button:focus-visible')] = {
|
|
1102
1161
|
'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
|
|
1103
1162
|
};
|
|
1104
|
-
rules[
|
|
1105
|
-
'border-top': '1px solid ' + palette.light.border
|
|
1163
|
+
rules[_sx(scope, '.bw_accordion_body')] = {
|
|
1164
|
+
'border-top': '1px solid ' + palette.light.border,
|
|
1165
|
+
'background-color': palette.surfaceAlt
|
|
1106
1166
|
};
|
|
1107
1167
|
return rules;
|
|
1108
1168
|
}
|
|
1109
1169
|
|
|
1110
1170
|
function generateCarouselThemed(scope, palette) {
|
|
1111
1171
|
var rules = {};
|
|
1112
|
-
rules[
|
|
1113
|
-
'background-color': palette.
|
|
1172
|
+
rules[_sx(scope, '.bw_carousel')] = {
|
|
1173
|
+
'background-color': palette.surfaceAlt
|
|
1114
1174
|
};
|
|
1115
|
-
rules[
|
|
1175
|
+
rules[_sx(scope, '.bw_carousel_indicator.active')] = {
|
|
1116
1176
|
'background-color': palette.primary.base
|
|
1117
1177
|
};
|
|
1118
|
-
rules[
|
|
1119
|
-
'background-color':
|
|
1120
|
-
'color':
|
|
1178
|
+
rules[_sx(scope, '.bw_carousel_control')] = {
|
|
1179
|
+
'background-color': palette.dark.base,
|
|
1180
|
+
'color': palette.dark.textOn
|
|
1121
1181
|
};
|
|
1122
|
-
rules[
|
|
1123
|
-
'background-color':
|
|
1182
|
+
rules[_sx(scope, '.bw_carousel_control:hover')] = {
|
|
1183
|
+
'background-color': palette.dark.hover
|
|
1124
1184
|
};
|
|
1125
|
-
rules[
|
|
1126
|
-
'background': 'linear-gradient(transparent,
|
|
1127
|
-
'color':
|
|
1185
|
+
rules[_sx(scope, '.bw_carousel_caption')] = {
|
|
1186
|
+
'background': 'linear-gradient(transparent, ' + palette.dark.base + ')',
|
|
1187
|
+
'color': palette.dark.textOn
|
|
1128
1188
|
};
|
|
1129
1189
|
return rules;
|
|
1130
1190
|
}
|
|
1131
1191
|
|
|
1132
1192
|
function generateModalThemed(scope, palette, layout) {
|
|
1133
1193
|
var rules = {};
|
|
1134
|
-
rules[
|
|
1194
|
+
rules[_sx(scope, '.bw_modal_content')] = {
|
|
1135
1195
|
'background-color': palette.surface || '#fff',
|
|
1136
1196
|
'border-color': palette.light.border,
|
|
1137
1197
|
'box-shadow': layout.elevation.lg
|
|
1138
1198
|
};
|
|
1139
|
-
rules[
|
|
1199
|
+
rules[_sx(scope, '.bw_modal_header')] = {
|
|
1140
1200
|
'border-bottom-color': palette.light.border
|
|
1141
1201
|
};
|
|
1142
|
-
rules[
|
|
1202
|
+
rules[_sx(scope, '.bw_modal_footer')] = {
|
|
1143
1203
|
'border-top-color': palette.light.border
|
|
1144
1204
|
};
|
|
1145
|
-
rules[
|
|
1205
|
+
rules[_sx(scope, '.bw_modal_title')] = {
|
|
1146
1206
|
'color': palette.dark.base
|
|
1147
1207
|
};
|
|
1148
1208
|
return rules;
|
|
@@ -1150,13 +1210,13 @@
|
|
|
1150
1210
|
|
|
1151
1211
|
function generateToastThemed(scope, palette, layout) {
|
|
1152
1212
|
var rules = {};
|
|
1153
|
-
rules[
|
|
1213
|
+
rules[_sx(scope, '.bw_toast')] = {
|
|
1154
1214
|
'background-color': palette.surface || '#fff',
|
|
1155
|
-
'border-color':
|
|
1215
|
+
'border-color': palette.light.border,
|
|
1156
1216
|
'box-shadow': layout.elevation.lg
|
|
1157
1217
|
};
|
|
1158
|
-
rules[
|
|
1159
|
-
'border-bottom-color':
|
|
1218
|
+
rules[_sx(scope, '.bw_toast_header')] = {
|
|
1219
|
+
'border-bottom-color': palette.light.border
|
|
1160
1220
|
};
|
|
1161
1221
|
// Variant toast borders handled by palette class
|
|
1162
1222
|
return rules;
|
|
@@ -1164,22 +1224,23 @@
|
|
|
1164
1224
|
|
|
1165
1225
|
function generateDropdownThemed(scope, palette, layout) {
|
|
1166
1226
|
var rules = {};
|
|
1167
|
-
rules[
|
|
1227
|
+
rules[_sx(scope, '.bw_dropdown_menu')] = {
|
|
1168
1228
|
'background-color': palette.surface || '#fff',
|
|
1169
1229
|
'border-color': palette.light.border,
|
|
1170
1230
|
'box-shadow': layout.elevation.md
|
|
1171
1231
|
};
|
|
1172
|
-
rules[
|
|
1173
|
-
'color': palette.dark.base
|
|
1232
|
+
rules[_sx(scope, '.bw_dropdown_item')] = {
|
|
1233
|
+
'color': palette.dark.base,
|
|
1234
|
+
'transition': 'background-color ' + layout.motion.fast + ' ' + layout.motion.easing
|
|
1174
1235
|
};
|
|
1175
|
-
rules[
|
|
1236
|
+
rules[_sx(scope, '.bw_dropdown_item:hover')] = {
|
|
1176
1237
|
'color': palette.dark.hover,
|
|
1177
|
-
'background-color': palette.
|
|
1238
|
+
'background-color': palette.surfaceAlt
|
|
1178
1239
|
};
|
|
1179
|
-
rules[
|
|
1240
|
+
rules[_sx(scope, '.bw_dropdown_item.disabled')] = {
|
|
1180
1241
|
'color': palette.secondary.base
|
|
1181
1242
|
};
|
|
1182
|
-
rules[
|
|
1243
|
+
rules[_sx(scope, '.bw_dropdown_divider')] = {
|
|
1183
1244
|
'border-top-color': palette.light.border
|
|
1184
1245
|
};
|
|
1185
1246
|
return rules;
|
|
@@ -1187,15 +1248,15 @@
|
|
|
1187
1248
|
|
|
1188
1249
|
function generateSwitchThemed(scope, palette) {
|
|
1189
1250
|
var rules = {};
|
|
1190
|
-
rules[
|
|
1251
|
+
rules[_sx(scope, '.bw_form_switch .bw_switch_input')] = {
|
|
1191
1252
|
'background-color': palette.secondary.base,
|
|
1192
1253
|
'border-color': palette.secondary.base
|
|
1193
1254
|
};
|
|
1194
|
-
rules[
|
|
1255
|
+
rules[_sx(scope, '.bw_form_switch .bw_switch_input:checked')] = {
|
|
1195
1256
|
'background-color': palette.primary.base,
|
|
1196
1257
|
'border-color': palette.primary.base
|
|
1197
1258
|
};
|
|
1198
|
-
rules[
|
|
1259
|
+
rules[_sx(scope, '.bw_form_switch .bw_switch_input:focus')] = {
|
|
1199
1260
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
1200
1261
|
};
|
|
1201
1262
|
return rules;
|
|
@@ -1203,88 +1264,102 @@
|
|
|
1203
1264
|
|
|
1204
1265
|
function generateSkeletonThemed(scope, palette) {
|
|
1205
1266
|
var rules = {};
|
|
1206
|
-
rules[
|
|
1207
|
-
'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.
|
|
1267
|
+
rules[_sx(scope, '.bw_skeleton')] = {
|
|
1268
|
+
'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.surfaceAlt + ' 37%, ' + palette.light.border + ' 63%)'
|
|
1208
1269
|
};
|
|
1209
1270
|
return rules;
|
|
1210
1271
|
}
|
|
1211
1272
|
|
|
1212
1273
|
// generateAvatarThemed: removed — palette class on root handles variants
|
|
1213
1274
|
|
|
1214
|
-
function generateStatCardThemed(scope, palette) {
|
|
1215
|
-
var rules = {};
|
|
1275
|
+
function generateStatCardThemed(scope, palette, layout) {
|
|
1276
|
+
var rules = {}, mo = layout.motion, el = layout.elevation, rd = layout.radius;
|
|
1277
|
+
rules[_sx(scope, '.bw_stat_card')] = {
|
|
1278
|
+
'background-color': palette.surface || '#fff',
|
|
1279
|
+
'color': palette.dark.base,
|
|
1280
|
+
'border': '1px solid ' + palette.light.border,
|
|
1281
|
+
'border-radius': rd.card,
|
|
1282
|
+
'box-shadow': el.sm,
|
|
1283
|
+
'transition': 'box-shadow ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
|
|
1284
|
+
};
|
|
1285
|
+
rules[_sx(scope, '.bw_stat_card:hover')] = { 'box-shadow': el.md };
|
|
1216
1286
|
// Variant border colors handled by palette class
|
|
1217
|
-
rules[
|
|
1218
|
-
rules[
|
|
1287
|
+
rules[_sx(scope, '.bw_stat_change_up')] = { 'color': palette.success.base };
|
|
1288
|
+
rules[_sx(scope, '.bw_stat_change_down')] = { 'color': palette.danger.base };
|
|
1219
1289
|
return rules;
|
|
1220
1290
|
}
|
|
1221
1291
|
|
|
1222
1292
|
function generateTimelineThemed(scope, palette) {
|
|
1223
1293
|
var rules = {};
|
|
1224
|
-
rules[
|
|
1294
|
+
rules[_sx(scope, '.bw_timeline::before')] = { 'background-color': palette.light.border };
|
|
1225
1295
|
// Variant marker colors handled by palette class
|
|
1226
|
-
rules[
|
|
1296
|
+
rules[_sx(scope, '.bw_timeline_date')] = { 'color': palette.secondary.base };
|
|
1227
1297
|
return rules;
|
|
1228
1298
|
}
|
|
1229
1299
|
|
|
1230
1300
|
function generateStepperThemed(scope, palette) {
|
|
1231
1301
|
var rules = {};
|
|
1232
|
-
rules[
|
|
1233
|
-
'background-color': palette.
|
|
1302
|
+
rules[_sx(scope, '.bw_step_indicator')] = {
|
|
1303
|
+
'background-color': palette.surfaceAlt,
|
|
1234
1304
|
'border': '2px solid ' + palette.light.border,
|
|
1235
1305
|
'color': palette.secondary.base
|
|
1236
1306
|
};
|
|
1237
|
-
rules[
|
|
1238
|
-
rules[
|
|
1307
|
+
rules[_sx(scope, '.bw_step + .bw_step::before')] = { 'background-color': palette.light.border };
|
|
1308
|
+
rules[_sx(scope, '.bw_step_active .bw_step_indicator')] = {
|
|
1239
1309
|
'background-color': palette.primary.base,
|
|
1240
1310
|
'color': palette.primary.textOn
|
|
1241
1311
|
};
|
|
1242
|
-
rules[
|
|
1312
|
+
rules[_sx(scope, '.bw_step_active .bw_step_label')] = {
|
|
1243
1313
|
'color': palette.dark.base,
|
|
1244
1314
|
'font-weight': '600'
|
|
1245
1315
|
};
|
|
1246
|
-
rules[
|
|
1316
|
+
rules[_sx(scope, '.bw_step_completed .bw_step_indicator')] = {
|
|
1247
1317
|
'background-color': palette.primary.base,
|
|
1248
1318
|
'color': palette.primary.textOn
|
|
1249
1319
|
};
|
|
1250
|
-
rules[
|
|
1251
|
-
rules[
|
|
1320
|
+
rules[_sx(scope, '.bw_step_completed .bw_step_label')] = { 'color': palette.primary.base };
|
|
1321
|
+
rules[_sx(scope, '.bw_step_completed + .bw_step::before')] = { 'background-color': palette.primary.base };
|
|
1252
1322
|
return rules;
|
|
1253
1323
|
}
|
|
1254
1324
|
|
|
1255
1325
|
function generateChipInputThemed(scope, palette) {
|
|
1256
1326
|
var rules = {};
|
|
1257
|
-
rules[
|
|
1258
|
-
|
|
1327
|
+
rules[_sx(scope, '.bw_chip_input')] = {
|
|
1328
|
+
'border-color': palette.light.border,
|
|
1329
|
+
'background-color': palette.surface || '#fff',
|
|
1330
|
+
'color': palette.dark.base
|
|
1331
|
+
};
|
|
1332
|
+
rules[_sx(scope, '.bw_chip_input:focus-within')] = {
|
|
1259
1333
|
'border-color': palette.primary.base,
|
|
1260
1334
|
'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
|
|
1261
1335
|
};
|
|
1262
|
-
rules[
|
|
1263
|
-
'background-color': palette.
|
|
1336
|
+
rules[_sx(scope, '.bw_chip')] = {
|
|
1337
|
+
'background-color': palette.surfaceAlt,
|
|
1264
1338
|
'color': palette.dark.base
|
|
1265
1339
|
};
|
|
1266
|
-
rules[
|
|
1340
|
+
rules[_sx(scope, '.bw_chip_remove:hover')] = {
|
|
1267
1341
|
'color': palette.danger.base,
|
|
1268
1342
|
'background-color': palette.danger.light
|
|
1269
1343
|
};
|
|
1270
1344
|
return rules;
|
|
1271
1345
|
}
|
|
1272
1346
|
|
|
1273
|
-
function generateFileUploadThemed(scope, palette) {
|
|
1274
|
-
var rules = {};
|
|
1275
|
-
rules[
|
|
1347
|
+
function generateFileUploadThemed(scope, palette, layout) {
|
|
1348
|
+
var rules = {}, mo = layout.motion;
|
|
1349
|
+
rules[_sx(scope, '.bw_file_upload')] = {
|
|
1276
1350
|
'border-color': palette.light.border,
|
|
1277
|
-
'background-color': palette.
|
|
1351
|
+
'background-color': palette.surfaceAlt,
|
|
1352
|
+
'transition': 'border-color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
1278
1353
|
};
|
|
1279
|
-
rules[
|
|
1354
|
+
rules[_sx(scope, '.bw_file_upload:hover')] = {
|
|
1280
1355
|
'border-color': palette.primary.base,
|
|
1281
1356
|
'background-color': palette.primary.light
|
|
1282
1357
|
};
|
|
1283
|
-
rules[
|
|
1358
|
+
rules[_sx(scope, '.bw_file_upload:focus')] = {
|
|
1284
1359
|
'outline': '2px solid ' + palette.primary.base,
|
|
1285
1360
|
'outline-offset': '2px'
|
|
1286
1361
|
};
|
|
1287
|
-
rules[
|
|
1362
|
+
rules[_sx(scope, '.bw_file_upload.bw_file_upload_active')] = {
|
|
1288
1363
|
'border-color': palette.primary.base,
|
|
1289
1364
|
'background-color': palette.primary.light,
|
|
1290
1365
|
'border-style': 'solid'
|
|
@@ -1294,35 +1369,73 @@
|
|
|
1294
1369
|
|
|
1295
1370
|
function generateRangeThemed(scope, palette) {
|
|
1296
1371
|
var rules = {};
|
|
1297
|
-
rules[
|
|
1298
|
-
rules[
|
|
1372
|
+
rules[_sx(scope, '.bw_range')] = { 'background-color': palette.light.border };
|
|
1373
|
+
rules[_sx(scope, '.bw_range::-webkit-slider-thumb')] = {
|
|
1299
1374
|
'background-color': palette.primary.base,
|
|
1300
|
-
'border-color': '#fff',
|
|
1375
|
+
'border-color': palette.surface || '#fff',
|
|
1301
1376
|
'box-shadow': '0 1px 3px rgba(0,0,0,0.2)',
|
|
1302
1377
|
'transition': 'background-color 0.15s ease-out, transform 0.15s ease-out'
|
|
1303
1378
|
};
|
|
1304
|
-
rules[
|
|
1379
|
+
rules[_sx(scope, '.bw_range::-moz-range-thumb')] = {
|
|
1305
1380
|
'background-color': palette.primary.base,
|
|
1306
|
-
'border-color': '#fff',
|
|
1381
|
+
'border-color': palette.surface || '#fff',
|
|
1307
1382
|
'box-shadow': '0 1px 3px rgba(0,0,0,0.2)'
|
|
1308
1383
|
};
|
|
1309
1384
|
return rules;
|
|
1310
1385
|
}
|
|
1311
1386
|
|
|
1312
|
-
function
|
|
1313
|
-
var rules = {};
|
|
1314
|
-
rules[
|
|
1387
|
+
function generateTooltipThemed(scope, palette, layout) {
|
|
1388
|
+
var rules = {}, sp = layout.spacing, rd = layout.radius, el = layout.elevation, mo = layout.motion;
|
|
1389
|
+
rules[_sx(scope, '.bw_tooltip')] = {
|
|
1390
|
+
'background-color': palette.dark.base, 'color': palette.dark.textOn,
|
|
1391
|
+
'padding': sp.input, 'border-radius': rd.badge, 'box-shadow': el.md,
|
|
1392
|
+
'transition': 'opacity ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
|
|
1393
|
+
};
|
|
1394
|
+
return rules;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function generatePopoverThemed(scope, palette, layout) {
|
|
1398
|
+
var rules = {}, sp = layout.spacing, rd = layout.radius, el = layout.elevation, mo = layout.motion;
|
|
1399
|
+
rules[_sx(scope, '.bw_popover')] = {
|
|
1400
|
+
'background-color': palette.surface || '#fff', 'color': palette.dark.base,
|
|
1401
|
+
'border': '1px solid ' + palette.light.border, 'border-radius': rd.card, 'box-shadow': el.lg,
|
|
1402
|
+
'transition': 'opacity ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
|
|
1403
|
+
};
|
|
1404
|
+
rules[_sx(scope, '.bw_popover_header')] = {
|
|
1405
|
+
'background-color': palette.surfaceAlt, 'border-bottom': '1px solid ' + palette.light.border,
|
|
1406
|
+
'padding': sp.input
|
|
1407
|
+
};
|
|
1408
|
+
rules[_sx(scope, '.bw_popover_body')] = { 'padding': sp.card };
|
|
1315
1409
|
return rules;
|
|
1316
1410
|
}
|
|
1317
1411
|
|
|
1318
|
-
function
|
|
1412
|
+
function generateSearchThemed(scope, palette, layout) {
|
|
1413
|
+
var rules = {}, mo = layout.motion;
|
|
1414
|
+
rules[_sx(scope, '.bw_search_input')] = {
|
|
1415
|
+
'background-color': palette.surface || '#fff',
|
|
1416
|
+
'color': palette.dark.base
|
|
1417
|
+
};
|
|
1418
|
+
rules[_sx(scope, '.bw_search_clear')] = {
|
|
1419
|
+
'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
|
|
1420
|
+
};
|
|
1421
|
+
rules[_sx(scope, '.bw_search_clear:hover')] = { 'color': palette.dark.base };
|
|
1422
|
+
return rules;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
function generateCodeDemoThemed(scope, palette, layout) {
|
|
1319
1426
|
var rules = {};
|
|
1320
|
-
|
|
1427
|
+
var rd = layout ? layout.radius : { card: '0.375rem' };
|
|
1428
|
+
rules[_sx(scope, '.bw_code_demo')] = {
|
|
1429
|
+
'background-color': palette.surface || '#fff',
|
|
1430
|
+
'color': palette.dark.base,
|
|
1431
|
+
'border-radius': rd.card
|
|
1432
|
+
};
|
|
1433
|
+
rules[_sx(scope, '.bw_code_copy_btn_copied')] = {
|
|
1321
1434
|
'background': palette.success.base,
|
|
1322
1435
|
'color': palette.success.textOn,
|
|
1323
1436
|
'border-color': palette.success.base
|
|
1324
1437
|
};
|
|
1325
|
-
rules[
|
|
1438
|
+
rules[_sx(scope, '.bw_copy_btn:hover')] = {
|
|
1326
1439
|
'background': 'rgba(255,255,255,0.2)',
|
|
1327
1440
|
'color': '#fff'
|
|
1328
1441
|
};
|
|
@@ -1332,7 +1445,7 @@
|
|
|
1332
1445
|
function generateNavPillsThemed(scope, palette, layout) {
|
|
1333
1446
|
var rules = {};
|
|
1334
1447
|
var rd = layout.radius;
|
|
1335
|
-
rules[
|
|
1448
|
+
rules[_sx(scope, '.bw_nav_pills .bw_nav_link')] = { 'border-radius': rd.btn };
|
|
1336
1449
|
return rules;
|
|
1337
1450
|
}
|
|
1338
1451
|
|
|
@@ -1358,21 +1471,21 @@
|
|
|
1358
1471
|
var s = palette[k];
|
|
1359
1472
|
|
|
1360
1473
|
// --- Root palette class: sets default bg/color/border ---
|
|
1361
|
-
rules[
|
|
1474
|
+
rules[_sx(scope, '.bw_' + k)] = {
|
|
1362
1475
|
'background-color': s.base,
|
|
1363
1476
|
'color': s.textOn,
|
|
1364
1477
|
'border-color': s.base
|
|
1365
1478
|
};
|
|
1366
1479
|
|
|
1367
1480
|
// --- Pseudo-states (shared across all components) ---
|
|
1368
|
-
rules[
|
|
1481
|
+
rules[_sx(scope, '.bw_' + k + ':hover')] = {
|
|
1369
1482
|
'background-color': s.hover,
|
|
1370
1483
|
'border-color': s.active
|
|
1371
1484
|
};
|
|
1372
|
-
rules[
|
|
1485
|
+
rules[_sx(scope, '.bw_' + k + ':active')] = {
|
|
1373
1486
|
'background-color': s.active
|
|
1374
1487
|
};
|
|
1375
|
-
rules[
|
|
1488
|
+
rules[_sx(scope, '.bw_' + k + ':focus-visible')] = {
|
|
1376
1489
|
'box-shadow': '0 0 0 3px ' + s.focus,
|
|
1377
1490
|
'outline': 'none'
|
|
1378
1491
|
};
|
|
@@ -1380,70 +1493,99 @@
|
|
|
1380
1493
|
// --- Component-specific overrides ---
|
|
1381
1494
|
|
|
1382
1495
|
// Alerts: light bg, dark text, subtle border
|
|
1383
|
-
rules[
|
|
1496
|
+
rules[_sx(scope, '.bw_alert.bw_' + k)] = {
|
|
1384
1497
|
'background-color': s.light,
|
|
1385
1498
|
'color': s.darkText,
|
|
1386
1499
|
'border-color': s.border
|
|
1387
1500
|
};
|
|
1388
1501
|
|
|
1389
1502
|
// Toast: inherit bg, left border accent
|
|
1390
|
-
rules[
|
|
1503
|
+
rules[_sx(scope, '.bw_toast.bw_' + k)] = {
|
|
1391
1504
|
'background-color': 'inherit',
|
|
1392
1505
|
'color': 'inherit',
|
|
1393
1506
|
'border-left': '4px solid ' + s.base
|
|
1394
1507
|
};
|
|
1395
1508
|
|
|
1396
1509
|
// Stat card: inherit bg, left border accent
|
|
1397
|
-
rules[
|
|
1510
|
+
rules[_sx(scope, '.bw_stat_card.bw_' + k)] = {
|
|
1398
1511
|
'background-color': 'inherit',
|
|
1399
1512
|
'color': 'inherit',
|
|
1400
1513
|
'border-left-color': s.base
|
|
1401
1514
|
};
|
|
1402
1515
|
|
|
1403
1516
|
// Card accent: left border accent, inherit bg
|
|
1404
|
-
rules[
|
|
1517
|
+
rules[_sx(scope, '.bw_card.bw_' + k)] = {
|
|
1405
1518
|
'background-color': 'inherit',
|
|
1406
1519
|
'color': 'inherit',
|
|
1407
1520
|
'border-left': '4px solid ' + s.base
|
|
1408
1521
|
};
|
|
1409
1522
|
|
|
1410
1523
|
// Timeline marker: colored dot
|
|
1411
|
-
rules[
|
|
1524
|
+
rules[_sx(scope, '.bw_timeline_marker.bw_' + k)] = {
|
|
1412
1525
|
'box-shadow': '0 0 0 2px ' + s.base
|
|
1413
1526
|
};
|
|
1414
1527
|
|
|
1415
|
-
// Spinner:
|
|
1416
|
-
|
|
1528
|
+
// Spinner: set color, re-apply border pattern so the root palette class
|
|
1529
|
+
// border-color doesn't fill in the transparent gap that makes it spin.
|
|
1530
|
+
// Also neutralize hover/active which would override border-right-color.
|
|
1531
|
+
rules[_sx(scope, '.bw_spinner_border.bw_' + k)] = {
|
|
1417
1532
|
'background-color': 'transparent',
|
|
1418
1533
|
'color': s.base,
|
|
1419
|
-
'border-color':
|
|
1534
|
+
'border-color': s.base,
|
|
1535
|
+
'border-right-color': 'transparent'
|
|
1536
|
+
};
|
|
1537
|
+
rules[_sx(scope, '.bw_spinner_border.bw_' + k + ':hover')] = {
|
|
1538
|
+
'background-color': 'transparent',
|
|
1539
|
+
'border-color': s.base,
|
|
1540
|
+
'border-right-color': 'transparent'
|
|
1541
|
+
};
|
|
1542
|
+
rules[_sx(scope, '.bw_spinner_grow.bw_' + k)] = {
|
|
1543
|
+
'background-color': s.base,
|
|
1544
|
+
'color': s.base
|
|
1420
1545
|
};
|
|
1421
1546
|
|
|
1422
1547
|
// Outline button: transparent bg, colored border+text, solid on hover
|
|
1423
|
-
rules[
|
|
1548
|
+
rules[_sx(scope, '.bw_btn_outline.bw_' + k)] = {
|
|
1424
1549
|
'background-color': 'transparent',
|
|
1425
1550
|
'color': s.base,
|
|
1426
1551
|
'border-color': s.base
|
|
1427
1552
|
};
|
|
1428
|
-
rules[
|
|
1553
|
+
rules[_sx(scope, '.bw_btn_outline.bw_' + k + ':hover')] = {
|
|
1429
1554
|
'background-color': s.base,
|
|
1430
1555
|
'color': s.textOn
|
|
1431
1556
|
};
|
|
1432
1557
|
|
|
1433
1558
|
// Hero: gradient background
|
|
1434
|
-
rules[
|
|
1559
|
+
rules[_sx(scope, '.bw_hero.bw_' + k)] = {
|
|
1435
1560
|
'background': 'linear-gradient(135deg, ' + s.base + ' 0%, ' + s.hover + ' 100%)',
|
|
1436
1561
|
'color': s.textOn
|
|
1437
1562
|
};
|
|
1438
1563
|
|
|
1439
|
-
// Progress bar:
|
|
1440
|
-
rules[
|
|
1441
|
-
'color':
|
|
1564
|
+
// Progress bar: contrasting text on colored bg
|
|
1565
|
+
rules[_sx(scope, '.bw_progress_bar.bw_' + k)] = {
|
|
1566
|
+
'color': s.textOn
|
|
1567
|
+
};
|
|
1568
|
+
|
|
1569
|
+
// Background utility: .bw_bg_primary, .bw_bg_secondary, etc.
|
|
1570
|
+
rules[_sx(scope, '.bw_bg_' + k)] = {
|
|
1571
|
+
'background-color': s.base,
|
|
1572
|
+
'color': s.textOn
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
// Text color utility: .bw_text_primary, .bw_text_secondary, etc.
|
|
1576
|
+
rules[_sx(scope, '.bw_text_' + k)] = {
|
|
1577
|
+
'color': s.base
|
|
1442
1578
|
};
|
|
1443
1579
|
});
|
|
1444
1580
|
|
|
1445
|
-
// Text muted
|
|
1446
|
-
rules[
|
|
1581
|
+
// Text muted — always a neutral gray, never a brand color
|
|
1582
|
+
rules[_sx(scope, '.bw_text_muted')] = { 'color': '#6c757d' };
|
|
1583
|
+
|
|
1584
|
+
// Common bg/text utilities that aren't per-variant
|
|
1585
|
+
rules[_sx(scope, '.bw_bg_dark')] = { 'background-color': '#212529', 'color': '#f8f9fa' };
|
|
1586
|
+
rules[_sx(scope, '.bw_bg_light')] = { 'background-color': '#f8f9fa', 'color': '#212529' };
|
|
1587
|
+
rules[_sx(scope, '.bw_text_light')] = { 'color': '#f8f9fa' };
|
|
1588
|
+
rules[_sx(scope, '.bw_text_dark')] = { 'color': '#212529' };
|
|
1447
1589
|
|
|
1448
1590
|
return rules;
|
|
1449
1591
|
}
|
|
@@ -1465,30 +1607,32 @@
|
|
|
1465
1607
|
generateAlerts(scopeName, palette, layout),
|
|
1466
1608
|
generateCards(scopeName, palette, layout),
|
|
1467
1609
|
generateForms(scopeName, palette, layout),
|
|
1468
|
-
generateNavigation(scopeName, palette),
|
|
1610
|
+
generateNavigation(scopeName, palette, layout),
|
|
1469
1611
|
generateTables(scopeName, palette, layout),
|
|
1470
|
-
generateTabs(scopeName, palette),
|
|
1612
|
+
generateTabs(scopeName, palette, layout),
|
|
1471
1613
|
generateListGroups(scopeName, palette, layout),
|
|
1472
|
-
generatePagination(scopeName, palette),
|
|
1614
|
+
generatePagination(scopeName, palette, layout),
|
|
1473
1615
|
generateProgress(scopeName, palette),
|
|
1474
|
-
generateBreadcrumbThemed(scopeName, palette),
|
|
1616
|
+
generateBreadcrumbThemed(scopeName, palette, layout),
|
|
1475
1617
|
generateCloseButtonThemed(scopeName, palette),
|
|
1476
1618
|
generateSectionsThemed(scopeName, palette),
|
|
1477
|
-
generateAccordionThemed(scopeName, palette),
|
|
1619
|
+
generateAccordionThemed(scopeName, palette, layout),
|
|
1478
1620
|
generateCarouselThemed(scopeName, palette),
|
|
1479
1621
|
generateModalThemed(scopeName, palette, layout),
|
|
1480
1622
|
generateToastThemed(scopeName, palette, layout),
|
|
1481
1623
|
generateDropdownThemed(scopeName, palette, layout),
|
|
1482
1624
|
generateSwitchThemed(scopeName, palette),
|
|
1483
1625
|
generateSkeletonThemed(scopeName, palette),
|
|
1484
|
-
generateStatCardThemed(scopeName, palette),
|
|
1626
|
+
generateStatCardThemed(scopeName, palette, layout),
|
|
1485
1627
|
generateTimelineThemed(scopeName, palette),
|
|
1486
1628
|
generateStepperThemed(scopeName, palette),
|
|
1487
1629
|
generateChipInputThemed(scopeName, palette),
|
|
1488
|
-
generateFileUploadThemed(scopeName, palette),
|
|
1630
|
+
generateFileUploadThemed(scopeName, palette, layout),
|
|
1489
1631
|
generateRangeThemed(scopeName, palette),
|
|
1490
|
-
generateSearchThemed(scopeName, palette),
|
|
1491
|
-
|
|
1632
|
+
generateSearchThemed(scopeName, palette, layout),
|
|
1633
|
+
generateTooltipThemed(scopeName, palette, layout),
|
|
1634
|
+
generatePopoverThemed(scopeName, palette, layout),
|
|
1635
|
+
generateCodeDemoThemed(scopeName, palette, layout),
|
|
1492
1636
|
generateNavPillsThemed(scopeName, palette, layout),
|
|
1493
1637
|
generatePaletteClasses(scopeName, palette)
|
|
1494
1638
|
);
|
|
@@ -1713,6 +1857,8 @@
|
|
|
1713
1857
|
},
|
|
1714
1858
|
'.bw_table caption': { 'font-size': '0.875rem', 'caption-side': 'bottom' },
|
|
1715
1859
|
'.bw_table_bordered > :not(caption) > * > *': { 'border-width': '1px', 'border-style': 'solid' },
|
|
1860
|
+
'.bw_table_selectable > tbody > tr': { 'cursor': 'pointer' },
|
|
1861
|
+
'.bw_table > tbody > tr.bw_table_row_selected > *': { 'background-color': 'rgba(0, 102, 102, 0.1)' },
|
|
1716
1862
|
'.bw_table_responsive': { 'overflow-x': 'auto', '-webkit-overflow-scrolling': 'touch' }
|
|
1717
1863
|
},
|
|
1718
1864
|
|
|
@@ -1766,6 +1912,7 @@
|
|
|
1766
1912
|
'.bw_nav_tabs .bw_nav_item': { 'margin-bottom': '-2px' },
|
|
1767
1913
|
'.bw_nav_link': {
|
|
1768
1914
|
'display': 'block', 'font-size': '0.875rem', 'font-weight': '500',
|
|
1915
|
+
'padding': '0.625rem 1rem',
|
|
1769
1916
|
'text-decoration': 'none', 'cursor': 'pointer',
|
|
1770
1917
|
'border': 'none', 'background': 'transparent', 'font-family': 'inherit'
|
|
1771
1918
|
},
|
|
@@ -1800,10 +1947,11 @@
|
|
|
1800
1947
|
'.bw_page_item': { 'display': 'list-item', 'list-style': 'none' },
|
|
1801
1948
|
'.bw_page_link': {
|
|
1802
1949
|
'position': 'relative', 'display': 'block', 'padding': '0.375rem 0.75rem',
|
|
1803
|
-
'margin-left': '-1px', 'line-height': '1.25', 'text-decoration': 'none'
|
|
1950
|
+
'margin-left': '-1px', 'line-height': '1.25', 'text-decoration': 'none',
|
|
1951
|
+
'border': '1px solid transparent', 'cursor': 'pointer',
|
|
1952
|
+
'font-family': 'inherit', 'font-size': 'inherit', 'background': 'none'
|
|
1804
1953
|
},
|
|
1805
|
-
'.bw_page_item:first-child .bw_page_link': { 'margin-left': '0'
|
|
1806
|
-
'.bw_page_item:last-child .bw_page_link': { 'border-top-right-radius': '0.375rem', 'border-bottom-right-radius': '0.375rem' },
|
|
1954
|
+
'.bw_page_item:first-child .bw_page_link': { 'margin-left': '0' },
|
|
1807
1955
|
'.bw_page_link:focus-visible': { 'z-index': '3', 'outline': '2px solid currentColor', 'outline-offset': '-2px' }
|
|
1808
1956
|
},
|
|
1809
1957
|
|
|
@@ -1960,6 +2108,7 @@
|
|
|
1960
2108
|
'.bw_accordion_header': { 'margin': '0' },
|
|
1961
2109
|
'.bw_accordion_button': {
|
|
1962
2110
|
'position': 'relative', 'display': 'flex', 'align-items': 'center', 'width': '100%',
|
|
2111
|
+
'padding': '0.875rem 1.25rem',
|
|
1963
2112
|
'font-size': '1rem', 'font-weight': '500', 'text-align': 'left',
|
|
1964
2113
|
'background-color': 'transparent', 'border': '0', 'overflow-anchor': 'none', 'cursor': 'pointer',
|
|
1965
2114
|
'font-family': 'inherit'
|
|
@@ -1971,10 +2120,9 @@
|
|
|
1971
2120
|
'background-repeat': 'no-repeat', 'background-size': '1.25rem'
|
|
1972
2121
|
},
|
|
1973
2122
|
'.bw_accordion_button:not(.bw_collapsed)::after': { 'transform': 'rotate(-180deg)' },
|
|
1974
|
-
'.
|
|
1975
|
-
'.bw_accordion_collapse
|
|
1976
|
-
'.
|
|
1977
|
-
'.bw_accordion_item:last-child': { 'border-bottom-left-radius': '8px', 'border-bottom-right-radius': '8px' }
|
|
2123
|
+
'.bw_accordion_body': { 'padding': '1rem 1.25rem' },
|
|
2124
|
+
'.bw_accordion_collapse': { 'max-height': '0', 'overflow': 'hidden', 'transition': 'max-height 0.3s ease' },
|
|
2125
|
+
'.bw_accordion_collapse.bw_collapse_show': { 'max-height': 'none' }
|
|
1978
2126
|
},
|
|
1979
2127
|
|
|
1980
2128
|
// ---- Carousel ----
|
|
@@ -2120,7 +2268,13 @@
|
|
|
2120
2268
|
|
|
2121
2269
|
// ---- Stat card ----
|
|
2122
2270
|
statCard: {
|
|
2123
|
-
'.bw_stat_card': {
|
|
2271
|
+
'.bw_stat_card': {
|
|
2272
|
+
'padding': '1.25rem',
|
|
2273
|
+
'border-left': '4px solid transparent',
|
|
2274
|
+
'border-radius': '0.375rem',
|
|
2275
|
+
'background-color': 'inherit',
|
|
2276
|
+
'transition': 'transform 0.15s ease'
|
|
2277
|
+
},
|
|
2124
2278
|
'.bw_stat_card:hover': { 'transform': 'translateY(-1px)' },
|
|
2125
2279
|
'.bw_stat_icon': { 'font-size': '1.5rem', 'margin-bottom': '0.5rem' },
|
|
2126
2280
|
'.bw_stat_value': { 'font-size': '2rem', 'font-weight': '700', 'line-height': '1.2' },
|
|
@@ -2483,6 +2637,20 @@
|
|
|
2483
2637
|
rules['.list-inline-item'] = { 'display': 'inline-block' };
|
|
2484
2638
|
rules['.list-inline-item:not(:last-child)'] = { 'margin-right': '.5rem' };
|
|
2485
2639
|
|
|
2640
|
+
// Typography — bw_ prefixed utilities via loops
|
|
2641
|
+
var _imp = function(p, v) { var o = {}; o[p] = v + ' !important'; return o; };
|
|
2642
|
+
[['fs',{'xs':'0.75rem','sm':'0.875rem','base':'1rem','lg':'1.125rem','xl':'1.25rem','2xl':'1.5rem'},'font-size'],
|
|
2643
|
+
['fw',{light:'300',normal:'400',medium:'500',semibold:'600',bold:'700'},'font-weight'],
|
|
2644
|
+
['lh',{tight:'1.25',normal:'1.5',relaxed:'1.75'},'line-height']
|
|
2645
|
+
].forEach(function(d) { for (var dk in d[1]) rules['.bw_'+d[0]+'_'+dk] = _imp(d[2], d[1][dk]); });
|
|
2646
|
+
|
|
2647
|
+
// Flex utilities
|
|
2648
|
+
rules['.bw_flex'] = { 'display': 'flex' };
|
|
2649
|
+
rules['.bw_flex_column'] = { 'flex-direction': 'column' };
|
|
2650
|
+
rules['.bw_flex_wrap'] = { 'flex-wrap': 'wrap' };
|
|
2651
|
+
rules['.bw_flex_center'] = { 'display': 'flex', 'align-items': 'center', 'justify-content': 'center' };
|
|
2652
|
+
for (var gk in spacingValues) rules['.bw_gap_' + gk] = { 'gap': spacingValues[gk] + ' !important' };
|
|
2653
|
+
|
|
2486
2654
|
// Visibility
|
|
2487
2655
|
rules['.bw_visible, .visible'] = { 'visibility': 'visible !important' };
|
|
2488
2656
|
rules['.bw_invisible, .invisible'] = { 'visibility': 'hidden !important' };
|
|
@@ -2543,6 +2711,26 @@
|
|
|
2543
2711
|
return getStructuralCSS();
|
|
2544
2712
|
}
|
|
2545
2713
|
|
|
2714
|
+
/**
|
|
2715
|
+
* Get CSS reset rules only (box-sizing, html/body font, reduced-motion).
|
|
2716
|
+
* Separate from themed/structural rules for independent injection.
|
|
2717
|
+
* @returns {Object} CSS rules object for the reset layer
|
|
2718
|
+
*/
|
|
2719
|
+
function getResetStyles() {
|
|
2720
|
+
var rules = {};
|
|
2721
|
+
Object.assign(rules, structuralRules.base);
|
|
2722
|
+
// Include reduced-motion preference
|
|
2723
|
+
rules['@media (prefers-reduced-motion: reduce)'] = {
|
|
2724
|
+
'*, *::before, *::after': {
|
|
2725
|
+
'animation-duration': '0.01ms !important',
|
|
2726
|
+
'animation-iteration-count': '1 !important',
|
|
2727
|
+
'transition-duration': '0.01ms !important',
|
|
2728
|
+
'scroll-behavior': 'auto !important'
|
|
2729
|
+
}
|
|
2730
|
+
};
|
|
2731
|
+
return rules;
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2546
2734
|
// =========================================================================
|
|
2547
2735
|
// defaultStyles — backward-compatible categorized view
|
|
2548
2736
|
// =========================================================================
|
|
@@ -2572,60 +2760,41 @@
|
|
|
2572
2760
|
});
|
|
2573
2761
|
|
|
2574
2762
|
/**
|
|
2575
|
-
*
|
|
2576
|
-
*
|
|
2577
|
-
*
|
|
2578
|
-
*
|
|
2579
|
-
* @param {
|
|
2580
|
-
*
|
|
2581
|
-
* @
|
|
2582
|
-
* @returns {Object} CSS rules object scoped under .bw_theme_alt (+ optional .name)
|
|
2763
|
+
* Prefix every selector in a rules object with a scope selector.
|
|
2764
|
+
* Handles @media/@keyframes blocks and comma-separated selectors.
|
|
2765
|
+
* @param {Object} rules - CSS rules object
|
|
2766
|
+
* @param {string} prefix - Scope prefix (e.g. '#my-dashboard', '.bw_theme_alt')
|
|
2767
|
+
* @param {boolean} [compound=false] - If true, use compound selector (no space)
|
|
2768
|
+
* for the first segment: `#scope.bw_theme_alt .sel` vs `#scope .sel`
|
|
2769
|
+
* @returns {Object} New rules object with scoped selectors
|
|
2583
2770
|
*/
|
|
2584
|
-
function
|
|
2585
|
-
|
|
2586
|
-
var
|
|
2587
|
-
|
|
2588
|
-
// Re-scope every selector under .bw_theme_alt (+ optional theme name)
|
|
2589
|
-
var altPrefix = name ? '.' + name + '.bw_theme_alt' : '.bw_theme_alt';
|
|
2590
|
-
var altRules = {};
|
|
2591
|
-
|
|
2592
|
-
for (var sel in rawRules) {
|
|
2593
|
-
if (!rawRules.hasOwnProperty(sel)) continue;
|
|
2594
|
-
|
|
2771
|
+
function scopeRulesUnder(rules, prefix, compound) {
|
|
2772
|
+
var scoped = {};
|
|
2773
|
+
for (var sel in rules) {
|
|
2774
|
+
if (!rules.hasOwnProperty(sel)) continue;
|
|
2595
2775
|
if (sel.charAt(0) === '@') {
|
|
2596
2776
|
// @media / @keyframes — recurse into the block
|
|
2597
|
-
var innerBlock =
|
|
2598
|
-
var
|
|
2777
|
+
var innerBlock = rules[sel];
|
|
2778
|
+
var scopedInner = {};
|
|
2599
2779
|
for (var innerSel in innerBlock) {
|
|
2600
2780
|
if (!innerBlock.hasOwnProperty(innerSel)) continue;
|
|
2601
|
-
|
|
2781
|
+
scopedInner[_prefixSelector(innerSel, prefix)] = innerBlock[innerSel];
|
|
2602
2782
|
}
|
|
2603
|
-
|
|
2783
|
+
scoped[sel] = scopedInner;
|
|
2604
2784
|
} else {
|
|
2605
|
-
|
|
2606
|
-
// Handle comma-separated selectors
|
|
2607
|
-
var parts = sel.split(',');
|
|
2608
|
-
var scopedParts = [];
|
|
2609
|
-
for (var i = 0; i < parts.length; i++) {
|
|
2610
|
-
var s = parts[i].trim();
|
|
2611
|
-
// 'body' selector gets special treatment: .bw_theme_alt body
|
|
2612
|
-
if (s === 'body' || s.indexOf('body') === 0) {
|
|
2613
|
-
scopedParts.push(altPrefix + ' ' + s);
|
|
2614
|
-
} else {
|
|
2615
|
-
scopedParts.push(altPrefix + ' ' + s);
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
altRules[scopedParts.join(', ')] = rawRules[sel];
|
|
2785
|
+
scoped[_prefixSelector(sel, prefix)] = rules[sel];
|
|
2619
2786
|
}
|
|
2620
2787
|
}
|
|
2788
|
+
return scoped;
|
|
2789
|
+
}
|
|
2621
2790
|
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
return
|
|
2791
|
+
function _prefixSelector(sel, prefix) {
|
|
2792
|
+
var parts = sel.split(',');
|
|
2793
|
+
var result = [];
|
|
2794
|
+
for (var i = 0; i < parts.length; i++) {
|
|
2795
|
+
result.push(prefix + ' ' + parts[i].trim());
|
|
2796
|
+
}
|
|
2797
|
+
return result.join(', ');
|
|
2629
2798
|
}
|
|
2630
2799
|
|
|
2631
2800
|
/**
|
|
@@ -3349,7 +3518,7 @@
|
|
|
3349
3518
|
__monkey_patch_is_nodejs__: {
|
|
3350
3519
|
_value: 'ignore',
|
|
3351
3520
|
set: function(x) {
|
|
3352
|
-
this._value = (
|
|
3521
|
+
this._value = _is(x, 'boolean') ? x : 'ignore';
|
|
3353
3522
|
},
|
|
3354
3523
|
get: function() {
|
|
3355
3524
|
return this._value;
|
|
@@ -3397,6 +3566,67 @@
|
|
|
3397
3566
|
configurable: true
|
|
3398
3567
|
});
|
|
3399
3568
|
|
|
3569
|
+
// ── Internal aliases ─────────────────────────────────────────────────────
|
|
3570
|
+
// Short names for frequently-used builtins and internal methods.
|
|
3571
|
+
// Same pattern as v1 (_to = bw.typeOf, etc.).
|
|
3572
|
+
//
|
|
3573
|
+
// Why: Terser can't shorten global property chains (console.warn,
|
|
3574
|
+
// Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
|
|
3575
|
+
// because it can't prove they're side-effect-free. We can, so we alias
|
|
3576
|
+
// them here. Each alias saves bytes in the minified output, and the short
|
|
3577
|
+
// names also reduce visual noise in the hot paths (binding pipeline,
|
|
3578
|
+
// createDOM, etc.).
|
|
3579
|
+
//
|
|
3580
|
+
// Alias Target Sites
|
|
3581
|
+
// ───────── ────────────────────────────────────── ─────
|
|
3582
|
+
// _hop Object.prototype.hasOwnProperty 15
|
|
3583
|
+
// _isA Array.isArray 25
|
|
3584
|
+
// _keys Object.keys 7
|
|
3585
|
+
// _to bw.typeOf (type string) 26
|
|
3586
|
+
// _is type check boolean: _is(x,'string') ~50
|
|
3587
|
+
// _cw console.warn 8
|
|
3588
|
+
// _cl console.log 11
|
|
3589
|
+
// _ce console.error 4
|
|
3590
|
+
// _chp ComponentHandle.prototype 28 (defined after constructor)
|
|
3591
|
+
//
|
|
3592
|
+
// Note: document.createElement etc. are NOT aliased because they require
|
|
3593
|
+
// `this === document` and .bind() would add overhead on every call.
|
|
3594
|
+
// Console aliases use thin wrappers (not direct refs) so test monkey-
|
|
3595
|
+
// patching of console.warn/log/error continues to work.
|
|
3596
|
+
//
|
|
3597
|
+
// `typeof x` for UNDECLARED globals (window, document, process, require,
|
|
3598
|
+
// EventSource, navigator, Promise, __filename, import.meta) MUST stay as
|
|
3599
|
+
// raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
|
|
3600
|
+
//
|
|
3601
|
+
// ── v1 functional type helpers (kept for reference, not currently used) ──
|
|
3602
|
+
// _toa(x, type, trueVal, falseVal) — bw.typeAssign:
|
|
3603
|
+
// returns trueVal if _to(x)===type, else falseVal.
|
|
3604
|
+
// Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
|
|
3605
|
+
// _toc(x, type, trueVal, falseVal) — bw.typeConvert:
|
|
3606
|
+
// same as _toa but if trueVal/falseVal are functions, calls them with x.
|
|
3607
|
+
// Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
|
|
3608
|
+
// Uncomment if pattern frequency justifies them:
|
|
3609
|
+
// var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
|
|
3610
|
+
// var _toc = function(x, t, y, n) { var r = _to(x)===t; return r ? (_to(y)==='function'?y(x):y) : (_to(n)==='function'?n(x):n); };
|
|
3611
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
3612
|
+
var _hop = Object.prototype.hasOwnProperty;
|
|
3613
|
+
var _isA = Array.isArray;
|
|
3614
|
+
var _keys = Object.keys;
|
|
3615
|
+
var _to = typeOf; // imported from bitwrench-utils.js
|
|
3616
|
+
var _is = function(x, t) { var r = _to(x); return r === t || r.toLowerCase() === t; };
|
|
3617
|
+
// Console aliases use thin wrappers (not direct references) so that test
|
|
3618
|
+
// code can monkey-patch console.warn/log/error and the patches take effect.
|
|
3619
|
+
var _cw = function() { console.warn.apply(console, arguments); };
|
|
3620
|
+
var _cl = function() { console.log.apply(console, arguments); };
|
|
3621
|
+
var _ce = function() { console.error.apply(console, arguments); };
|
|
3622
|
+
|
|
3623
|
+
/**
|
|
3624
|
+
* Debug flag. When true, emits console.warn for silent binding failures
|
|
3625
|
+
* (missing paths, null refs, auto-created intermediate objects).
|
|
3626
|
+
* @type {boolean}
|
|
3627
|
+
*/
|
|
3628
|
+
bw.debug = false;
|
|
3629
|
+
|
|
3400
3630
|
/**
|
|
3401
3631
|
* Lazy-resolve Node.js `fs` module.
|
|
3402
3632
|
* Tries require('fs') first (available in CJS/UMD Node.js builds),
|
|
@@ -3544,7 +3774,7 @@
|
|
|
3544
3774
|
*/
|
|
3545
3775
|
bw._el = function(id) {
|
|
3546
3776
|
// Pass-through for DOM elements
|
|
3547
|
-
if (
|
|
3777
|
+
if (!_is(id, 'string')) return id || null;
|
|
3548
3778
|
if (!id) return null;
|
|
3549
3779
|
if (!bw._isBrowser) return null;
|
|
3550
3780
|
|
|
@@ -3572,7 +3802,12 @@
|
|
|
3572
3802
|
el = document.querySelector('[data-bw_id="' + id + '"]');
|
|
3573
3803
|
}
|
|
3574
3804
|
|
|
3575
|
-
// 5.
|
|
3805
|
+
// 5. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
|
|
3806
|
+
if (!el && id.indexOf('bw_uuid_') === 0) {
|
|
3807
|
+
el = document.querySelector('.' + id);
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
// 6. Cache the result for next time
|
|
3576
3811
|
if (el) {
|
|
3577
3812
|
bw._nodeMap[id] = el;
|
|
3578
3813
|
}
|
|
@@ -3625,6 +3860,84 @@
|
|
|
3625
3860
|
}
|
|
3626
3861
|
};
|
|
3627
3862
|
|
|
3863
|
+
// ===================================================================================
|
|
3864
|
+
// bw.assignUUID() / bw.getUUID() — Explicit UUID addressing for TACO objects
|
|
3865
|
+
// ===================================================================================
|
|
3866
|
+
|
|
3867
|
+
/**
|
|
3868
|
+
* Regex to match a bw_uuid_* token in a class string.
|
|
3869
|
+
* @private
|
|
3870
|
+
*/
|
|
3871
|
+
var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
|
|
3872
|
+
|
|
3873
|
+
/**
|
|
3874
|
+
* Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
|
|
3875
|
+
*
|
|
3876
|
+
* Idempotent by default — calling twice returns the same UUID. Pass `forceNew=true`
|
|
3877
|
+
* to replace an existing UUID (useful in loops where each TACO needs a unique ID).
|
|
3878
|
+
*
|
|
3879
|
+
* @param {Object} taco - A TACO object `{t, a, c, o}`
|
|
3880
|
+
* @param {boolean} [forceNew=false] - If true, replaces any existing UUID with a new one
|
|
3881
|
+
* @returns {string} The UUID string (e.g. 'bw_uuid_a1b2c3d4e5')
|
|
3882
|
+
* @category Identifiers
|
|
3883
|
+
* @example
|
|
3884
|
+
* var card = bw.makeStatCard({ value: '0', label: 'Scans' });
|
|
3885
|
+
* var uuid = bw.assignUUID(card); // 'bw_uuid_a1b2c3d4e5'
|
|
3886
|
+
* var same = bw.assignUUID(card); // same UUID (idempotent)
|
|
3887
|
+
* var diff = bw.assignUUID(card, true); // new UUID (forced)
|
|
3888
|
+
*/
|
|
3889
|
+
bw.assignUUID = function(taco, forceNew) {
|
|
3890
|
+
if (!taco || !_is(taco, 'object')) return null;
|
|
3891
|
+
|
|
3892
|
+
// Ensure taco.a exists
|
|
3893
|
+
if (!taco.a) taco.a = {};
|
|
3894
|
+
if (!_is(taco.a.class, 'string')) taco.a.class = taco.a.class ? String(taco.a.class) : '';
|
|
3895
|
+
|
|
3896
|
+
var existing = taco.a.class.match(_UUID_RE);
|
|
3897
|
+
|
|
3898
|
+
if (existing && !forceNew) {
|
|
3899
|
+
return existing[0];
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
// Remove old UUID if forceNew
|
|
3903
|
+
if (existing) {
|
|
3904
|
+
taco.a.class = taco.a.class.replace(_UUID_RE, '').replace(/\s+/g, ' ').trim();
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
var uuid = bw.uuid('uuid');
|
|
3908
|
+
taco.a.class = (taco.a.class ? taco.a.class + ' ' : '') + uuid;
|
|
3909
|
+
return uuid;
|
|
3910
|
+
};
|
|
3911
|
+
|
|
3912
|
+
/**
|
|
3913
|
+
* Read the UUID from a TACO object or DOM element. Pure getter, no side effects.
|
|
3914
|
+
*
|
|
3915
|
+
* @param {Object|Element} tacoOrElement - A TACO object or DOM element
|
|
3916
|
+
* @returns {string|null} The UUID string, or null if none assigned
|
|
3917
|
+
* @category Identifiers
|
|
3918
|
+
* @example
|
|
3919
|
+
* bw.getUUID(card) // 'bw_uuid_a1b2c3d4e5' (from TACO)
|
|
3920
|
+
* bw.getUUID(domEl) // 'bw_uuid_a1b2c3d4e5' (from DOM element)
|
|
3921
|
+
* bw.getUUID({t:'div'}) // null (no UUID)
|
|
3922
|
+
*/
|
|
3923
|
+
bw.getUUID = function(tacoOrElement) {
|
|
3924
|
+
if (!tacoOrElement) return null;
|
|
3925
|
+
|
|
3926
|
+
var classStr;
|
|
3927
|
+
// DOM element: check className
|
|
3928
|
+
if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
|
|
3929
|
+
classStr = tacoOrElement.className;
|
|
3930
|
+
}
|
|
3931
|
+
// TACO object: check a.class
|
|
3932
|
+
else if (tacoOrElement.a && _is(tacoOrElement.a.class, 'string')) {
|
|
3933
|
+
classStr = tacoOrElement.a.class;
|
|
3934
|
+
}
|
|
3935
|
+
|
|
3936
|
+
if (!classStr) return null;
|
|
3937
|
+
var match = classStr.match(_UUID_RE);
|
|
3938
|
+
return match ? match[0] : null;
|
|
3939
|
+
};
|
|
3940
|
+
|
|
3628
3941
|
/**
|
|
3629
3942
|
* Escape HTML special characters to prevent XSS.
|
|
3630
3943
|
*
|
|
@@ -3640,7 +3953,7 @@
|
|
|
3640
3953
|
* // => '<b>Hello</b> & "world"'
|
|
3641
3954
|
*/
|
|
3642
3955
|
bw.escapeHTML = function(str) {
|
|
3643
|
-
if (
|
|
3956
|
+
if (!_is(str, 'string')) return '';
|
|
3644
3957
|
|
|
3645
3958
|
const escapeMap = {
|
|
3646
3959
|
'&': '&',
|
|
@@ -3674,6 +3987,42 @@
|
|
|
3674
3987
|
return { __bw_raw: true, v: String(str) };
|
|
3675
3988
|
};
|
|
3676
3989
|
|
|
3990
|
+
/**
|
|
3991
|
+
* Hyperscript-style TACO constructor.
|
|
3992
|
+
*
|
|
3993
|
+
* A convenience helper that returns a canonical TACO object from positional
|
|
3994
|
+
* arguments. The return value is a plain object — serializable, works with
|
|
3995
|
+
* bwserve, and accepted everywhere TACO is accepted.
|
|
3996
|
+
*
|
|
3997
|
+
* @param {string} tag - HTML tag name (e.g. 'div', 'p', 'section')
|
|
3998
|
+
* @param {Object|null} [attrs] - HTML attributes object. Pass null or omit to skip.
|
|
3999
|
+
* @param {*} [content] - Content: string, number, TACO object, or array of children.
|
|
4000
|
+
* @param {Object} [options] - TACO options (state, lifecycle hooks, render fn).
|
|
4001
|
+
* @returns {Object} Plain TACO object {t, a?, c?, o?}
|
|
4002
|
+
* @category Utilities
|
|
4003
|
+
* @see bw.html
|
|
4004
|
+
* @see bw.createDOM
|
|
4005
|
+
* @see bw.DOM
|
|
4006
|
+
* @example
|
|
4007
|
+
* bw.h('div')
|
|
4008
|
+
* // => { t: 'div' }
|
|
4009
|
+
*
|
|
4010
|
+
* bw.h('p', { class: 'bw_text_muted' }, 'Hello')
|
|
4011
|
+
* // => { t: 'p', a: { class: 'bw_text_muted' }, c: 'Hello' }
|
|
4012
|
+
*
|
|
4013
|
+
* bw.h('ul', null, [
|
|
4014
|
+
* bw.h('li', null, 'one'),
|
|
4015
|
+
* bw.h('li', null, 'two')
|
|
4016
|
+
* ])
|
|
4017
|
+
* // => { t: 'ul', c: [{ t: 'li', c: 'one' }, { t: 'li', c: 'two' }] }
|
|
4018
|
+
*/
|
|
4019
|
+
bw.h = function(tag, attrs, content, options) {
|
|
4020
|
+
var taco = { t: String(tag) };
|
|
4021
|
+
if (attrs !== null && attrs !== undefined) taco.a = attrs;
|
|
4022
|
+
if (content !== undefined) taco.c = content;
|
|
4023
|
+
if (options !== undefined) taco.o = options;
|
|
4024
|
+
return taco;
|
|
4025
|
+
};
|
|
3677
4026
|
|
|
3678
4027
|
/**
|
|
3679
4028
|
* Convert a TACO object (or array of TACOs) to an HTML string.
|
|
@@ -3713,7 +4062,7 @@
|
|
|
3713
4062
|
}
|
|
3714
4063
|
|
|
3715
4064
|
// Handle arrays of TACOs
|
|
3716
|
-
if (
|
|
4065
|
+
if (_isA(taco)) {
|
|
3717
4066
|
return taco.map(t => bw.html(t, options)).join('');
|
|
3718
4067
|
}
|
|
3719
4068
|
|
|
@@ -3736,15 +4085,15 @@
|
|
|
3736
4085
|
if (taco && taco._bwEach && options.state) {
|
|
3737
4086
|
var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
|
|
3738
4087
|
var arr = bw._evaluatePath(options.state, eachExpr);
|
|
3739
|
-
if (!
|
|
4088
|
+
if (!_isA(arr)) return '';
|
|
3740
4089
|
return arr.map(function(item, idx) { return bw.html(taco.factory(item, idx), options); }).join('');
|
|
3741
4090
|
}
|
|
3742
4091
|
|
|
3743
4092
|
// Handle primitives and non-TACO objects
|
|
3744
|
-
if (
|
|
4093
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
3745
4094
|
var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
|
|
3746
4095
|
// Resolve template bindings if state provided
|
|
3747
|
-
if (options.state &&
|
|
4096
|
+
if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
|
|
3748
4097
|
str = bw._resolveTemplate(str, options.state, !!options.compile);
|
|
3749
4098
|
}
|
|
3750
4099
|
return str;
|
|
@@ -3764,10 +4113,18 @@
|
|
|
3764
4113
|
// Skip null, undefined, false
|
|
3765
4114
|
if (value == null || value === false) continue;
|
|
3766
4115
|
|
|
3767
|
-
//
|
|
3768
|
-
if (key.startsWith('on'))
|
|
4116
|
+
// Serialize event handlers via funcRegister
|
|
4117
|
+
if (key.startsWith('on')) {
|
|
4118
|
+
if (_is(value, 'function')) {
|
|
4119
|
+
var fnId = bw.funcRegister(value);
|
|
4120
|
+
attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
|
|
4121
|
+
} else if (_is(value, 'string')) {
|
|
4122
|
+
attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
|
|
4123
|
+
}
|
|
4124
|
+
continue;
|
|
4125
|
+
}
|
|
3769
4126
|
|
|
3770
|
-
if (key === 'style' &&
|
|
4127
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
3771
4128
|
// Convert style object to string
|
|
3772
4129
|
const styleStr = Object.entries(value)
|
|
3773
4130
|
.filter(([, v]) => v != null)
|
|
@@ -3778,7 +4135,7 @@
|
|
|
3778
4135
|
}
|
|
3779
4136
|
} else if (key === 'class') {
|
|
3780
4137
|
// Handle class as array or string
|
|
3781
|
-
const classStr =
|
|
4138
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
3782
4139
|
if (classStr) {
|
|
3783
4140
|
attrStr += ` class="${bw.escapeHTML(classStr)}"`;
|
|
3784
4141
|
}
|
|
@@ -3814,13 +4171,184 @@
|
|
|
3814
4171
|
// Process content recursively
|
|
3815
4172
|
let contentStr = content != null ? bw.html(content, options) : '';
|
|
3816
4173
|
// Resolve template bindings in content if state provided
|
|
3817
|
-
if (options.state &&
|
|
4174
|
+
if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
|
|
3818
4175
|
contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
|
|
3819
4176
|
}
|
|
3820
4177
|
|
|
3821
4178
|
return `<${tag}${attrStr}>${contentStr}</${tag}>`;
|
|
3822
4179
|
};
|
|
3823
4180
|
|
|
4181
|
+
/**
|
|
4182
|
+
* Generate a complete, self-contained HTML document from TACO content.
|
|
4183
|
+
*
|
|
4184
|
+
* Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
|
|
4185
|
+
* func registry emission (so serialized event handlers work), optional theme,
|
|
4186
|
+
* and extra head elements. Designed for static site generation, offline/airgapped
|
|
4187
|
+
* use, and the "static site that isn't static" workflow.
|
|
4188
|
+
*
|
|
4189
|
+
* @param {Object} [opts={}] - Page options
|
|
4190
|
+
* @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
|
|
4191
|
+
* @param {string} [opts.title='bitwrench'] - Page title
|
|
4192
|
+
* @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
|
|
4193
|
+
* @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
|
|
4194
|
+
* @param {string} [opts.css=''] - Additional CSS for <style> block
|
|
4195
|
+
* @param {string|Object} [opts.theme=null] - Theme preset name or config object
|
|
4196
|
+
* @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
|
|
4197
|
+
* @param {string} [opts.favicon=''] - Favicon URL
|
|
4198
|
+
* @param {string} [opts.lang='en'] - HTML lang attribute
|
|
4199
|
+
* @returns {string} Complete HTML document string
|
|
4200
|
+
* @category DOM Generation
|
|
4201
|
+
* @see bw.html
|
|
4202
|
+
* @example
|
|
4203
|
+
* bw.htmlPage({
|
|
4204
|
+
* title: 'My App',
|
|
4205
|
+
* body: { t: 'h1', c: 'Hello World' },
|
|
4206
|
+
* runtime: 'shim'
|
|
4207
|
+
* })
|
|
4208
|
+
*/
|
|
4209
|
+
bw.htmlPage = function(opts) {
|
|
4210
|
+
opts = opts || {};
|
|
4211
|
+
var title = opts.title || 'bitwrench';
|
|
4212
|
+
var body = opts.body || '';
|
|
4213
|
+
var state = opts.state || undefined;
|
|
4214
|
+
var runtime = opts.runtime || 'shim';
|
|
4215
|
+
var css = opts.css || '';
|
|
4216
|
+
var theme = opts.theme || null;
|
|
4217
|
+
var headExtra = opts.head || [];
|
|
4218
|
+
var favicon = opts.favicon || '';
|
|
4219
|
+
var lang = opts.lang || 'en';
|
|
4220
|
+
|
|
4221
|
+
// Snapshot funcRegistry counter before rendering
|
|
4222
|
+
var fnCounterBefore = bw._fnIDCounter;
|
|
4223
|
+
|
|
4224
|
+
// Render body content
|
|
4225
|
+
var bodyHTML = '';
|
|
4226
|
+
if (_is(body, 'string')) {
|
|
4227
|
+
bodyHTML = body;
|
|
4228
|
+
} else {
|
|
4229
|
+
var htmlOpts = {};
|
|
4230
|
+
if (state) htmlOpts.state = state;
|
|
4231
|
+
bodyHTML = bw.html(body, htmlOpts);
|
|
4232
|
+
}
|
|
4233
|
+
|
|
4234
|
+
// Collect functions registered during this render
|
|
4235
|
+
var fnCounterAfter = bw._fnIDCounter;
|
|
4236
|
+
var registryEntries = '';
|
|
4237
|
+
for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
|
|
4238
|
+
var fnKey = 'bw_fn_' + i;
|
|
4239
|
+
if (bw._fnRegistry[fnKey]) {
|
|
4240
|
+
registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' +
|
|
4241
|
+
bw._fnRegistry[fnKey].toString() + ';\n';
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
4244
|
+
|
|
4245
|
+
// Build runtime script for <head>
|
|
4246
|
+
var runtimeHead = '';
|
|
4247
|
+
if (runtime === 'inline') {
|
|
4248
|
+
// Read UMD bundle synchronously if in Node.js
|
|
4249
|
+
var umdSource = null;
|
|
4250
|
+
if (bw._isNode) {
|
|
4251
|
+
try {
|
|
4252
|
+
var fs = (typeof require === 'function') ? require('fs') : null;
|
|
4253
|
+
var pathMod = (typeof require === 'function') ? require('path') : null;
|
|
4254
|
+
if (fs && pathMod) {
|
|
4255
|
+
// Resolve dist/ relative to this source file
|
|
4256
|
+
var srcDir = '';
|
|
4257
|
+
try { srcDir = pathMod.dirname((typeof __filename !== 'undefined') ? __filename : ''); }
|
|
4258
|
+
catch(e2) { /* ESM: __filename not available */ }
|
|
4259
|
+
if (!srcDir && typeof ({ url: (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench-lean.umd.js', document.baseURI).href)) }) !== 'undefined' && (typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench-lean.umd.js', document.baseURI).href))) {
|
|
4260
|
+
var url = (typeof require === 'function') ? require('url') : null;
|
|
4261
|
+
if (url && url.fileURLToPath) srcDir = pathMod.dirname(url.fileURLToPath((typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bitwrench-lean.umd.js', document.baseURI).href))));
|
|
4262
|
+
}
|
|
4263
|
+
if (srcDir) {
|
|
4264
|
+
var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
|
|
4265
|
+
umdSource = fs.readFileSync(distPath, 'utf8');
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
} catch(e) { /* fall through */ }
|
|
4269
|
+
}
|
|
4270
|
+
if (umdSource) {
|
|
4271
|
+
runtimeHead = '<script>' + umdSource + '</script>';
|
|
4272
|
+
} else {
|
|
4273
|
+
// Fallback to shim in browser or if dist not available
|
|
4274
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
4275
|
+
}
|
|
4276
|
+
} else if (runtime === 'cdn') {
|
|
4277
|
+
runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
|
|
4278
|
+
} else if (runtime === 'shim') {
|
|
4279
|
+
runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
|
|
4280
|
+
}
|
|
4281
|
+
// runtime === 'none' → empty
|
|
4282
|
+
|
|
4283
|
+
// Theme CSS
|
|
4284
|
+
var themeCSS = '';
|
|
4285
|
+
if (theme) {
|
|
4286
|
+
var themeConfig = _is(theme, 'string')
|
|
4287
|
+
? (THEME_PRESETS[theme.toLowerCase()] || null)
|
|
4288
|
+
: theme;
|
|
4289
|
+
if (themeConfig) {
|
|
4290
|
+
var themeResult = bw.makeStyles(themeConfig);
|
|
4291
|
+
themeCSS = themeResult.css;
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
|
|
4295
|
+
// Extra <head> elements
|
|
4296
|
+
var headHTML = '';
|
|
4297
|
+
if (_isA(headExtra) && headExtra.length > 0) {
|
|
4298
|
+
headHTML = headExtra.map(function(el) { return bw.html(el); }).join('\n');
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
// Favicon
|
|
4302
|
+
var faviconTag = '';
|
|
4303
|
+
if (favicon) {
|
|
4304
|
+
var safeFavicon = favicon.replace(/[&<>"']/g, function(c) {
|
|
4305
|
+
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c];
|
|
4306
|
+
});
|
|
4307
|
+
faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
|
|
4308
|
+
}
|
|
4309
|
+
|
|
4310
|
+
// Escaped title
|
|
4311
|
+
var safeTitle = bw.escapeHTML(title);
|
|
4312
|
+
|
|
4313
|
+
// Combine all CSS
|
|
4314
|
+
var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
|
|
4315
|
+
|
|
4316
|
+
// Body-end script: registry entries + optional loadStyles
|
|
4317
|
+
var bodyEndScript = '';
|
|
4318
|
+
var bodyEndParts = [];
|
|
4319
|
+
if (registryEntries) {
|
|
4320
|
+
bodyEndParts.push(registryEntries);
|
|
4321
|
+
}
|
|
4322
|
+
if (runtime === 'inline' || runtime === 'cdn') {
|
|
4323
|
+
bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadStyles();}');
|
|
4324
|
+
}
|
|
4325
|
+
if (bodyEndParts.length > 0) {
|
|
4326
|
+
bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
|
|
4327
|
+
}
|
|
4328
|
+
|
|
4329
|
+
// Assemble document
|
|
4330
|
+
var parts = [
|
|
4331
|
+
'<!DOCTYPE html>',
|
|
4332
|
+
'<html lang="' + lang + '">',
|
|
4333
|
+
'<head>',
|
|
4334
|
+
'<meta charset="UTF-8">',
|
|
4335
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
4336
|
+
];
|
|
4337
|
+
parts.push('<title>' + safeTitle + '</title>');
|
|
4338
|
+
if (faviconTag) parts.push(faviconTag);
|
|
4339
|
+
if (runtimeHead) parts.push(runtimeHead);
|
|
4340
|
+
if (headHTML) parts.push(headHTML);
|
|
4341
|
+
if (allCSS) parts.push('<style>' + allCSS + '</style>');
|
|
4342
|
+
parts.push('</head>');
|
|
4343
|
+
parts.push('<body>');
|
|
4344
|
+
parts.push(bodyHTML);
|
|
4345
|
+
if (bodyEndScript) parts.push(bodyEndScript);
|
|
4346
|
+
parts.push('</body>');
|
|
4347
|
+
parts.push('</html>');
|
|
4348
|
+
|
|
4349
|
+
return parts.join('\n');
|
|
4350
|
+
};
|
|
4351
|
+
|
|
3824
4352
|
/**
|
|
3825
4353
|
* Create a live DOM element from a TACO object (browser only).
|
|
3826
4354
|
*
|
|
@@ -3865,7 +4393,7 @@
|
|
|
3865
4393
|
}
|
|
3866
4394
|
|
|
3867
4395
|
// Handle text nodes
|
|
3868
|
-
if (
|
|
4396
|
+
if (!_is(taco, 'object') || !taco.t) {
|
|
3869
4397
|
return document.createTextNode(String(taco));
|
|
3870
4398
|
}
|
|
3871
4399
|
|
|
@@ -3878,16 +4406,16 @@
|
|
|
3878
4406
|
for (const [key, value] of Object.entries(attrs)) {
|
|
3879
4407
|
if (value == null || value === false) continue;
|
|
3880
4408
|
|
|
3881
|
-
if (key === 'style' &&
|
|
4409
|
+
if (key === 'style' && _is(value, 'object')) {
|
|
3882
4410
|
// Apply styles directly
|
|
3883
4411
|
Object.assign(el.style, value);
|
|
3884
4412
|
} else if (key === 'class') {
|
|
3885
4413
|
// Handle class as array or string
|
|
3886
|
-
const classStr =
|
|
4414
|
+
const classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
|
|
3887
4415
|
if (classStr) {
|
|
3888
4416
|
el.className = classStr;
|
|
3889
4417
|
}
|
|
3890
|
-
} else if (key.startsWith('on') &&
|
|
4418
|
+
} else if (key.startsWith('on') && _is(value, 'function')) {
|
|
3891
4419
|
// Event handlers
|
|
3892
4420
|
const eventName = key.slice(2).toLowerCase();
|
|
3893
4421
|
el.addEventListener(eventName, value);
|
|
@@ -3907,7 +4435,7 @@
|
|
|
3907
4435
|
// Children with data-bw_id or id attributes get local refs on the parent,
|
|
3908
4436
|
// so o.render functions can access them without any DOM lookup.
|
|
3909
4437
|
if (content != null) {
|
|
3910
|
-
if (
|
|
4438
|
+
if (_isA(content)) {
|
|
3911
4439
|
content.forEach(child => {
|
|
3912
4440
|
if (child != null) {
|
|
3913
4441
|
// Handle ComponentHandle in content arrays (Level 2 children)
|
|
@@ -3927,20 +4455,20 @@
|
|
|
3927
4455
|
if (childEl._bw_refs) {
|
|
3928
4456
|
if (!el._bw_refs) el._bw_refs = {};
|
|
3929
4457
|
for (var rk in childEl._bw_refs) {
|
|
3930
|
-
if (
|
|
4458
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
3931
4459
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
3932
4460
|
}
|
|
3933
4461
|
}
|
|
3934
4462
|
}
|
|
3935
4463
|
}
|
|
3936
4464
|
});
|
|
3937
|
-
} else if (
|
|
4465
|
+
} else if (_is(content, 'object') && content.__bw_raw) {
|
|
3938
4466
|
// Raw HTML content — inject via innerHTML
|
|
3939
4467
|
el.innerHTML = content.v;
|
|
3940
4468
|
} else if (content._bwComponent === true) {
|
|
3941
4469
|
// Single ComponentHandle as content
|
|
3942
4470
|
content.mount(el);
|
|
3943
|
-
} else if (
|
|
4471
|
+
} else if (_is(content, 'object') && content.t) {
|
|
3944
4472
|
var childEl = bw.createDOM(content, options);
|
|
3945
4473
|
el.appendChild(childEl);
|
|
3946
4474
|
var childBwId = content.a ? (content.a['data-bw_id'] || content.a.id) : null;
|
|
@@ -3951,7 +4479,7 @@
|
|
|
3951
4479
|
if (childEl._bw_refs) {
|
|
3952
4480
|
if (!el._bw_refs) el._bw_refs = {};
|
|
3953
4481
|
for (var rk in childEl._bw_refs) {
|
|
3954
|
-
if (
|
|
4482
|
+
if (_hop.call(childEl._bw_refs, rk)) {
|
|
3955
4483
|
el._bw_refs[rk] = childEl._bw_refs[rk];
|
|
3956
4484
|
}
|
|
3957
4485
|
}
|
|
@@ -3966,6 +4494,14 @@
|
|
|
3966
4494
|
bw._registerNode(el, null);
|
|
3967
4495
|
}
|
|
3968
4496
|
|
|
4497
|
+
// Register UUID class in node cache (bw_uuid_* tokens in class string)
|
|
4498
|
+
if (el.className) {
|
|
4499
|
+
var uuidMatch = el.className.match(_UUID_RE);
|
|
4500
|
+
if (uuidMatch) {
|
|
4501
|
+
bw._nodeMap[uuidMatch[0]] = el;
|
|
4502
|
+
}
|
|
4503
|
+
}
|
|
4504
|
+
|
|
3969
4505
|
// Handle lifecycle hooks and state
|
|
3970
4506
|
if (opts.mounted || opts.unmount || opts.render || opts.state) {
|
|
3971
4507
|
const id = attrs['data-bw_id'] || bw.uuid();
|
|
@@ -3984,7 +4520,7 @@
|
|
|
3984
4520
|
el._bw_render = opts.render;
|
|
3985
4521
|
|
|
3986
4522
|
if (opts.mounted) {
|
|
3987
|
-
|
|
4523
|
+
_cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
|
|
3988
4524
|
}
|
|
3989
4525
|
|
|
3990
4526
|
// Queue initial render (same timing as mounted)
|
|
@@ -4057,7 +4593,7 @@
|
|
|
4057
4593
|
const targetEl = bw._el(target);
|
|
4058
4594
|
|
|
4059
4595
|
if (!targetEl) {
|
|
4060
|
-
|
|
4596
|
+
_ce('bw.DOM: Target element not found:', target);
|
|
4061
4597
|
return null;
|
|
4062
4598
|
}
|
|
4063
4599
|
|
|
@@ -4097,7 +4633,7 @@
|
|
|
4097
4633
|
targetEl.appendChild(taco.element);
|
|
4098
4634
|
}
|
|
4099
4635
|
// Handle arrays
|
|
4100
|
-
else if (
|
|
4636
|
+
else if (_isA(taco)) {
|
|
4101
4637
|
taco.forEach(t => {
|
|
4102
4638
|
if (t != null) {
|
|
4103
4639
|
if (t._bwComponent === true) {
|
|
@@ -4133,7 +4669,7 @@
|
|
|
4133
4669
|
bw.compileProps = function(handle, props = {}) {
|
|
4134
4670
|
const compiledProps = {};
|
|
4135
4671
|
|
|
4136
|
-
|
|
4672
|
+
_keys(props).forEach(key => {
|
|
4137
4673
|
// Create getter/setter for each prop
|
|
4138
4674
|
Object.defineProperty(compiledProps, key, {
|
|
4139
4675
|
get() {
|
|
@@ -4338,6 +4874,16 @@
|
|
|
4338
4874
|
bw.cleanup = function(element) {
|
|
4339
4875
|
if (!bw._isBrowser || !element) return;
|
|
4340
4876
|
|
|
4877
|
+
// Deregister UUID classes from node cache (element + descendants)
|
|
4878
|
+
// Covers elements that have UUID but no data-bw_id
|
|
4879
|
+
var selfUuidMatch = element.className && element.className.match(_UUID_RE);
|
|
4880
|
+
if (selfUuidMatch) delete bw._nodeMap[selfUuidMatch[0]];
|
|
4881
|
+
var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
|
|
4882
|
+
uuidEls.forEach(function(uel) {
|
|
4883
|
+
var m = uel.className && uel.className.match(_UUID_RE);
|
|
4884
|
+
if (m) delete bw._nodeMap[m[0]];
|
|
4885
|
+
});
|
|
4886
|
+
|
|
4341
4887
|
// Find all elements with data-bw_id
|
|
4342
4888
|
const elements = element.querySelectorAll('[data-bw_id]');
|
|
4343
4889
|
|
|
@@ -4353,6 +4899,10 @@
|
|
|
4353
4899
|
// Deregister from node cache
|
|
4354
4900
|
bw._deregisterNode(el, id);
|
|
4355
4901
|
|
|
4902
|
+
// Deregister UUID class from node cache
|
|
4903
|
+
var uuidMatch = el.className && el.className.match(_UUID_RE);
|
|
4904
|
+
if (uuidMatch) delete bw._nodeMap[uuidMatch[0]];
|
|
4905
|
+
|
|
4356
4906
|
// Clean up pub/sub subscriptions tied to this element
|
|
4357
4907
|
if (el._bw_subs) {
|
|
4358
4908
|
el._bw_subs.forEach(function(unsub) { unsub(); });
|
|
@@ -4377,6 +4927,10 @@
|
|
|
4377
4927
|
// Deregister from node cache
|
|
4378
4928
|
bw._deregisterNode(element, id);
|
|
4379
4929
|
|
|
4930
|
+
// Deregister UUID class from node cache
|
|
4931
|
+
var elemUuidMatch = element.className && element.className.match(_UUID_RE);
|
|
4932
|
+
if (elemUuidMatch) delete bw._nodeMap[elemUuidMatch[0]];
|
|
4933
|
+
|
|
4380
4934
|
// Clean up pub/sub subscriptions tied to element itself
|
|
4381
4935
|
if (element._bw_subs) {
|
|
4382
4936
|
element._bw_subs.forEach(function(unsub) { unsub(); });
|
|
@@ -4451,17 +5005,17 @@
|
|
|
4451
5005
|
if (attr) {
|
|
4452
5006
|
// Patch an attribute
|
|
4453
5007
|
el.setAttribute(attr, String(content));
|
|
4454
|
-
} else if (
|
|
5008
|
+
} else if (_isA(content)) {
|
|
4455
5009
|
// Patch with array of children (strings and/or TACOs)
|
|
4456
5010
|
el.innerHTML = '';
|
|
4457
5011
|
content.forEach(function(item) {
|
|
4458
|
-
if (
|
|
5012
|
+
if (_is(item, 'string') || _is(item, 'number')) {
|
|
4459
5013
|
el.appendChild(document.createTextNode(String(item)));
|
|
4460
5014
|
} else if (item && item.t) {
|
|
4461
5015
|
el.appendChild(bw.createDOM(item));
|
|
4462
5016
|
}
|
|
4463
5017
|
});
|
|
4464
|
-
} else if (
|
|
5018
|
+
} else if (_is(content, 'object') && content.t) {
|
|
4465
5019
|
// Patch with a TACO — replace children
|
|
4466
5020
|
el.innerHTML = '';
|
|
4467
5021
|
el.appendChild(bw.createDOM(content));
|
|
@@ -4492,7 +5046,7 @@
|
|
|
4492
5046
|
bw.patchAll = function(patches) {
|
|
4493
5047
|
var results = {};
|
|
4494
5048
|
for (var id in patches) {
|
|
4495
|
-
if (
|
|
5049
|
+
if (_hop.call(patches, id)) {
|
|
4496
5050
|
results[id] = bw.patch(id, patches[id]);
|
|
4497
5051
|
}
|
|
4498
5052
|
}
|
|
@@ -4589,7 +5143,7 @@
|
|
|
4589
5143
|
snapshot[i].handler(detail);
|
|
4590
5144
|
called++;
|
|
4591
5145
|
} catch (err) {
|
|
4592
|
-
|
|
5146
|
+
_cw('bw.pub: subscriber error on topic "' + topic + '":', err);
|
|
4593
5147
|
}
|
|
4594
5148
|
}
|
|
4595
5149
|
return called;
|
|
@@ -4685,8 +5239,8 @@
|
|
|
4685
5239
|
* @see bw.funcGetDispatchStr
|
|
4686
5240
|
*/
|
|
4687
5241
|
bw.funcRegister = function(fn, name) {
|
|
4688
|
-
if (
|
|
4689
|
-
var fnID = (
|
|
5242
|
+
if (!_is(fn, 'function')) return '';
|
|
5243
|
+
var fnID = (_is(name, 'string') && name.length > 0) ? name : ('bw_fn_' + bw._fnIDCounter++);
|
|
4690
5244
|
bw._fnRegistry[fnID] = fn;
|
|
4691
5245
|
return fnID;
|
|
4692
5246
|
};
|
|
@@ -4705,7 +5259,7 @@
|
|
|
4705
5259
|
bw.funcGetById = function(name, errFn) {
|
|
4706
5260
|
name = String(name);
|
|
4707
5261
|
if (name in bw._fnRegistry) return bw._fnRegistry[name];
|
|
4708
|
-
return (
|
|
5262
|
+
return _is(errFn, 'function') ? errFn : function() { _cw('bw.funcGetById: unregistered fn "' + name + '"'); };
|
|
4709
5263
|
};
|
|
4710
5264
|
|
|
4711
5265
|
/**
|
|
@@ -4746,13 +5300,30 @@
|
|
|
4746
5300
|
bw.funcGetRegistry = function() {
|
|
4747
5301
|
var copy = {};
|
|
4748
5302
|
for (var k in bw._fnRegistry) {
|
|
4749
|
-
if (
|
|
5303
|
+
if (_hop.call(bw._fnRegistry, k)) {
|
|
4750
5304
|
copy[k] = bw._fnRegistry[k];
|
|
4751
5305
|
}
|
|
4752
5306
|
}
|
|
4753
5307
|
return copy;
|
|
4754
5308
|
};
|
|
4755
5309
|
|
|
5310
|
+
/**
|
|
5311
|
+
* Minimal runtime shim for funcRegister dispatch in static HTML.
|
|
5312
|
+
* When embedded in a `<script>` tag, provides just enough infrastructure
|
|
5313
|
+
* for `bw.funcGetById()` calls to resolve. The actual function bodies
|
|
5314
|
+
* are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
|
|
5315
|
+
* @type {string}
|
|
5316
|
+
* @category Function Registry
|
|
5317
|
+
*/
|
|
5318
|
+
bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' +
|
|
5319
|
+
'if(!bw._fnRegistry)bw._fnRegistry={};' +
|
|
5320
|
+
'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' +
|
|
5321
|
+
'console.warn("bw: unregistered fn "+n)};};' +
|
|
5322
|
+
'bw.funcRegister=function(fn,name){' +
|
|
5323
|
+
'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' +
|
|
5324
|
+
'bw._fnRegistry[id]=fn;return id;};' +
|
|
5325
|
+
'window.bw=bw;})();';
|
|
5326
|
+
|
|
4756
5327
|
// ===================================================================================
|
|
4757
5328
|
// Template Binding Utilities
|
|
4758
5329
|
// ===================================================================================
|
|
@@ -4780,7 +5351,10 @@
|
|
|
4780
5351
|
var parts = path.split('.');
|
|
4781
5352
|
var val = state;
|
|
4782
5353
|
for (var i = 0; i < parts.length; i++) {
|
|
4783
|
-
if (val == null)
|
|
5354
|
+
if (val == null) {
|
|
5355
|
+
if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
|
|
5356
|
+
return '';
|
|
5357
|
+
}
|
|
4784
5358
|
val = val[parts[i]];
|
|
4785
5359
|
}
|
|
4786
5360
|
return (val == null) ? '' : val;
|
|
@@ -4800,7 +5374,7 @@
|
|
|
4800
5374
|
*/
|
|
4801
5375
|
bw._compiledExprs = {};
|
|
4802
5376
|
bw._resolveTemplate = function(str, state, compile) {
|
|
4803
|
-
if (
|
|
5377
|
+
if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
|
|
4804
5378
|
var bindings = bw._parseBindings(str);
|
|
4805
5379
|
if (bindings.length === 0) return str;
|
|
4806
5380
|
|
|
@@ -4822,6 +5396,7 @@
|
|
|
4822
5396
|
try {
|
|
4823
5397
|
val = bw._compiledExprs[b.expr](state);
|
|
4824
5398
|
} catch (e) {
|
|
5399
|
+
if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
|
|
4825
5400
|
val = '';
|
|
4826
5401
|
}
|
|
4827
5402
|
} else {
|
|
@@ -4930,7 +5505,7 @@
|
|
|
4930
5505
|
this._state = {};
|
|
4931
5506
|
if (o.state) {
|
|
4932
5507
|
for (var k in o.state) {
|
|
4933
|
-
if (
|
|
5508
|
+
if (_hop.call(o.state, k)) {
|
|
4934
5509
|
this._state[k] = o.state[k];
|
|
4935
5510
|
}
|
|
4936
5511
|
}
|
|
@@ -4939,7 +5514,7 @@
|
|
|
4939
5514
|
this._actions = {};
|
|
4940
5515
|
if (o.actions) {
|
|
4941
5516
|
for (var k2 in o.actions) {
|
|
4942
|
-
if (
|
|
5517
|
+
if (_hop.call(o.actions, k2)) {
|
|
4943
5518
|
this._actions[k2] = o.actions[k2];
|
|
4944
5519
|
}
|
|
4945
5520
|
}
|
|
@@ -4949,7 +5524,7 @@
|
|
|
4949
5524
|
if (o.methods) {
|
|
4950
5525
|
var self = this;
|
|
4951
5526
|
for (var k3 in o.methods) {
|
|
4952
|
-
if (
|
|
5527
|
+
if (_hop.call(o.methods, k3)) {
|
|
4953
5528
|
this._methods[k3] = o.methods[k3];
|
|
4954
5529
|
(function(methodName, methodFn) {
|
|
4955
5530
|
self[methodName] = function() {
|
|
@@ -4967,7 +5542,7 @@
|
|
|
4967
5542
|
willMount: o.willMount || null,
|
|
4968
5543
|
mounted: o.mounted || null,
|
|
4969
5544
|
willUpdate: o.willUpdate || null,
|
|
4970
|
-
onUpdate: o.onUpdate || null,
|
|
5545
|
+
onUpdate: o.onUpdate || o.updated || null,
|
|
4971
5546
|
unmount: o.unmount || null,
|
|
4972
5547
|
willDestroy: o.willDestroy || null
|
|
4973
5548
|
};
|
|
@@ -4982,14 +5557,23 @@
|
|
|
4982
5557
|
this._compile = !!o.compile;
|
|
4983
5558
|
this._bw_refs = {};
|
|
4984
5559
|
this._refCounter = 0;
|
|
5560
|
+
// Child component ownership (Bug #5)
|
|
5561
|
+
this._children = [];
|
|
5562
|
+
this._parent = null;
|
|
5563
|
+
// Factory metadata for BCCL rebuild (Bug #6)
|
|
5564
|
+
this._factory = taco._bwFactory || null;
|
|
4985
5565
|
}
|
|
4986
5566
|
|
|
5567
|
+
// Short alias for ComponentHandle.prototype (see alias block at top of file).
|
|
5568
|
+
// 28 method definitions × 25 chars = ~700B raw savings in minified output.
|
|
5569
|
+
var _chp = ComponentHandle.prototype;
|
|
5570
|
+
|
|
4987
5571
|
// ── State Methods ──
|
|
4988
5572
|
|
|
4989
5573
|
/**
|
|
4990
5574
|
* Get a state value. Dot-path supported: `get('user.name')`
|
|
4991
5575
|
*/
|
|
4992
|
-
|
|
5576
|
+
_chp.get = function(key) {
|
|
4993
5577
|
return bw._evaluatePath(this._state, key);
|
|
4994
5578
|
};
|
|
4995
5579
|
|
|
@@ -4999,12 +5583,13 @@
|
|
|
4999
5583
|
* @param {*} value - New value
|
|
5000
5584
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
5001
5585
|
*/
|
|
5002
|
-
|
|
5586
|
+
_chp.set = function(key, value, opts) {
|
|
5003
5587
|
// Dot-path set
|
|
5004
5588
|
var parts = key.split('.');
|
|
5005
5589
|
var obj = this._state;
|
|
5006
5590
|
for (var i = 0; i < parts.length - 1; i++) {
|
|
5007
|
-
if (obj[parts[i]]
|
|
5591
|
+
if (!_is(obj[parts[i]], 'object')) {
|
|
5592
|
+
if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
|
|
5008
5593
|
obj[parts[i]] = {};
|
|
5009
5594
|
}
|
|
5010
5595
|
obj = obj[parts[i]];
|
|
@@ -5024,10 +5609,10 @@
|
|
|
5024
5609
|
/**
|
|
5025
5610
|
* Get a shallow clone of the full state.
|
|
5026
5611
|
*/
|
|
5027
|
-
|
|
5612
|
+
_chp.getState = function() {
|
|
5028
5613
|
var clone = {};
|
|
5029
5614
|
for (var k in this._state) {
|
|
5030
|
-
if (
|
|
5615
|
+
if (_hop.call(this._state, k)) {
|
|
5031
5616
|
clone[k] = this._state[k];
|
|
5032
5617
|
}
|
|
5033
5618
|
}
|
|
@@ -5039,9 +5624,9 @@
|
|
|
5039
5624
|
* @param {Object} updates - Key-value pairs to merge
|
|
5040
5625
|
* @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
|
|
5041
5626
|
*/
|
|
5042
|
-
|
|
5627
|
+
_chp.setState = function(updates, opts) {
|
|
5043
5628
|
for (var k in updates) {
|
|
5044
|
-
if (
|
|
5629
|
+
if (_hop.call(updates, k)) {
|
|
5045
5630
|
this._state[k] = updates[k];
|
|
5046
5631
|
this._dirtyKeys[k] = true;
|
|
5047
5632
|
}
|
|
@@ -5058,9 +5643,9 @@
|
|
|
5058
5643
|
/**
|
|
5059
5644
|
* Push a value onto an array in state. Clones the array.
|
|
5060
5645
|
*/
|
|
5061
|
-
|
|
5646
|
+
_chp.push = function(key, val) {
|
|
5062
5647
|
var arr = this.get(key);
|
|
5063
|
-
var newArr =
|
|
5648
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
5064
5649
|
newArr.push(val);
|
|
5065
5650
|
this.set(key, newArr);
|
|
5066
5651
|
};
|
|
@@ -5068,9 +5653,9 @@
|
|
|
5068
5653
|
/**
|
|
5069
5654
|
* Splice an array in state. Clones the array.
|
|
5070
5655
|
*/
|
|
5071
|
-
|
|
5656
|
+
_chp.splice = function(key, start, deleteCount) {
|
|
5072
5657
|
var arr = this.get(key);
|
|
5073
|
-
var newArr =
|
|
5658
|
+
var newArr = _isA(arr) ? arr.slice() : [];
|
|
5074
5659
|
var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
|
|
5075
5660
|
Array.prototype.splice.apply(newArr, args);
|
|
5076
5661
|
this.set(key, newArr);
|
|
@@ -5078,7 +5663,7 @@
|
|
|
5078
5663
|
|
|
5079
5664
|
// ── Scheduling ──
|
|
5080
5665
|
|
|
5081
|
-
|
|
5666
|
+
_chp._scheduleDirty = function() {
|
|
5082
5667
|
if (!this._scheduled) {
|
|
5083
5668
|
this._scheduled = true;
|
|
5084
5669
|
bw._dirtyComponents.push(this);
|
|
@@ -5093,17 +5678,17 @@
|
|
|
5093
5678
|
* Creates binding descriptors with refIds for targeted DOM updates.
|
|
5094
5679
|
* @private
|
|
5095
5680
|
*/
|
|
5096
|
-
|
|
5681
|
+
_chp._compileBindings = function() {
|
|
5097
5682
|
this._bindings = [];
|
|
5098
5683
|
this._refCounter = 0;
|
|
5099
|
-
var stateKeys =
|
|
5684
|
+
var stateKeys = _keys(this._state);
|
|
5100
5685
|
var self = this;
|
|
5101
5686
|
|
|
5102
5687
|
function walkTaco(taco, path) {
|
|
5103
|
-
if (taco
|
|
5688
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5104
5689
|
|
|
5105
5690
|
// Check content for bindings
|
|
5106
|
-
if (
|
|
5691
|
+
if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
|
|
5107
5692
|
var refId = 'bw_ref_' + self._refCounter++;
|
|
5108
5693
|
var parsed = bw._parseBindings(taco.c);
|
|
5109
5694
|
var deps = [];
|
|
@@ -5125,10 +5710,10 @@
|
|
|
5125
5710
|
// Check attributes for bindings
|
|
5126
5711
|
if (taco.a) {
|
|
5127
5712
|
for (var attrName in taco.a) {
|
|
5128
|
-
if (!
|
|
5713
|
+
if (!_hop.call(taco.a, attrName)) continue;
|
|
5129
5714
|
if (attrName === 'data-bw_ref') continue;
|
|
5130
5715
|
var attrVal = taco.a[attrName];
|
|
5131
|
-
if (
|
|
5716
|
+
if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
|
|
5132
5717
|
var refId2 = 'bw_ref_' + self._refCounter++;
|
|
5133
5718
|
var parsed2 = bw._parseBindings(attrVal);
|
|
5134
5719
|
var deps2 = [];
|
|
@@ -5154,9 +5739,27 @@
|
|
|
5154
5739
|
}
|
|
5155
5740
|
|
|
5156
5741
|
// Recurse into children
|
|
5157
|
-
if (
|
|
5742
|
+
if (_isA(taco.c)) {
|
|
5158
5743
|
for (var i = 0; i < taco.c.length; i++) {
|
|
5159
|
-
|
|
5744
|
+
// Wrap string children with ${expr} in a span so patches target the span, not the parent
|
|
5745
|
+
if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
|
|
5746
|
+
var mixedRefId = 'bw_ref_' + self._refCounter++;
|
|
5747
|
+
var mixedParsed = bw._parseBindings(taco.c[i]);
|
|
5748
|
+
var mixedDeps = [];
|
|
5749
|
+
for (var mi = 0; mi < mixedParsed.length; mi++) {
|
|
5750
|
+
mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
|
|
5751
|
+
}
|
|
5752
|
+
self._bindings.push({
|
|
5753
|
+
expr: taco.c[i],
|
|
5754
|
+
type: 'content',
|
|
5755
|
+
refId: mixedRefId,
|
|
5756
|
+
deps: mixedDeps,
|
|
5757
|
+
template: taco.c[i]
|
|
5758
|
+
});
|
|
5759
|
+
// Replace string with a span wrapper so textContent targets the span only
|
|
5760
|
+
taco.c[i] = { t: 'span', a: { 'data-bw_ref': mixedRefId, style: 'display:contents' }, c: taco.c[i] };
|
|
5761
|
+
}
|
|
5762
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
5160
5763
|
walkTaco(taco.c[i], path.concat(i));
|
|
5161
5764
|
}
|
|
5162
5765
|
// Handle bw.when/bw.each markers
|
|
@@ -5191,7 +5794,7 @@
|
|
|
5191
5794
|
taco.c[i]._refId = eachRefId;
|
|
5192
5795
|
}
|
|
5193
5796
|
}
|
|
5194
|
-
} else if (taco.c
|
|
5797
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5195
5798
|
walkTaco(taco.c, path.concat(0));
|
|
5196
5799
|
}
|
|
5197
5800
|
|
|
@@ -5207,7 +5810,7 @@
|
|
|
5207
5810
|
* Build ref map from the live DOM after createDOM.
|
|
5208
5811
|
* @private
|
|
5209
5812
|
*/
|
|
5210
|
-
|
|
5813
|
+
_chp._collectRefs = function() {
|
|
5211
5814
|
this._bw_refs = {};
|
|
5212
5815
|
if (!this.element) return;
|
|
5213
5816
|
var els = this.element.querySelectorAll('[data-bw_ref]');
|
|
@@ -5228,7 +5831,7 @@
|
|
|
5228
5831
|
* Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
|
|
5229
5832
|
* @param {Element} parentEl - DOM element to mount into
|
|
5230
5833
|
*/
|
|
5231
|
-
|
|
5834
|
+
_chp.mount = function(parentEl) {
|
|
5232
5835
|
// willMount hook
|
|
5233
5836
|
if (this._hooks.willMount) this._hooks.willMount(this);
|
|
5234
5837
|
|
|
@@ -5250,7 +5853,7 @@
|
|
|
5250
5853
|
// Register named actions in function registry
|
|
5251
5854
|
var self = this;
|
|
5252
5855
|
for (var actionName in this._actions) {
|
|
5253
|
-
if (
|
|
5856
|
+
if (_hop.call(this._actions, actionName)) {
|
|
5254
5857
|
var registeredName = this._bwId + '_' + actionName;
|
|
5255
5858
|
(function(aName) {
|
|
5256
5859
|
bw.funcRegister(function(evt) {
|
|
@@ -5269,6 +5872,11 @@
|
|
|
5269
5872
|
this.element = bw.createDOM(tacoForDOM);
|
|
5270
5873
|
this.element._bwComponentHandle = this;
|
|
5271
5874
|
this.element.setAttribute('data-bw_comp_id', this._bwId);
|
|
5875
|
+
|
|
5876
|
+
// Restore o.render from original TACO (stripped by _tacoForDOM)
|
|
5877
|
+
if (this.taco.o && this.taco.o.render) {
|
|
5878
|
+
this.element._bw_render = this.taco.o.render;
|
|
5879
|
+
}
|
|
5272
5880
|
if (this._userTag) {
|
|
5273
5881
|
this.element.classList.add(this._userTag);
|
|
5274
5882
|
}
|
|
@@ -5284,6 +5892,16 @@
|
|
|
5284
5892
|
|
|
5285
5893
|
this.mounted = true;
|
|
5286
5894
|
|
|
5895
|
+
// Scan for child ComponentHandles and link parent/child (Bug #5)
|
|
5896
|
+
var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
|
|
5897
|
+
for (var ci = 0; ci < childEls.length; ci++) {
|
|
5898
|
+
var ch = childEls[ci]._bwComponentHandle;
|
|
5899
|
+
if (ch && ch !== this && !ch._parent) {
|
|
5900
|
+
ch._parent = this;
|
|
5901
|
+
this._children.push(ch);
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
|
|
5287
5905
|
// mounted hook (backward compat: fn.length === 2 wraps (el, state))
|
|
5288
5906
|
if (this._hooks.mounted) {
|
|
5289
5907
|
if (this._hooks.mounted.length === 2) {
|
|
@@ -5292,16 +5910,21 @@
|
|
|
5292
5910
|
this._hooks.mounted(this);
|
|
5293
5911
|
}
|
|
5294
5912
|
}
|
|
5913
|
+
|
|
5914
|
+
// Invoke o.render on initial mount (if present)
|
|
5915
|
+
if (this.element._bw_render) {
|
|
5916
|
+
this.element._bw_render(this.element, this._state);
|
|
5917
|
+
}
|
|
5295
5918
|
};
|
|
5296
5919
|
|
|
5297
5920
|
/**
|
|
5298
5921
|
* Prepare TACO for initial render: resolve when/each markers.
|
|
5299
5922
|
* @private
|
|
5300
5923
|
*/
|
|
5301
|
-
|
|
5302
|
-
if (!taco
|
|
5924
|
+
_chp._prepareTaco = function(taco) {
|
|
5925
|
+
if (!_is(taco, 'object')) return;
|
|
5303
5926
|
|
|
5304
|
-
if (
|
|
5927
|
+
if (_isA(taco.c)) {
|
|
5305
5928
|
for (var i = taco.c.length - 1; i >= 0; i--) {
|
|
5306
5929
|
var child = taco.c[i];
|
|
5307
5930
|
if (child && child._bwWhen) {
|
|
@@ -5326,18 +5949,18 @@
|
|
|
5326
5949
|
var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
|
|
5327
5950
|
var arr = bw._evaluatePath(this._state, eachExprStr);
|
|
5328
5951
|
var items = [];
|
|
5329
|
-
if (
|
|
5952
|
+
if (_isA(arr)) {
|
|
5330
5953
|
for (var j = 0; j < arr.length; j++) {
|
|
5331
5954
|
items.push(child.factory(arr[j], j));
|
|
5332
5955
|
}
|
|
5333
5956
|
}
|
|
5334
5957
|
taco.c[i] = { t: 'span', a: { 'data-bw_each': child._refId, style: 'display:contents' }, c: items };
|
|
5335
5958
|
}
|
|
5336
|
-
if (taco.c[i]
|
|
5959
|
+
if (_is(taco.c[i], 'object') && taco.c[i].t) {
|
|
5337
5960
|
this._prepareTaco(taco.c[i]);
|
|
5338
5961
|
}
|
|
5339
5962
|
}
|
|
5340
|
-
} else if (taco.c
|
|
5963
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5341
5964
|
this._prepareTaco(taco.c);
|
|
5342
5965
|
}
|
|
5343
5966
|
};
|
|
@@ -5346,12 +5969,12 @@
|
|
|
5346
5969
|
* Wire action name strings (in onclick etc.) to dispatch function calls.
|
|
5347
5970
|
* @private
|
|
5348
5971
|
*/
|
|
5349
|
-
|
|
5350
|
-
if (!taco
|
|
5972
|
+
_chp._wireActions = function(taco) {
|
|
5973
|
+
if (!_is(taco, 'object') || !taco.t) return;
|
|
5351
5974
|
if (taco.a) {
|
|
5352
5975
|
for (var key in taco.a) {
|
|
5353
|
-
if (!
|
|
5354
|
-
if (key.startsWith('on') &&
|
|
5976
|
+
if (!_hop.call(taco.a, key)) continue;
|
|
5977
|
+
if (key.startsWith('on') && _is(taco.a[key], 'string')) {
|
|
5355
5978
|
var actionName = taco.a[key];
|
|
5356
5979
|
if (actionName in this._actions) {
|
|
5357
5980
|
var registeredName = this._bwId + '_' + actionName;
|
|
@@ -5365,11 +5988,11 @@
|
|
|
5365
5988
|
}
|
|
5366
5989
|
}
|
|
5367
5990
|
}
|
|
5368
|
-
if (
|
|
5991
|
+
if (_isA(taco.c)) {
|
|
5369
5992
|
for (var i = 0; i < taco.c.length; i++) {
|
|
5370
5993
|
this._wireActions(taco.c[i]);
|
|
5371
5994
|
}
|
|
5372
|
-
} else if (taco.c
|
|
5995
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5373
5996
|
this._wireActions(taco.c);
|
|
5374
5997
|
}
|
|
5375
5998
|
};
|
|
@@ -5378,7 +6001,7 @@
|
|
|
5378
6001
|
* Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
|
|
5379
6002
|
* @private
|
|
5380
6003
|
*/
|
|
5381
|
-
|
|
6004
|
+
_chp._deepCloneTaco = function(taco) {
|
|
5382
6005
|
if (taco == null) return taco;
|
|
5383
6006
|
// Preserve _bwWhen / _bwEach markers (contain functions)
|
|
5384
6007
|
if (taco._bwWhen) {
|
|
@@ -5390,18 +6013,18 @@
|
|
|
5390
6013
|
if (taco._bwEach) {
|
|
5391
6014
|
return { _bwEach: true, expr: taco.expr, factory: taco.factory, _refId: taco._refId };
|
|
5392
6015
|
}
|
|
5393
|
-
if (
|
|
6016
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5394
6017
|
var result = { t: taco.t };
|
|
5395
6018
|
if (taco.a) {
|
|
5396
6019
|
result.a = {};
|
|
5397
6020
|
for (var k in taco.a) {
|
|
5398
|
-
if (
|
|
6021
|
+
if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
|
|
5399
6022
|
}
|
|
5400
6023
|
}
|
|
5401
6024
|
if (taco.c != null) {
|
|
5402
|
-
if (
|
|
6025
|
+
if (_isA(taco.c)) {
|
|
5403
6026
|
result.c = taco.c.map(function(child) { return this._deepCloneTaco(child); }.bind(this));
|
|
5404
|
-
} else if (
|
|
6027
|
+
} else if (_is(taco.c, 'object')) {
|
|
5405
6028
|
result.c = this._deepCloneTaco(taco.c);
|
|
5406
6029
|
} else {
|
|
5407
6030
|
result.c = taco.c;
|
|
@@ -5415,27 +6038,31 @@
|
|
|
5415
6038
|
* Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
|
|
5416
6039
|
* @private
|
|
5417
6040
|
*/
|
|
5418
|
-
|
|
5419
|
-
if (!taco
|
|
6041
|
+
_chp._tacoForDOM = function(taco) {
|
|
6042
|
+
if (!_is(taco, 'object') || !taco.t) return taco;
|
|
5420
6043
|
var result = { t: taco.t };
|
|
5421
6044
|
if (taco.a) result.a = taco.a;
|
|
5422
6045
|
if (taco.c != null) {
|
|
5423
|
-
if (
|
|
6046
|
+
if (_isA(taco.c)) {
|
|
5424
6047
|
result.c = taco.c.map(function(child) { return this._tacoForDOM(child); }.bind(this));
|
|
5425
|
-
} else if (
|
|
6048
|
+
} else if (_is(taco.c, 'object') && taco.c.t) {
|
|
5426
6049
|
result.c = this._tacoForDOM(taco.c);
|
|
5427
6050
|
} else {
|
|
5428
6051
|
result.c = taco.c;
|
|
5429
6052
|
}
|
|
5430
6053
|
}
|
|
5431
6054
|
// Intentionally strip o (no mounted/unmount/state/render on sub-elements)
|
|
6055
|
+
if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
|
|
6056
|
+
_cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t +
|
|
6057
|
+
'>. Use onclick attribute or bw.component() for child interactivity.');
|
|
6058
|
+
}
|
|
5432
6059
|
return result;
|
|
5433
6060
|
};
|
|
5434
6061
|
|
|
5435
6062
|
/**
|
|
5436
6063
|
* Unmount: remove from DOM, deactivate, preserve state for re-mount.
|
|
5437
6064
|
*/
|
|
5438
|
-
|
|
6065
|
+
_chp.unmount = function() {
|
|
5439
6066
|
if (!this.mounted) return;
|
|
5440
6067
|
|
|
5441
6068
|
// unmount hook
|
|
@@ -5470,12 +6097,23 @@
|
|
|
5470
6097
|
/**
|
|
5471
6098
|
* Destroy: unmount + clear state + unregister actions.
|
|
5472
6099
|
*/
|
|
5473
|
-
|
|
6100
|
+
_chp.destroy = function() {
|
|
5474
6101
|
// willDestroy hook
|
|
5475
6102
|
if (this._hooks.willDestroy) {
|
|
5476
6103
|
this._hooks.willDestroy(this);
|
|
5477
6104
|
}
|
|
5478
6105
|
|
|
6106
|
+
// Cascade destroy to children depth-first (Bug #5)
|
|
6107
|
+
for (var ci = this._children.length - 1; ci >= 0; ci--) {
|
|
6108
|
+
this._children[ci].destroy();
|
|
6109
|
+
}
|
|
6110
|
+
this._children = [];
|
|
6111
|
+
if (this._parent) {
|
|
6112
|
+
var idx = this._parent._children.indexOf(this);
|
|
6113
|
+
if (idx >= 0) this._parent._children.splice(idx, 1);
|
|
6114
|
+
this._parent = null;
|
|
6115
|
+
}
|
|
6116
|
+
|
|
5479
6117
|
this.unmount();
|
|
5480
6118
|
|
|
5481
6119
|
// Unregister actions from function registry
|
|
@@ -5502,12 +6140,36 @@
|
|
|
5502
6140
|
* Flush dirty state: resolve changed bindings and apply to DOM.
|
|
5503
6141
|
* @private
|
|
5504
6142
|
*/
|
|
5505
|
-
|
|
6143
|
+
_chp._flush = function() {
|
|
5506
6144
|
this._scheduled = false;
|
|
5507
|
-
var changedKeys =
|
|
6145
|
+
var changedKeys = _keys(this._dirtyKeys);
|
|
5508
6146
|
this._dirtyKeys = {};
|
|
5509
6147
|
if (changedKeys.length === 0 || !this.mounted) return;
|
|
5510
6148
|
|
|
6149
|
+
// Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
|
|
6150
|
+
// rebuild the TACO from the factory with merged state (Bug #6)
|
|
6151
|
+
if (this._factory) {
|
|
6152
|
+
var rebuildNeeded = false;
|
|
6153
|
+
for (var fi = 0; fi < changedKeys.length; fi++) {
|
|
6154
|
+
if (_hop.call(this._factory.props, changedKeys[fi])) {
|
|
6155
|
+
rebuildNeeded = true; break;
|
|
6156
|
+
}
|
|
6157
|
+
}
|
|
6158
|
+
if (rebuildNeeded) {
|
|
6159
|
+
var merged = {};
|
|
6160
|
+
for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
|
|
6161
|
+
for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
|
|
6162
|
+
this._factory.props = merged;
|
|
6163
|
+
var newTaco = bw.make(this._factory.type, merged);
|
|
6164
|
+
newTaco._bwFactory = this._factory;
|
|
6165
|
+
this.taco = newTaco;
|
|
6166
|
+
this._originalTaco = this._deepCloneTaco(newTaco);
|
|
6167
|
+
this._render();
|
|
6168
|
+
if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
|
|
6169
|
+
return;
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
|
|
5511
6173
|
// willUpdate hook
|
|
5512
6174
|
if (this._hooks.willUpdate) {
|
|
5513
6175
|
this._hooks.willUpdate(this, changedKeys);
|
|
@@ -5546,7 +6208,7 @@
|
|
|
5546
6208
|
* Returns list of patches to apply.
|
|
5547
6209
|
* @private
|
|
5548
6210
|
*/
|
|
5549
|
-
|
|
6211
|
+
_chp._resolveBindings = function(changedKeys) {
|
|
5550
6212
|
var patches = [];
|
|
5551
6213
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
5552
6214
|
var b = this._bindings[i];
|
|
@@ -5582,11 +6244,14 @@
|
|
|
5582
6244
|
* Apply patches to DOM.
|
|
5583
6245
|
* @private
|
|
5584
6246
|
*/
|
|
5585
|
-
|
|
6247
|
+
_chp._applyPatches = function(patches) {
|
|
5586
6248
|
for (var i = 0; i < patches.length; i++) {
|
|
5587
6249
|
var p = patches[i];
|
|
5588
6250
|
var el = this._bw_refs[p.refId];
|
|
5589
|
-
if (!el)
|
|
6251
|
+
if (!el) {
|
|
6252
|
+
if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
|
|
6253
|
+
continue;
|
|
6254
|
+
}
|
|
5590
6255
|
if (p.type === 'content') {
|
|
5591
6256
|
el.textContent = p.value;
|
|
5592
6257
|
} else if (p.type === 'attribute') {
|
|
@@ -5603,7 +6268,7 @@
|
|
|
5603
6268
|
* Resolve all bindings and apply (used for initial render).
|
|
5604
6269
|
* @private
|
|
5605
6270
|
*/
|
|
5606
|
-
|
|
6271
|
+
_chp._resolveAndApplyAll = function() {
|
|
5607
6272
|
var patches = [];
|
|
5608
6273
|
for (var i = 0; i < this._bindings.length; i++) {
|
|
5609
6274
|
var b = this._bindings[i];
|
|
@@ -5626,7 +6291,7 @@
|
|
|
5626
6291
|
* Full re-render for structural changes (when/each branch switches).
|
|
5627
6292
|
* @private
|
|
5628
6293
|
*/
|
|
5629
|
-
|
|
6294
|
+
_chp._render = function() {
|
|
5630
6295
|
if (!this.element || !this.element.parentNode) return;
|
|
5631
6296
|
var parent = this.element.parentNode;
|
|
5632
6297
|
var nextSibling = this.element.nextSibling;
|
|
@@ -5666,7 +6331,7 @@
|
|
|
5666
6331
|
* @param {string} event - Event name (e.g., 'click')
|
|
5667
6332
|
* @param {Function} handler - Event handler
|
|
5668
6333
|
*/
|
|
5669
|
-
|
|
6334
|
+
_chp.on = function(event, handler) {
|
|
5670
6335
|
if (this.element) {
|
|
5671
6336
|
this.element.addEventListener(event, handler);
|
|
5672
6337
|
}
|
|
@@ -5678,7 +6343,7 @@
|
|
|
5678
6343
|
* @param {string} event - Event name
|
|
5679
6344
|
* @param {Function} handler - Handler to remove
|
|
5680
6345
|
*/
|
|
5681
|
-
|
|
6346
|
+
_chp.off = function(event, handler) {
|
|
5682
6347
|
if (this.element) {
|
|
5683
6348
|
this.element.removeEventListener(event, handler);
|
|
5684
6349
|
}
|
|
@@ -5693,7 +6358,7 @@
|
|
|
5693
6358
|
* @param {Function} handler - Handler function
|
|
5694
6359
|
* @returns {Function} Unsubscribe function
|
|
5695
6360
|
*/
|
|
5696
|
-
|
|
6361
|
+
_chp.sub = function(topic, handler) {
|
|
5697
6362
|
var unsub = bw.sub(topic, handler);
|
|
5698
6363
|
this._subs.push(unsub);
|
|
5699
6364
|
return unsub;
|
|
@@ -5704,10 +6369,10 @@
|
|
|
5704
6369
|
* @param {string} name - Action name
|
|
5705
6370
|
* @param {...*} args - Arguments passed after comp
|
|
5706
6371
|
*/
|
|
5707
|
-
|
|
6372
|
+
_chp.action = function(name) {
|
|
5708
6373
|
var fn = this._actions[name];
|
|
5709
6374
|
if (!fn) {
|
|
5710
|
-
|
|
6375
|
+
_cw('ComponentHandle.action: unknown action "' + name + '"');
|
|
5711
6376
|
return;
|
|
5712
6377
|
}
|
|
5713
6378
|
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
|
|
@@ -5719,7 +6384,7 @@
|
|
|
5719
6384
|
* @param {string} sel - CSS selector
|
|
5720
6385
|
* @returns {Element|null}
|
|
5721
6386
|
*/
|
|
5722
|
-
|
|
6387
|
+
_chp.select = function(sel) {
|
|
5723
6388
|
return this.element ? this.element.querySelector(sel) : null;
|
|
5724
6389
|
};
|
|
5725
6390
|
|
|
@@ -5728,7 +6393,7 @@
|
|
|
5728
6393
|
* @param {string} sel - CSS selector
|
|
5729
6394
|
* @returns {Element[]}
|
|
5730
6395
|
*/
|
|
5731
|
-
|
|
6396
|
+
_chp.selectAll = function(sel) {
|
|
5732
6397
|
if (!this.element) return [];
|
|
5733
6398
|
return Array.prototype.slice.call(this.element.querySelectorAll(sel));
|
|
5734
6399
|
};
|
|
@@ -5739,7 +6404,7 @@
|
|
|
5739
6404
|
* @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
|
|
5740
6405
|
* @returns {ComponentHandle} this (for chaining)
|
|
5741
6406
|
*/
|
|
5742
|
-
|
|
6407
|
+
_chp.userTag = function(tag) {
|
|
5743
6408
|
this._userTag = tag;
|
|
5744
6409
|
if (this.element) {
|
|
5745
6410
|
this.element.classList.add(tag);
|
|
@@ -5816,7 +6481,7 @@
|
|
|
5816
6481
|
* and calls the named method. This is the bitwrench equivalent of
|
|
5817
6482
|
* Win32 SendMessage(hwnd, msg, wParam, lParam).
|
|
5818
6483
|
*
|
|
5819
|
-
* @param {string} target - Component UUID (data-bw_comp_id) or user tag (CSS class)
|
|
6484
|
+
* @param {string} target - Component UUID (bw_uuid_*), comp ID (data-bw_comp_id), or user tag (CSS class)
|
|
5820
6485
|
* @param {string} action - Method name to call on the component
|
|
5821
6486
|
* @param {*} data - Data to pass to the method
|
|
5822
6487
|
* @returns {boolean} True if message was dispatched successfully
|
|
@@ -5833,15 +6498,20 @@
|
|
|
5833
6498
|
* };
|
|
5834
6499
|
*/
|
|
5835
6500
|
bw.message = function(target, action, data) {
|
|
5836
|
-
// Try
|
|
5837
|
-
var el = bw
|
|
5838
|
-
|
|
6501
|
+
// Try bw._el() first (handles UUID class, nodeMap cache, getElementById)
|
|
6502
|
+
var el = bw._el(target);
|
|
6503
|
+
// Then try data-bw_comp_id attribute
|
|
6504
|
+
if (!el || !el._bwComponentHandle) {
|
|
6505
|
+
el = bw.$('[data-bw_comp_id="' + target + '"]')[0];
|
|
6506
|
+
}
|
|
6507
|
+
// Then try CSS class (user tag)
|
|
6508
|
+
if (!el || !el._bwComponentHandle) {
|
|
5839
6509
|
el = bw.$('.' + target)[0];
|
|
5840
6510
|
}
|
|
5841
6511
|
if (!el || !el._bwComponentHandle) return false;
|
|
5842
6512
|
var comp = el._bwComponentHandle;
|
|
5843
|
-
if (
|
|
5844
|
-
|
|
6513
|
+
if (!_is(comp[action], 'function')) {
|
|
6514
|
+
_cw('bw.message: unknown action "' + action + '" on component ' + target);
|
|
5845
6515
|
return false;
|
|
5846
6516
|
}
|
|
5847
6517
|
comp[action](data);
|
|
@@ -5849,59 +6519,24 @@
|
|
|
5849
6519
|
};
|
|
5850
6520
|
|
|
5851
6521
|
// ===================================================================================
|
|
5852
|
-
// bw.
|
|
6522
|
+
// bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
|
|
5853
6523
|
// ===================================================================================
|
|
5854
6524
|
|
|
5855
6525
|
/**
|
|
5856
6526
|
* Registry of named functions sent via register messages.
|
|
5857
|
-
* Populated by
|
|
5858
|
-
* Invoked by
|
|
6527
|
+
* Populated by bw.apply({ type: 'register', name, body }).
|
|
6528
|
+
* Invoked by bw.apply({ type: 'call', name, args }).
|
|
5859
6529
|
* @private
|
|
5860
6530
|
*/
|
|
5861
6531
|
bw._clientFunctions = {};
|
|
5862
6532
|
|
|
5863
6533
|
/**
|
|
5864
|
-
* Whether exec messages are allowed. Set by
|
|
6534
|
+
* Whether exec messages are allowed. Set by bwclient connect opts.allowExec.
|
|
5865
6535
|
* Default false — exec messages are rejected unless explicitly opted in.
|
|
5866
6536
|
* @private
|
|
5867
6537
|
*/
|
|
5868
6538
|
bw._allowExec = false;
|
|
5869
6539
|
|
|
5870
|
-
/**
|
|
5871
|
-
* Built-in client functions available via call() without registration.
|
|
5872
|
-
* @private
|
|
5873
|
-
*/
|
|
5874
|
-
bw._builtinClientFunctions = {
|
|
5875
|
-
scrollTo: function(selector) {
|
|
5876
|
-
var el = bw._el(selector);
|
|
5877
|
-
if (el) el.scrollTop = el.scrollHeight;
|
|
5878
|
-
},
|
|
5879
|
-
focus: function(selector) {
|
|
5880
|
-
var el = bw._el(selector);
|
|
5881
|
-
if (el && typeof el.focus === 'function') el.focus();
|
|
5882
|
-
},
|
|
5883
|
-
download: function(filename, content, mimeType) {
|
|
5884
|
-
if (typeof document === 'undefined') return;
|
|
5885
|
-
var blob = new Blob([content], { type: mimeType || 'text/plain' });
|
|
5886
|
-
var a = document.createElement('a');
|
|
5887
|
-
a.href = URL.createObjectURL(blob);
|
|
5888
|
-
a.download = filename;
|
|
5889
|
-
a.click();
|
|
5890
|
-
URL.revokeObjectURL(a.href);
|
|
5891
|
-
},
|
|
5892
|
-
clipboard: function(text) {
|
|
5893
|
-
if (typeof navigator !== 'undefined' && navigator.clipboard) {
|
|
5894
|
-
navigator.clipboard.writeText(text);
|
|
5895
|
-
}
|
|
5896
|
-
},
|
|
5897
|
-
redirect: function(url) {
|
|
5898
|
-
if (typeof window !== 'undefined') window.location.href = url;
|
|
5899
|
-
},
|
|
5900
|
-
log: function() {
|
|
5901
|
-
console.log.apply(console, arguments);
|
|
5902
|
-
}
|
|
5903
|
-
};
|
|
5904
|
-
|
|
5905
6540
|
/**
|
|
5906
6541
|
* Parse a bwserve protocol message string, supporting both strict JSON
|
|
5907
6542
|
* and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
|
|
@@ -5916,9 +6551,9 @@
|
|
|
5916
6551
|
* @param {string} str - JSON or r-prefixed relaxed JSON string
|
|
5917
6552
|
* @returns {Object} Parsed message object
|
|
5918
6553
|
* @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
|
|
5919
|
-
* @category
|
|
6554
|
+
* @category Core
|
|
5920
6555
|
*/
|
|
5921
|
-
bw.
|
|
6556
|
+
bw.parseJSONFlex = function(str) {
|
|
5922
6557
|
str = (str || '').trim();
|
|
5923
6558
|
if (str.charAt(0) !== 'r') return JSON.parse(str);
|
|
5924
6559
|
str = str.slice(1);
|
|
@@ -6003,10 +6638,10 @@
|
|
|
6003
6638
|
* append — target.appendChild(bw.createDOM(node))
|
|
6004
6639
|
* remove — bw.cleanup(target); target.remove()
|
|
6005
6640
|
* patch — bw.patch(target, content, attr)
|
|
6006
|
-
* batch — iterate ops, call
|
|
6641
|
+
* batch — iterate ops, call bw.apply for each
|
|
6007
6642
|
* message — bw.message(target, action, data)
|
|
6008
6643
|
* register — store a named function for later call()
|
|
6009
|
-
* call — invoke a registered
|
|
6644
|
+
* call — invoke a registered function
|
|
6010
6645
|
* exec — execute arbitrary JS (requires allowExec)
|
|
6011
6646
|
*
|
|
6012
6647
|
* Target resolution:
|
|
@@ -6015,9 +6650,9 @@
|
|
|
6015
6650
|
*
|
|
6016
6651
|
* @param {Object} msg - Protocol message
|
|
6017
6652
|
* @returns {boolean} true if the message was applied successfully
|
|
6018
|
-
* @category
|
|
6653
|
+
* @category Core
|
|
6019
6654
|
*/
|
|
6020
|
-
bw.
|
|
6655
|
+
bw.apply = function(msg) {
|
|
6021
6656
|
if (!msg || !msg.type) return false;
|
|
6022
6657
|
|
|
6023
6658
|
var type = msg.type;
|
|
@@ -6043,15 +6678,15 @@
|
|
|
6043
6678
|
} else if (type === 'remove') {
|
|
6044
6679
|
var toRemove = bw._el(target);
|
|
6045
6680
|
if (!toRemove) return false;
|
|
6046
|
-
if (
|
|
6681
|
+
if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
|
|
6047
6682
|
toRemove.remove();
|
|
6048
6683
|
return true;
|
|
6049
6684
|
|
|
6050
6685
|
} else if (type === 'batch') {
|
|
6051
|
-
if (!
|
|
6686
|
+
if (!_isA(msg.ops)) return false;
|
|
6052
6687
|
var allOk = true;
|
|
6053
6688
|
msg.ops.forEach(function(op) {
|
|
6054
|
-
if (!bw.
|
|
6689
|
+
if (!bw.apply(op)) allOk = false;
|
|
6055
6690
|
});
|
|
6056
6691
|
return allOk;
|
|
6057
6692
|
|
|
@@ -6064,26 +6699,26 @@
|
|
|
6064
6699
|
bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
|
|
6065
6700
|
return true;
|
|
6066
6701
|
} catch (e) {
|
|
6067
|
-
|
|
6702
|
+
_ce('[bw] register error:', msg.name, e);
|
|
6068
6703
|
return false;
|
|
6069
6704
|
}
|
|
6070
6705
|
|
|
6071
6706
|
} else if (type === 'call') {
|
|
6072
6707
|
if (!msg.name) return false;
|
|
6073
|
-
var fn = bw._clientFunctions[msg.name]
|
|
6074
|
-
if (
|
|
6708
|
+
var fn = bw._clientFunctions[msg.name];
|
|
6709
|
+
if (!_is(fn, 'function')) return false;
|
|
6075
6710
|
try {
|
|
6076
|
-
var args =
|
|
6711
|
+
var args = _isA(msg.args) ? msg.args : [];
|
|
6077
6712
|
fn.apply(null, args);
|
|
6078
6713
|
return true;
|
|
6079
6714
|
} catch (e) {
|
|
6080
|
-
|
|
6715
|
+
_ce('[bw] call error:', msg.name, e);
|
|
6081
6716
|
return false;
|
|
6082
6717
|
}
|
|
6083
6718
|
|
|
6084
6719
|
} else if (type === 'exec') {
|
|
6085
6720
|
if (!bw._allowExec) {
|
|
6086
|
-
|
|
6721
|
+
_cw('[bw] exec rejected: allowExec is not enabled');
|
|
6087
6722
|
return false;
|
|
6088
6723
|
}
|
|
6089
6724
|
if (!msg.code) return false;
|
|
@@ -6091,7 +6726,7 @@
|
|
|
6091
6726
|
new Function(msg.code)();
|
|
6092
6727
|
return true;
|
|
6093
6728
|
} catch (e) {
|
|
6094
|
-
|
|
6729
|
+
_ce('[bw] exec error:', e);
|
|
6095
6730
|
return false;
|
|
6096
6731
|
}
|
|
6097
6732
|
}
|
|
@@ -6099,139 +6734,6 @@
|
|
|
6099
6734
|
return false;
|
|
6100
6735
|
};
|
|
6101
6736
|
|
|
6102
|
-
/**
|
|
6103
|
-
* Connect to a bwserve SSE endpoint and apply protocol messages automatically.
|
|
6104
|
-
*
|
|
6105
|
-
* Returns a connection object with sendAction(), on(), and close() methods.
|
|
6106
|
-
*
|
|
6107
|
-
* @param {string} url - SSE endpoint URL (e.g., '/__bw/events/client-1')
|
|
6108
|
-
* @param {Object} [opts] - Connection options
|
|
6109
|
-
* @param {string} [opts.transport='sse'] - Transport type: 'sse' (default) or 'poll'
|
|
6110
|
-
* @param {number} [opts.interval=2000] - Poll interval in ms (only for 'poll' transport)
|
|
6111
|
-
* @param {string} [opts.actionUrl] - POST endpoint for actions (default: derived from url)
|
|
6112
|
-
* @param {boolean} [opts.reconnect=true] - Auto-reconnect on disconnect
|
|
6113
|
-
* @param {boolean} [opts.allowExec=false] - Enable exec message type (arbitrary JS execution)
|
|
6114
|
-
* @param {Function} [opts.onStatus] - Status callback: 'connecting'|'connected'|'disconnected'
|
|
6115
|
-
* @param {Function} [opts.onMessage] - Raw message callback (before clientApply)
|
|
6116
|
-
* @returns {Object} Connection object { sendAction, on, close, status }
|
|
6117
|
-
* @category Server
|
|
6118
|
-
*/
|
|
6119
|
-
bw.clientConnect = function(url, opts) {
|
|
6120
|
-
opts = opts || {};
|
|
6121
|
-
var transport = opts.transport || 'sse';
|
|
6122
|
-
var actionUrl = opts.actionUrl || url.replace(/\/events\//, '/action/');
|
|
6123
|
-
var reconnect = opts.reconnect !== false;
|
|
6124
|
-
var onStatus = opts.onStatus || function() {};
|
|
6125
|
-
var onMessage = opts.onMessage || null;
|
|
6126
|
-
var handlers = {};
|
|
6127
|
-
// Set the global allowExec flag from connection options
|
|
6128
|
-
bw._allowExec = !!opts.allowExec;
|
|
6129
|
-
var conn = {
|
|
6130
|
-
status: 'connecting',
|
|
6131
|
-
_es: null,
|
|
6132
|
-
_pollTimer: null
|
|
6133
|
-
};
|
|
6134
|
-
|
|
6135
|
-
function setStatus(s) {
|
|
6136
|
-
conn.status = s;
|
|
6137
|
-
onStatus(s);
|
|
6138
|
-
}
|
|
6139
|
-
|
|
6140
|
-
function handleMessage(data) {
|
|
6141
|
-
try {
|
|
6142
|
-
var msg = typeof data === 'string' ? bw.clientParse(data) : data;
|
|
6143
|
-
if (onMessage) onMessage(msg);
|
|
6144
|
-
if (handlers.message) handlers.message(msg);
|
|
6145
|
-
bw.clientApply(msg);
|
|
6146
|
-
} catch (e) {
|
|
6147
|
-
if (handlers.error) handlers.error(e);
|
|
6148
|
-
}
|
|
6149
|
-
}
|
|
6150
|
-
|
|
6151
|
-
if (transport === 'sse' && typeof EventSource !== 'undefined') {
|
|
6152
|
-
setStatus('connecting');
|
|
6153
|
-
var es = new EventSource(url);
|
|
6154
|
-
conn._es = es;
|
|
6155
|
-
|
|
6156
|
-
es.onopen = function() {
|
|
6157
|
-
setStatus('connected');
|
|
6158
|
-
if (handlers.open) handlers.open();
|
|
6159
|
-
};
|
|
6160
|
-
|
|
6161
|
-
es.onmessage = function(e) {
|
|
6162
|
-
handleMessage(e.data);
|
|
6163
|
-
};
|
|
6164
|
-
|
|
6165
|
-
es.onerror = function() {
|
|
6166
|
-
if (conn.status === 'connected') {
|
|
6167
|
-
setStatus('disconnected');
|
|
6168
|
-
}
|
|
6169
|
-
if (handlers.error) handlers.error(new Error('SSE connection error'));
|
|
6170
|
-
if (!reconnect) {
|
|
6171
|
-
es.close();
|
|
6172
|
-
}
|
|
6173
|
-
// EventSource auto-reconnects by default when reconnect=true
|
|
6174
|
-
};
|
|
6175
|
-
} else if (transport === 'poll') {
|
|
6176
|
-
var interval = opts.interval || 2000;
|
|
6177
|
-
setStatus('connected');
|
|
6178
|
-
conn._pollTimer = setInterval(function() {
|
|
6179
|
-
fetch(url).then(function(r) { return r.json(); }).then(function(msgs) {
|
|
6180
|
-
if (Array.isArray(msgs)) {
|
|
6181
|
-
msgs.forEach(handleMessage);
|
|
6182
|
-
} else if (msgs && msgs.type) {
|
|
6183
|
-
handleMessage(msgs);
|
|
6184
|
-
}
|
|
6185
|
-
}).catch(function(e) {
|
|
6186
|
-
if (handlers.error) handlers.error(e);
|
|
6187
|
-
});
|
|
6188
|
-
}, interval);
|
|
6189
|
-
}
|
|
6190
|
-
|
|
6191
|
-
/**
|
|
6192
|
-
* Send an action to the server via POST.
|
|
6193
|
-
* @param {string} action - Action name
|
|
6194
|
-
* @param {Object} [data] - Action payload
|
|
6195
|
-
*/
|
|
6196
|
-
conn.sendAction = function(action, data) {
|
|
6197
|
-
var body = JSON.stringify({ type: 'action', action: action, data: data || {} });
|
|
6198
|
-
fetch(actionUrl, {
|
|
6199
|
-
method: 'POST',
|
|
6200
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6201
|
-
body: body
|
|
6202
|
-
}).catch(function(e) {
|
|
6203
|
-
if (handlers.error) handlers.error(e);
|
|
6204
|
-
});
|
|
6205
|
-
};
|
|
6206
|
-
|
|
6207
|
-
/**
|
|
6208
|
-
* Register an event handler.
|
|
6209
|
-
* @param {string} event - 'open'|'message'|'error'|'close'
|
|
6210
|
-
* @param {Function} handler
|
|
6211
|
-
*/
|
|
6212
|
-
conn.on = function(event, handler) {
|
|
6213
|
-
handlers[event] = handler;
|
|
6214
|
-
return conn;
|
|
6215
|
-
};
|
|
6216
|
-
|
|
6217
|
-
/**
|
|
6218
|
-
* Close the connection.
|
|
6219
|
-
*/
|
|
6220
|
-
conn.close = function() {
|
|
6221
|
-
if (conn._es) {
|
|
6222
|
-
conn._es.close();
|
|
6223
|
-
conn._es = null;
|
|
6224
|
-
}
|
|
6225
|
-
if (conn._pollTimer) {
|
|
6226
|
-
clearInterval(conn._pollTimer);
|
|
6227
|
-
conn._pollTimer = null;
|
|
6228
|
-
}
|
|
6229
|
-
setStatus('disconnected');
|
|
6230
|
-
if (handlers.close) handlers.close();
|
|
6231
|
-
};
|
|
6232
|
-
|
|
6233
|
-
return conn;
|
|
6234
|
-
};
|
|
6235
6737
|
|
|
6236
6738
|
// ===================================================================================
|
|
6237
6739
|
// bw.inspect() — Debug utility
|
|
@@ -6259,33 +6761,33 @@
|
|
|
6259
6761
|
el = target.element;
|
|
6260
6762
|
comp = target;
|
|
6261
6763
|
} else {
|
|
6262
|
-
if (
|
|
6764
|
+
if (_is(target, 'string')) {
|
|
6263
6765
|
el = bw.$(target)[0];
|
|
6264
6766
|
}
|
|
6265
6767
|
if (!el) {
|
|
6266
|
-
|
|
6768
|
+
_cw('bw.inspect: element not found');
|
|
6267
6769
|
return null;
|
|
6268
6770
|
}
|
|
6269
6771
|
comp = el._bwComponentHandle;
|
|
6270
6772
|
}
|
|
6271
6773
|
if (!comp) {
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6774
|
+
_cl('bw.inspect: no ComponentHandle on this element');
|
|
6775
|
+
_cl(' Tag:', el.tagName);
|
|
6776
|
+
_cl(' Classes:', el.className);
|
|
6777
|
+
_cl(' _bw_state:', el._bw_state || '(none)');
|
|
6276
6778
|
return null;
|
|
6277
6779
|
}
|
|
6278
6780
|
var deps = comp._bindings.reduce(function(s, b) {
|
|
6279
6781
|
return s.concat(b.deps || []);
|
|
6280
6782
|
}, []).filter(function(v, i, a) { return a.indexOf(v) === i; });
|
|
6281
6783
|
console.group('Component: ' + comp._bwId);
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6784
|
+
_cl('State:', comp._state);
|
|
6785
|
+
_cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
|
|
6786
|
+
_cl('Methods:', _keys(comp._methods));
|
|
6787
|
+
_cl('Actions:', _keys(comp._actions));
|
|
6788
|
+
_cl('User tag:', comp._userTag || '(none)');
|
|
6789
|
+
_cl('Mounted:', comp.mounted);
|
|
6790
|
+
_cl('Element:', comp.element);
|
|
6289
6791
|
console.groupEnd();
|
|
6290
6792
|
return comp;
|
|
6291
6793
|
};
|
|
@@ -6308,8 +6810,8 @@
|
|
|
6308
6810
|
// Pre-extract all binding expressions
|
|
6309
6811
|
var precompiled = [];
|
|
6310
6812
|
function walkExpressions(node) {
|
|
6311
|
-
if (!node
|
|
6312
|
-
if (
|
|
6813
|
+
if (!_is(node, 'object')) return;
|
|
6814
|
+
if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
|
|
6313
6815
|
var parsed = bw._parseBindings(node.c);
|
|
6314
6816
|
for (var i = 0; i < parsed.length; i++) {
|
|
6315
6817
|
try {
|
|
@@ -6324,9 +6826,9 @@
|
|
|
6324
6826
|
}
|
|
6325
6827
|
if (node.a) {
|
|
6326
6828
|
for (var key in node.a) {
|
|
6327
|
-
if (
|
|
6829
|
+
if (_hop.call(node.a, key)) {
|
|
6328
6830
|
var v = node.a[key];
|
|
6329
|
-
if (
|
|
6831
|
+
if (_is(v, 'string') && v.indexOf('${') >= 0) {
|
|
6330
6832
|
var parsed2 = bw._parseBindings(v);
|
|
6331
6833
|
for (var j = 0; j < parsed2.length; j++) {
|
|
6332
6834
|
try {
|
|
@@ -6342,9 +6844,9 @@
|
|
|
6342
6844
|
}
|
|
6343
6845
|
}
|
|
6344
6846
|
}
|
|
6345
|
-
if (
|
|
6847
|
+
if (_isA(node.c)) {
|
|
6346
6848
|
for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
|
|
6347
|
-
} else if (node.c
|
|
6849
|
+
} else if (_is(node.c, 'object') && node.c.t) {
|
|
6348
6850
|
walkExpressions(node.c);
|
|
6349
6851
|
}
|
|
6350
6852
|
}
|
|
@@ -6356,7 +6858,7 @@
|
|
|
6356
6858
|
handle._precompiledBindings = precompiled;
|
|
6357
6859
|
if (initialState) {
|
|
6358
6860
|
for (var k in initialState) {
|
|
6359
|
-
if (
|
|
6861
|
+
if (_hop.call(initialState, k)) {
|
|
6360
6862
|
handle._state[k] = initialState[k];
|
|
6361
6863
|
}
|
|
6362
6864
|
}
|
|
@@ -6387,18 +6889,18 @@
|
|
|
6387
6889
|
bw.css = function(rules, options = {}) {
|
|
6388
6890
|
const { minify = false, pretty = !minify } = options;
|
|
6389
6891
|
|
|
6390
|
-
if (
|
|
6892
|
+
if (_is(rules, 'string')) return rules;
|
|
6391
6893
|
|
|
6392
6894
|
let css = '';
|
|
6393
6895
|
const indent = pretty ? ' ' : '';
|
|
6394
6896
|
const newline = pretty ? '\n' : '';
|
|
6395
6897
|
const space = pretty ? ' ' : '';
|
|
6396
6898
|
|
|
6397
|
-
if (
|
|
6899
|
+
if (_isA(rules)) {
|
|
6398
6900
|
css = rules.map(rule => bw.css(rule, options)).join(newline);
|
|
6399
|
-
} else if (
|
|
6901
|
+
} else if (_is(rules, 'object')) {
|
|
6400
6902
|
Object.entries(rules).forEach(([selector, styles]) => {
|
|
6401
|
-
if (
|
|
6903
|
+
if (_is(styles, 'object')) {
|
|
6402
6904
|
// Handle @media, @keyframes, @supports — recurse into nested block
|
|
6403
6905
|
if (selector.charAt(0) === '@') {
|
|
6404
6906
|
const inner = bw.css(styles, options);
|
|
@@ -6440,14 +6942,14 @@
|
|
|
6440
6942
|
* @returns {Element} The style element
|
|
6441
6943
|
* @category CSS & Styling
|
|
6442
6944
|
* @see bw.css
|
|
6443
|
-
* @see bw.
|
|
6945
|
+
* @see bw.loadStyles
|
|
6444
6946
|
* @example
|
|
6445
6947
|
* bw.injectCSS('.my-class { color: red; }');
|
|
6446
6948
|
* bw.injectCSS({ '.card': { padding: '1rem' } }, { id: 'card-styles' });
|
|
6447
6949
|
*/
|
|
6448
6950
|
bw.injectCSS = function(css, options = {}) {
|
|
6449
6951
|
if (!bw._isBrowser) {
|
|
6450
|
-
|
|
6952
|
+
_cw('bw.injectCSS requires a DOM environment');
|
|
6451
6953
|
return null;
|
|
6452
6954
|
}
|
|
6453
6955
|
|
|
@@ -6464,7 +6966,7 @@
|
|
|
6464
6966
|
}
|
|
6465
6967
|
|
|
6466
6968
|
// Convert CSS if needed
|
|
6467
|
-
const cssStr =
|
|
6969
|
+
const cssStr = _is(css, 'string') ? css : bw.css(css, options);
|
|
6468
6970
|
|
|
6469
6971
|
// Set or append CSS
|
|
6470
6972
|
if (append && styleEl.textContent) {
|
|
@@ -6485,113 +6987,19 @@
|
|
|
6485
6987
|
* @param {...Object} styles - Style objects to merge (left-to-right)
|
|
6486
6988
|
* @returns {Object} Merged style object
|
|
6487
6989
|
* @category CSS & Styling
|
|
6488
|
-
* @see bw.u
|
|
6489
6990
|
* @example
|
|
6490
|
-
* var style = bw.s(
|
|
6991
|
+
* var style = bw.s({ display: 'flex' }, { gap: '1rem' }, { color: 'red' });
|
|
6491
6992
|
* // => { display: 'flex', gap: '1rem', color: 'red' }
|
|
6492
6993
|
*/
|
|
6493
6994
|
bw.s = function() {
|
|
6494
6995
|
var result = {};
|
|
6495
6996
|
for (var i = 0; i < arguments.length; i++) {
|
|
6496
6997
|
var arg = arguments[i];
|
|
6497
|
-
if (arg
|
|
6998
|
+
if (_is(arg, 'object')) Object.assign(result, arg);
|
|
6498
6999
|
}
|
|
6499
7000
|
return result;
|
|
6500
7001
|
};
|
|
6501
7002
|
|
|
6502
|
-
/**
|
|
6503
|
-
* Pre-built CSS utility objects (like Tailwind utilities, but in JS).
|
|
6504
|
-
*
|
|
6505
|
-
* Compose with `bw.s()` to build inline styles without writing raw CSS strings.
|
|
6506
|
-
* Includes flex, padding, margin, typography, color, border, and transition utilities.
|
|
6507
|
-
*
|
|
6508
|
-
* @category CSS & Styling
|
|
6509
|
-
* @see bw.s
|
|
6510
|
-
* @example
|
|
6511
|
-
* { t: 'div', a: { style: bw.s(bw.u.flex, bw.u.gap4, bw.u.p4) },
|
|
6512
|
-
* c: 'Flexbox with 1rem gap and padding' }
|
|
6513
|
-
*/
|
|
6514
|
-
bw.u = {
|
|
6515
|
-
// Display
|
|
6516
|
-
flex: { display: 'flex' },
|
|
6517
|
-
flexCol: { display: 'flex', flexDirection: 'column' },
|
|
6518
|
-
flexRow: { display: 'flex', flexDirection: 'row' },
|
|
6519
|
-
flexWrap: { display: 'flex', flexWrap: 'wrap' },
|
|
6520
|
-
block: { display: 'block' },
|
|
6521
|
-
inline: { display: 'inline' },
|
|
6522
|
-
hidden: { display: 'none' },
|
|
6523
|
-
|
|
6524
|
-
// Flex alignment
|
|
6525
|
-
justifyCenter: { justifyContent: 'center' },
|
|
6526
|
-
justifyBetween: { justifyContent: 'space-between' },
|
|
6527
|
-
justifyEnd: { justifyContent: 'flex-end' },
|
|
6528
|
-
alignCenter: { alignItems: 'center' },
|
|
6529
|
-
alignStart: { alignItems: 'flex-start' },
|
|
6530
|
-
alignEnd: { alignItems: 'flex-end' },
|
|
6531
|
-
|
|
6532
|
-
// Gap (0.25rem increments)
|
|
6533
|
-
gap1: { gap: '0.25rem' },
|
|
6534
|
-
gap2: { gap: '0.5rem' },
|
|
6535
|
-
gap3: { gap: '0.75rem' },
|
|
6536
|
-
gap4: { gap: '1rem' },
|
|
6537
|
-
gap6: { gap: '1.5rem' },
|
|
6538
|
-
gap8: { gap: '2rem' },
|
|
6539
|
-
|
|
6540
|
-
// Padding
|
|
6541
|
-
p0: { padding: '0' },
|
|
6542
|
-
p1: { padding: '0.25rem' },
|
|
6543
|
-
p2: { padding: '0.5rem' },
|
|
6544
|
-
p3: { padding: '0.75rem' },
|
|
6545
|
-
p4: { padding: '1rem' },
|
|
6546
|
-
p6: { padding: '1.5rem' },
|
|
6547
|
-
p8: { padding: '2rem' },
|
|
6548
|
-
px4: { paddingLeft: '1rem', paddingRight: '1rem' },
|
|
6549
|
-
py2: { paddingTop: '0.5rem', paddingBottom: '0.5rem' },
|
|
6550
|
-
py4: { paddingTop: '1rem', paddingBottom: '1rem' },
|
|
6551
|
-
|
|
6552
|
-
// Margin (same scale)
|
|
6553
|
-
m0: { margin: '0' },
|
|
6554
|
-
m4: { margin: '1rem' },
|
|
6555
|
-
mt2: { marginTop: '0.5rem' },
|
|
6556
|
-
mt4: { marginTop: '1rem' },
|
|
6557
|
-
mb2: { marginBottom: '0.5rem' },
|
|
6558
|
-
mb4: { marginBottom: '1rem' },
|
|
6559
|
-
mx_auto: { marginLeft: 'auto', marginRight: 'auto' },
|
|
6560
|
-
|
|
6561
|
-
// Typography
|
|
6562
|
-
textSm: { fontSize: '0.875rem' },
|
|
6563
|
-
textBase: { fontSize: '1rem' },
|
|
6564
|
-
textLg: { fontSize: '1.125rem' },
|
|
6565
|
-
textXl: { fontSize: '1.25rem' },
|
|
6566
|
-
text2xl: { fontSize: '1.5rem' },
|
|
6567
|
-
text3xl: { fontSize: '1.875rem' },
|
|
6568
|
-
bold: { fontWeight: '700' },
|
|
6569
|
-
semibold: { fontWeight: '600' },
|
|
6570
|
-
italic: { fontStyle: 'italic' },
|
|
6571
|
-
textCenter: { textAlign: 'center' },
|
|
6572
|
-
textRight: { textAlign: 'right' },
|
|
6573
|
-
|
|
6574
|
-
// Colors (from design tokens)
|
|
6575
|
-
bgWhite: { background: '#ffffff' },
|
|
6576
|
-
bgTeal: { background: '#006666', color: '#ffffff' },
|
|
6577
|
-
textWhite: { color: '#ffffff' },
|
|
6578
|
-
textTeal: { color: '#006666' },
|
|
6579
|
-
textMuted: { color: '#888' },
|
|
6580
|
-
|
|
6581
|
-
// Borders
|
|
6582
|
-
rounded: { borderRadius: '0.375rem' },
|
|
6583
|
-
roundedLg: { borderRadius: '0.5rem' },
|
|
6584
|
-
roundedFull: { borderRadius: '9999px' },
|
|
6585
|
-
border: { border: '1px solid #d8d8d8' },
|
|
6586
|
-
|
|
6587
|
-
// Sizing
|
|
6588
|
-
wFull: { width: '100%' },
|
|
6589
|
-
hFull: { height: '100%' },
|
|
6590
|
-
|
|
6591
|
-
// Transitions
|
|
6592
|
-
transition: { transition: 'all 0.2s ease' }
|
|
6593
|
-
};
|
|
6594
|
-
|
|
6595
7003
|
/**
|
|
6596
7004
|
* Generate responsive CSS with media query breakpoints.
|
|
6597
7005
|
*
|
|
@@ -6617,7 +7025,7 @@
|
|
|
6617
7025
|
bw.responsive = function(selector, breakpoints) {
|
|
6618
7026
|
var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
|
|
6619
7027
|
var parts = [];
|
|
6620
|
-
|
|
7028
|
+
_keys(breakpoints).forEach(function(key) {
|
|
6621
7029
|
var rules = {};
|
|
6622
7030
|
if (key === 'base') {
|
|
6623
7031
|
rules[selector] = breakpoints[key];
|
|
@@ -6689,18 +7097,18 @@
|
|
|
6689
7097
|
if (!selector) return [];
|
|
6690
7098
|
|
|
6691
7099
|
// Already an array
|
|
6692
|
-
if (
|
|
7100
|
+
if (_isA(selector)) return selector;
|
|
6693
7101
|
|
|
6694
7102
|
// Single element
|
|
6695
7103
|
if (selector.nodeType) return [selector];
|
|
6696
7104
|
|
|
6697
7105
|
// NodeList or HTMLCollection
|
|
6698
|
-
if (selector.length !== undefined &&
|
|
7106
|
+
if (selector.length !== undefined && !_is(selector, 'string')) {
|
|
6699
7107
|
return Array.from(selector);
|
|
6700
7108
|
}
|
|
6701
7109
|
|
|
6702
7110
|
// CSS selector string
|
|
6703
|
-
if (
|
|
7111
|
+
if (_is(selector, 'string')) {
|
|
6704
7112
|
return Array.from(document.querySelectorAll(selector));
|
|
6705
7113
|
}
|
|
6706
7114
|
|
|
@@ -6713,103 +7121,49 @@
|
|
|
6713
7121
|
};
|
|
6714
7122
|
}
|
|
6715
7123
|
|
|
6716
|
-
/**
|
|
6717
|
-
* Load the built-in Bootstrap-inspired default stylesheet.
|
|
6718
|
-
*
|
|
6719
|
-
* Injects bitwrench's batteries-included CSS (buttons, cards, grids, forms,
|
|
6720
|
-
* alerts, badges, nav, tabs, etc.) into the document head. Call once at app startup.
|
|
6721
|
-
* Returns null in Node.js (no DOM).
|
|
6722
|
-
*
|
|
6723
|
-
* @param {Object} [options] - Style loading options
|
|
6724
|
-
* @param {boolean} [options.minify=true] - Minify the CSS output
|
|
6725
|
-
* @returns {Element|null} Style element if in browser, null in Node.js
|
|
6726
|
-
* @category CSS & Styling
|
|
6727
|
-
* @see bw.setTheme
|
|
6728
|
-
* @see bw.applyTheme
|
|
6729
|
-
* @see bw.toggleTheme
|
|
6730
|
-
* @example
|
|
6731
|
-
* bw.loadDefaultStyles(); // inject all default CSS
|
|
6732
|
-
*/
|
|
6733
|
-
bw.loadDefaultStyles = function(options = {}) {
|
|
6734
|
-
const { minify = true, palette } = options;
|
|
6735
|
-
|
|
6736
|
-
// 1. Inject structural CSS (layout, sizing — never changes with theme)
|
|
6737
|
-
if (bw._isBrowser) {
|
|
6738
|
-
var structuralCSS = bw.css(getStructuralStyles());
|
|
6739
|
-
bw.injectCSS(structuralCSS, { id: 'bw_structural', append: false, minify: minify });
|
|
6740
|
-
}
|
|
6741
7124
|
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6745
|
-
return result;
|
|
6746
|
-
};
|
|
7125
|
+
// =========================================================================
|
|
7126
|
+
// v2.0.18 Clean Styles API — makeStyles / applyStyles / loadStyles / etc.
|
|
7127
|
+
// =========================================================================
|
|
6747
7128
|
|
|
7129
|
+
/**
|
|
7130
|
+
* Convert a scope selector to a <style> element id.
|
|
7131
|
+
* @private
|
|
7132
|
+
* @param {string} [scope] - Scope selector (e.g. '#my-dashboard', '.preview')
|
|
7133
|
+
* @returns {string} Style element id (e.g. 'bw_style_my_dashboard')
|
|
7134
|
+
*/
|
|
7135
|
+
function _scopeToStyleId(scope) {
|
|
7136
|
+
if (!scope || scope === '' || scope === 'global') return 'bw_style_global';
|
|
7137
|
+
if (scope === 'reset') return 'bw_style_reset';
|
|
7138
|
+
// Strip leading # or . and convert - to _
|
|
7139
|
+
var clean = scope.replace(/^[#.]/, '').replace(/-/g, '_');
|
|
7140
|
+
return 'bw_style_' + clean;
|
|
7141
|
+
}
|
|
6748
7142
|
|
|
6749
7143
|
/**
|
|
6750
|
-
* Generate a complete
|
|
7144
|
+
* Generate a complete styles object from seed colors and layout config.
|
|
7145
|
+
* Pure function — no DOM, no state, no side effects.
|
|
6751
7146
|
*
|
|
6752
|
-
*
|
|
6753
|
-
* forms, nav, tables, tabs, list groups, pagination, progress, hero, utilities)
|
|
6754
|
-
* scoped under `.name` class. Multiple themes can coexist in the stylesheet.
|
|
6755
|
-
* Swap themes by changing the class on a container element.
|
|
7147
|
+
* All parameters are optional. Defaults to the bitwrench default palette.
|
|
6756
7148
|
*
|
|
6757
|
-
* @param {
|
|
6758
|
-
* @param {
|
|
6759
|
-
* @param {string} config.
|
|
6760
|
-
* @param {string} config.
|
|
6761
|
-
* @param {string} [config.tertiary] - Tertiary/accent color hex (defaults to primary)
|
|
6762
|
-
* @param {string} [config.success='#198754'] - Success color hex
|
|
6763
|
-
* @param {string} [config.danger='#dc3545'] - Danger color hex
|
|
6764
|
-
* @param {string} [config.warning='#ffc107'] - Warning color hex
|
|
6765
|
-
* @param {string} [config.info='#0dcaf0'] - Info color hex
|
|
6766
|
-
* @param {string} [config.light='#f8f9fa'] - Light color hex
|
|
6767
|
-
* @param {string} [config.dark='#212529'] - Dark color hex
|
|
6768
|
-
* @param {string} [config.background] - Page background hex (default: '#ffffff' light, derived dark)
|
|
6769
|
-
* @param {string} [config.surface] - Surface/card background hex (default: '#f8f9fa' light, derived dark)
|
|
7149
|
+
* @param {Object} [config] - Style configuration
|
|
7150
|
+
* @param {string} [config.primary='#006666'] - Primary brand color hex
|
|
7151
|
+
* @param {string} [config.secondary='#6c757d'] - Secondary color hex
|
|
7152
|
+
* @param {string} [config.tertiary] - Tertiary color hex (defaults to primary)
|
|
6770
7153
|
* @param {string} [config.spacing='normal'] - 'compact' | 'normal' | 'spacious'
|
|
6771
7154
|
* @param {string} [config.radius='md'] - 'none' | 'sm' | 'md' | 'lg' | 'pill'
|
|
6772
|
-
* @
|
|
6773
|
-
* @param {string|number} [config.typeRatio='normal'] - 'tight' | 'normal' | 'relaxed' | 'dramatic' or a number
|
|
6774
|
-
* @param {string} [config.elevation='md'] - 'flat' | 'sm' | 'md' | 'lg'
|
|
6775
|
-
* @param {string} [config.motion='standard'] - 'reduced' | 'standard' | 'expressive'
|
|
6776
|
-
* @param {number} [config.harmonize=0.20] - 0-1, semantic color hue shift toward primary
|
|
6777
|
-
* @param {boolean} [config.inject=true] - Inject into DOM (browser only)
|
|
6778
|
-
* @returns {Object} { css, palette, name, isLightPrimary, alternate: { css, palette } }
|
|
7155
|
+
* @returns {Object} { css, alternateCss, rules, alternateRules, palette, alternatePalette, isLightPrimary }
|
|
6779
7156
|
* @category CSS & Styling
|
|
6780
|
-
* @see bw.
|
|
6781
|
-
* @see bw.
|
|
6782
|
-
* @see bw.loadDefaultStyles
|
|
7157
|
+
* @see bw.applyStyles
|
|
7158
|
+
* @see bw.loadStyles
|
|
6783
7159
|
* @example
|
|
6784
|
-
*
|
|
6785
|
-
*
|
|
6786
|
-
*
|
|
6787
|
-
* secondary: '#90e0ef',
|
|
6788
|
-
* tertiary: '#00b4d8'
|
|
6789
|
-
* });
|
|
6790
|
-
*
|
|
6791
|
-
* // Apply to a container
|
|
6792
|
-
* document.getElementById('app').classList.add('ocean');
|
|
6793
|
-
*
|
|
6794
|
-
* // Toggle to alternate palette
|
|
6795
|
-
* bw.toggleTheme();
|
|
6796
|
-
*
|
|
6797
|
-
* // Generate CSS for static export (Node.js)
|
|
6798
|
-
* var result = bw.generateTheme('sunset', {
|
|
6799
|
-
* primary: '#e76f51',
|
|
6800
|
-
* secondary: '#264653',
|
|
6801
|
-
* inject: false
|
|
6802
|
-
* });
|
|
6803
|
-
* fs.writeFileSync('sunset.css', result.css + result.alternate.css);
|
|
7160
|
+
* var styles = bw.makeStyles({ primary: '#4f46e5', secondary: '#d97706' });
|
|
7161
|
+
* console.log(styles.palette.primary.base); // '#4f46e5'
|
|
7162
|
+
* // styles.css contains all themed CSS — nothing injected
|
|
6804
7163
|
*/
|
|
6805
|
-
bw.
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
}
|
|
6809
|
-
|
|
6810
|
-
// Merge with defaults; if user didn't supply tertiary, default to their primary
|
|
6811
|
-
var fullConfig = Object.assign({}, DEFAULT_PALETTE_CONFIG, config);
|
|
6812
|
-
if (!config.tertiary) fullConfig.tertiary = fullConfig.primary;
|
|
7164
|
+
bw.makeStyles = function(config) {
|
|
7165
|
+
var fullConfig = Object.assign({}, DEFAULT_PALETTE_CONFIG, config || {});
|
|
7166
|
+
if (config && !config.tertiary) fullConfig.tertiary = fullConfig.primary;
|
|
6813
7167
|
|
|
6814
7168
|
// Derive primary palette
|
|
6815
7169
|
var palette = derivePalette(fullConfig);
|
|
@@ -6817,131 +7171,207 @@
|
|
|
6817
7171
|
// Resolve layout
|
|
6818
7172
|
var layout = resolveLayout(fullConfig);
|
|
6819
7173
|
|
|
6820
|
-
// Generate primary themed CSS rules
|
|
6821
|
-
var themedRules = generateThemedCSS(
|
|
7174
|
+
// Generate primary themed CSS rules (unscoped)
|
|
7175
|
+
var themedRules = generateThemedCSS('', palette, layout);
|
|
6822
7176
|
var cssStr = bw.css(themedRules);
|
|
6823
7177
|
|
|
6824
7178
|
// Derive alternate palette (luminance-inverted)
|
|
6825
7179
|
var altConfig = deriveAlternateConfig(fullConfig);
|
|
6826
7180
|
var altPalette = derivePalette(altConfig);
|
|
6827
7181
|
|
|
6828
|
-
// Generate alternate CSS
|
|
6829
|
-
|
|
6830
|
-
var
|
|
7182
|
+
// Generate alternate CSS rules WITHOUT .bw_theme_alt prefix (raw rules)
|
|
7183
|
+
// applyStyles() wraps them appropriately based on scope
|
|
7184
|
+
var altRawRules = generateThemedCSS('', altPalette, layout);
|
|
7185
|
+
|
|
7186
|
+
// Add body-level surface overrides for the alternate palette.
|
|
7187
|
+
// When .bw_theme_alt is on <html>, ".bw_theme_alt body" correctly matches.
|
|
7188
|
+
altRawRules['body'] = {
|
|
7189
|
+
'color': altPalette.dark.base,
|
|
7190
|
+
'background-color': altPalette.surface || altPalette.light.base
|
|
7191
|
+
};
|
|
7192
|
+
|
|
7193
|
+
var altCssStr = bw.css(altRawRules);
|
|
6831
7194
|
|
|
6832
7195
|
// Determine if primary is light-flavored
|
|
6833
7196
|
var lightPrimary = isLightPalette(fullConfig);
|
|
6834
7197
|
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
7198
|
+
return {
|
|
7199
|
+
css: cssStr,
|
|
7200
|
+
alternateCss: altCssStr,
|
|
7201
|
+
rules: themedRules,
|
|
7202
|
+
alternateRules: altRawRules,
|
|
7203
|
+
palette: palette,
|
|
7204
|
+
alternatePalette: altPalette,
|
|
7205
|
+
isLightPrimary: lightPrimary
|
|
7206
|
+
};
|
|
7207
|
+
};
|
|
6844
7208
|
|
|
6845
|
-
|
|
7209
|
+
/**
|
|
7210
|
+
* Inject styles into the DOM with optional scoping.
|
|
7211
|
+
*
|
|
7212
|
+
* Takes a styles object from `makeStyles()` and creates a single `<style>`
|
|
7213
|
+
* element in `<head>`. If a scope selector is provided, all CSS rules are
|
|
7214
|
+
* wrapped under that selector. Alternate CSS is wrapped under `.bw_theme_alt`.
|
|
7215
|
+
*
|
|
7216
|
+
* @param {Object} styles - Result of `bw.makeStyles()`
|
|
7217
|
+
* @param {string} [scope] - Scope selector (e.g. '#my-dashboard', '.preview'). Omit for global.
|
|
7218
|
+
* @returns {Element|null} The `<style>` element, or null in Node.js
|
|
7219
|
+
* @category CSS & Styling
|
|
7220
|
+
* @see bw.makeStyles
|
|
7221
|
+
* @see bw.loadStyles
|
|
7222
|
+
* @see bw.clearStyles
|
|
7223
|
+
* @example
|
|
7224
|
+
* var styles = bw.makeStyles({ primary: '#4f46e5' });
|
|
7225
|
+
* bw.applyStyles(styles); // global
|
|
7226
|
+
* bw.applyStyles(styles, '#my-dashboard'); // scoped
|
|
7227
|
+
*/
|
|
7228
|
+
bw.applyStyles = function(styles, scope) {
|
|
7229
|
+
if (!bw._isBrowser) return null;
|
|
7230
|
+
if (!styles || !styles.rules) {
|
|
7231
|
+
_cw('bw.applyStyles: invalid styles object');
|
|
7232
|
+
return null;
|
|
6846
7233
|
}
|
|
6847
7234
|
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
7235
|
+
var styleId = _scopeToStyleId(scope);
|
|
7236
|
+
|
|
7237
|
+
// Scope the primary rules if a scope is provided
|
|
7238
|
+
var primaryRules = styles.rules;
|
|
7239
|
+
if (scope) {
|
|
7240
|
+
primaryRules = scopeRulesUnder(primaryRules, scope);
|
|
6854
7241
|
}
|
|
6855
7242
|
|
|
6856
|
-
//
|
|
6857
|
-
var
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
7243
|
+
// Wrap alternate rules with .bw_theme_alt
|
|
7244
|
+
var altRules = styles.alternateRules;
|
|
7245
|
+
if (altRules) {
|
|
7246
|
+
if (scope) {
|
|
7247
|
+
// Scoped compound: #scope.bw_theme_alt .bw_card
|
|
7248
|
+
altRules = scopeRulesUnder(altRules, scope + '.bw_theme_alt');
|
|
7249
|
+
} else {
|
|
7250
|
+
// Global: .bw_theme_alt .bw_card
|
|
7251
|
+
altRules = scopeRulesUnder(altRules, '.bw_theme_alt');
|
|
6865
7252
|
}
|
|
6866
|
-
}
|
|
6867
|
-
bw._activeTheme = result;
|
|
6868
|
-
bw._activeThemeMode = 'primary';
|
|
7253
|
+
}
|
|
6869
7254
|
|
|
6870
|
-
|
|
7255
|
+
// Combine primary + alternate into one CSS string
|
|
7256
|
+
var combined = bw.css(primaryRules);
|
|
7257
|
+
if (altRules) {
|
|
7258
|
+
combined += '\n' + bw.css(altRules);
|
|
7259
|
+
}
|
|
7260
|
+
|
|
7261
|
+
return bw.injectCSS(combined, { id: styleId, append: false });
|
|
6871
7262
|
};
|
|
6872
7263
|
|
|
6873
7264
|
/**
|
|
6874
|
-
*
|
|
6875
|
-
*
|
|
7265
|
+
* Generate and apply styles in one call. Convenience wrapper.
|
|
7266
|
+
*
|
|
7267
|
+
* Equivalent to: `bw.applyStyles(bw.makeStyles(config), scope)`
|
|
6876
7268
|
*
|
|
6877
|
-
* @param {
|
|
6878
|
-
* @
|
|
7269
|
+
* @param {Object} [config] - Style configuration (same as `makeStyles`)
|
|
7270
|
+
* @param {string} [scope] - Scope selector (same as `applyStyles`)
|
|
7271
|
+
* @returns {Element|null} The `<style>` element, or null in Node.js
|
|
6879
7272
|
* @category CSS & Styling
|
|
6880
|
-
* @see bw.
|
|
6881
|
-
* @see bw.
|
|
7273
|
+
* @see bw.makeStyles
|
|
7274
|
+
* @see bw.applyStyles
|
|
6882
7275
|
* @example
|
|
6883
|
-
* bw.
|
|
6884
|
-
* bw.
|
|
6885
|
-
* bw.
|
|
6886
|
-
*/
|
|
6887
|
-
bw.
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
else if (mode === 'light') wantAlt = !isLight;
|
|
6896
|
-
else if (mode === 'dark') wantAlt = isLight;
|
|
6897
|
-
else wantAlt = false;
|
|
6898
|
-
|
|
6899
|
-
if (wantAlt) {
|
|
6900
|
-
root.classList.add('bw_theme_alt');
|
|
6901
|
-
} else {
|
|
6902
|
-
root.classList.remove('bw_theme_alt');
|
|
7276
|
+
* bw.loadStyles(); // defaults, global
|
|
7277
|
+
* bw.loadStyles({ primary: '#4f46e5' }); // custom, global
|
|
7278
|
+
* bw.loadStyles({ primary: '#4f46e5' }, '#my-dashboard'); // custom, scoped
|
|
7279
|
+
*/
|
|
7280
|
+
bw.loadStyles = function(config, scope) {
|
|
7281
|
+
// Also inject structural CSS first (only once)
|
|
7282
|
+
if (bw._isBrowser) {
|
|
7283
|
+
var existing = document.getElementById('bw_structural');
|
|
7284
|
+
if (!existing) {
|
|
7285
|
+
var structuralCSS = bw.css(getStructuralStyles());
|
|
7286
|
+
bw.injectCSS(structuralCSS, { id: 'bw_structural', append: false });
|
|
7287
|
+
}
|
|
6903
7288
|
}
|
|
7289
|
+
return bw.applyStyles(bw.makeStyles(config), scope);
|
|
7290
|
+
};
|
|
6904
7291
|
|
|
6905
|
-
|
|
6906
|
-
|
|
7292
|
+
/**
|
|
7293
|
+
* Inject the CSS reset (box-sizing, html/body font, reduced-motion).
|
|
7294
|
+
* Idempotent — if already injected, returns the existing `<style>` element.
|
|
7295
|
+
*
|
|
7296
|
+
* @returns {Element|null} The `<style>` element, or null in Node.js
|
|
7297
|
+
* @category CSS & Styling
|
|
7298
|
+
* @see bw.loadStyles
|
|
7299
|
+
* @see bw.clearStyles
|
|
7300
|
+
* @example
|
|
7301
|
+
* bw.loadReset(); // inject once, safe to call multiple times
|
|
7302
|
+
*/
|
|
7303
|
+
bw.loadReset = function() {
|
|
7304
|
+
if (!bw._isBrowser) return null;
|
|
7305
|
+
var existing = document.getElementById('bw_style_reset');
|
|
7306
|
+
if (existing) return existing;
|
|
7307
|
+
return bw.injectCSS(bw.css(getResetStyles()), { id: 'bw_style_reset', append: false });
|
|
6907
7308
|
};
|
|
6908
7309
|
|
|
6909
7310
|
/**
|
|
6910
|
-
* Toggle between primary and alternate
|
|
7311
|
+
* Toggle between primary and alternate palettes.
|
|
7312
|
+
*
|
|
7313
|
+
* Adds/removes the `bw_theme_alt` class on the scoping element.
|
|
7314
|
+
* Without a scope, toggles on `<html>` (global).
|
|
7315
|
+
* With a scope, toggles on the first matching element.
|
|
6911
7316
|
*
|
|
7317
|
+
* @param {string} [scope] - Scope selector (e.g. '#my-dashboard'). Omit for global.
|
|
6912
7318
|
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
6913
7319
|
* @category CSS & Styling
|
|
6914
|
-
* @see bw.
|
|
6915
|
-
* @see bw.
|
|
7320
|
+
* @see bw.applyStyles
|
|
7321
|
+
* @see bw.clearStyles
|
|
6916
7322
|
* @example
|
|
6917
|
-
* bw.
|
|
7323
|
+
* bw.toggleStyles(); // global toggle on <html>
|
|
7324
|
+
* bw.toggleStyles('#my-dashboard'); // scoped toggle
|
|
6918
7325
|
*/
|
|
6919
|
-
bw.
|
|
6920
|
-
|
|
6921
|
-
|
|
7326
|
+
bw.toggleStyles = function(scope) {
|
|
7327
|
+
if (!bw._isBrowser) return 'primary';
|
|
7328
|
+
var target;
|
|
7329
|
+
if (scope) {
|
|
7330
|
+
var els = bw.$(scope);
|
|
7331
|
+
target = els[0];
|
|
7332
|
+
} else {
|
|
7333
|
+
target = document.documentElement;
|
|
7334
|
+
}
|
|
7335
|
+
if (!target) return 'primary';
|
|
7336
|
+
|
|
7337
|
+
var hasAlt = target.classList.contains('bw_theme_alt');
|
|
7338
|
+
if (hasAlt) {
|
|
7339
|
+
target.classList.remove('bw_theme_alt');
|
|
7340
|
+
return 'primary';
|
|
7341
|
+
} else {
|
|
7342
|
+
target.classList.add('bw_theme_alt');
|
|
7343
|
+
return 'alternate';
|
|
7344
|
+
}
|
|
6922
7345
|
};
|
|
6923
7346
|
|
|
6924
7347
|
/**
|
|
6925
|
-
* Remove
|
|
6926
|
-
*
|
|
6927
|
-
*
|
|
7348
|
+
* Remove injected styles for a given scope.
|
|
7349
|
+
*
|
|
7350
|
+
* Finds the `<style>` element by id and removes it. Also removes
|
|
7351
|
+
* the `bw_theme_alt` class from the relevant element.
|
|
6928
7352
|
*
|
|
7353
|
+
* @param {string} [scope] - Scope selector. Omit to remove global styles.
|
|
6929
7354
|
* @category CSS & Styling
|
|
6930
|
-
* @see bw.
|
|
7355
|
+
* @see bw.applyStyles
|
|
7356
|
+
* @see bw.loadStyles
|
|
6931
7357
|
* @example
|
|
6932
|
-
* bw.
|
|
6933
|
-
* bw.
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
7358
|
+
* bw.clearStyles(); // remove global styles
|
|
7359
|
+
* bw.clearStyles('#my-dashboard'); // remove scoped styles
|
|
7360
|
+
* bw.clearStyles('reset'); // remove the CSS reset
|
|
7361
|
+
*/
|
|
7362
|
+
bw.clearStyles = function(scope) {
|
|
7363
|
+
if (!bw._isBrowser) return;
|
|
7364
|
+
var styleId = _scopeToStyleId(scope);
|
|
7365
|
+
var el = document.getElementById(styleId);
|
|
7366
|
+
if (el) el.remove();
|
|
7367
|
+
|
|
7368
|
+
// Also remove bw_theme_alt from the relevant element
|
|
7369
|
+
if (scope && scope !== 'reset' && scope !== 'global') {
|
|
7370
|
+
var targets = bw.$(scope);
|
|
7371
|
+
if (targets[0]) targets[0].classList.remove('bw_theme_alt');
|
|
7372
|
+
} else if (!scope || scope === 'global') {
|
|
7373
|
+
document.documentElement.classList.remove('bw_theme_alt');
|
|
6942
7374
|
}
|
|
6943
|
-
bw._activeTheme = null;
|
|
6944
|
-
bw._activeThemeMode = 'primary';
|
|
6945
7375
|
};
|
|
6946
7376
|
|
|
6947
7377
|
// Expose color utility functions on bw namespace
|
|
@@ -7164,10 +7594,15 @@
|
|
|
7164
7594
|
* @param {Object} config - Table configuration
|
|
7165
7595
|
* @param {Array<Object>} config.data - Array of row objects to display
|
|
7166
7596
|
* @param {Array<Object>} [config.columns] - Column definitions with key, label, render
|
|
7167
|
-
* @param {string} [config.className='
|
|
7597
|
+
* @param {string} [config.className=''] - Additional CSS classes for table element
|
|
7168
7598
|
* @param {boolean} [config.sortable=true] - Enable click-to-sort headers
|
|
7169
7599
|
* @param {Function} [config.onSort] - Sort callback (column, direction)
|
|
7170
|
-
* @
|
|
7600
|
+
* @param {boolean} [config.selectable=false] - Enable row selection on click
|
|
7601
|
+
* @param {Function} [config.onRowClick] - Row click callback (row, index, event)
|
|
7602
|
+
* @param {number} [config.pageSize] - Rows per page (enables pagination when set)
|
|
7603
|
+
* @param {number} [config.currentPage=1] - Current page number (1-based)
|
|
7604
|
+
* @param {Function} [config.onPageChange] - Page change callback (newPage)
|
|
7605
|
+
* @returns {Object} TACO object for table (with optional pagination controls)
|
|
7171
7606
|
* @category Component Builders
|
|
7172
7607
|
* @see bw.makeDataTable
|
|
7173
7608
|
* @example
|
|
@@ -7179,7 +7614,12 @@
|
|
|
7179
7614
|
* columns: [
|
|
7180
7615
|
* { key: 'name', label: 'Name' },
|
|
7181
7616
|
* { key: 'age', label: 'Age' }
|
|
7182
|
-
* ]
|
|
7617
|
+
* ],
|
|
7618
|
+
* selectable: true,
|
|
7619
|
+
* onRowClick: function(row, i) { console.log('clicked', row.name); },
|
|
7620
|
+
* pageSize: 10,
|
|
7621
|
+
* currentPage: 1,
|
|
7622
|
+
* onPageChange: function(page) { console.log('page', page); }
|
|
7183
7623
|
* });
|
|
7184
7624
|
*/
|
|
7185
7625
|
bw.makeTable = function(config) {
|
|
@@ -7192,41 +7632,47 @@
|
|
|
7192
7632
|
sortable = true,
|
|
7193
7633
|
onSort,
|
|
7194
7634
|
sortColumn,
|
|
7195
|
-
sortDirection = 'asc'
|
|
7635
|
+
sortDirection = 'asc',
|
|
7636
|
+
selectable = false,
|
|
7637
|
+
onRowClick,
|
|
7638
|
+
pageSize,
|
|
7639
|
+
currentPage = 1,
|
|
7640
|
+
onPageChange
|
|
7196
7641
|
} = config;
|
|
7197
7642
|
|
|
7198
|
-
// Build class list: always include bw_table, add striped/hover, append user className
|
|
7643
|
+
// Build class list: always include bw_table, add striped/hover/selectable, append user className
|
|
7199
7644
|
let cls = 'bw_table';
|
|
7200
7645
|
if (striped) cls += ' bw_table_striped';
|
|
7201
|
-
if (hover) cls += ' bw_table_hover';
|
|
7646
|
+
if (hover || selectable) cls += ' bw_table_hover';
|
|
7647
|
+
if (selectable) cls += ' bw_table_selectable';
|
|
7202
7648
|
if (className) cls += ' ' + className;
|
|
7203
7649
|
cls = cls.trim();
|
|
7204
|
-
|
|
7650
|
+
|
|
7205
7651
|
// Auto-detect columns if not provided
|
|
7206
|
-
const cols = columns || (data.length > 0
|
|
7207
|
-
?
|
|
7652
|
+
const cols = columns || (data.length > 0
|
|
7653
|
+
? _keys(data[0]).map(key => ({ key, label: key }))
|
|
7208
7654
|
: []);
|
|
7209
|
-
|
|
7655
|
+
|
|
7210
7656
|
// Current sort state
|
|
7211
7657
|
let currentSortColumn = sortColumn || null;
|
|
7212
7658
|
let currentSortDirection = sortDirection;
|
|
7213
|
-
|
|
7659
|
+
|
|
7214
7660
|
// Sort data if column specified
|
|
7215
7661
|
let sortedData = [...data];
|
|
7216
7662
|
if (currentSortColumn) {
|
|
7217
7663
|
sortedData.sort((a, b) => {
|
|
7218
7664
|
const aVal = a[currentSortColumn];
|
|
7219
7665
|
const bVal = b[currentSortColumn];
|
|
7220
|
-
|
|
7666
|
+
|
|
7221
7667
|
// Handle different types
|
|
7222
|
-
if (
|
|
7668
|
+
if (_is(aVal, 'number') && _is(bVal, 'number')) {
|
|
7223
7669
|
return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
|
|
7224
7670
|
}
|
|
7225
|
-
|
|
7671
|
+
|
|
7226
7672
|
// String comparison
|
|
7227
7673
|
const aStr = String(aVal || '').toLowerCase();
|
|
7228
7674
|
const bStr = String(bVal || '').toLowerCase();
|
|
7229
|
-
|
|
7675
|
+
|
|
7230
7676
|
if (currentSortDirection === 'asc') {
|
|
7231
7677
|
return aStr.localeCompare(bStr);
|
|
7232
7678
|
} else {
|
|
@@ -7234,23 +7680,32 @@
|
|
|
7234
7680
|
}
|
|
7235
7681
|
});
|
|
7236
7682
|
}
|
|
7237
|
-
|
|
7683
|
+
|
|
7684
|
+
// Pagination
|
|
7685
|
+
const totalRows = sortedData.length;
|
|
7686
|
+
const totalPages = pageSize ? Math.max(1, Math.ceil(totalRows / pageSize)) : 1;
|
|
7687
|
+
const page = Math.max(1, Math.min(currentPage, totalPages));
|
|
7688
|
+
if (pageSize) {
|
|
7689
|
+
const start = (page - 1) * pageSize;
|
|
7690
|
+
sortedData = sortedData.slice(start, start + pageSize);
|
|
7691
|
+
}
|
|
7692
|
+
|
|
7238
7693
|
// Create sort handler
|
|
7239
7694
|
const handleSort = (column) => {
|
|
7240
7695
|
if (!sortable) return;
|
|
7241
|
-
|
|
7696
|
+
|
|
7242
7697
|
if (currentSortColumn === column) {
|
|
7243
7698
|
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
|
|
7244
7699
|
} else {
|
|
7245
7700
|
currentSortColumn = column;
|
|
7246
7701
|
currentSortDirection = 'asc';
|
|
7247
7702
|
}
|
|
7248
|
-
|
|
7703
|
+
|
|
7249
7704
|
if (onSort) {
|
|
7250
7705
|
onSort(column, currentSortDirection);
|
|
7251
7706
|
}
|
|
7252
7707
|
};
|
|
7253
|
-
|
|
7708
|
+
|
|
7254
7709
|
// Build table header
|
|
7255
7710
|
const thead = {
|
|
7256
7711
|
t: 'thead',
|
|
@@ -7273,24 +7728,87 @@
|
|
|
7273
7728
|
}))
|
|
7274
7729
|
}
|
|
7275
7730
|
};
|
|
7276
|
-
|
|
7277
|
-
// Build table body
|
|
7731
|
+
|
|
7732
|
+
// Build table body with selectable/onRowClick support
|
|
7278
7733
|
const tbody = {
|
|
7279
7734
|
t: 'tbody',
|
|
7280
|
-
c: sortedData.map(row =>
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7735
|
+
c: sortedData.map((row, idx) => {
|
|
7736
|
+
const globalIdx = pageSize ? (page - 1) * pageSize + idx : idx;
|
|
7737
|
+
const rowAttrs = {};
|
|
7738
|
+
if (selectable || onRowClick) {
|
|
7739
|
+
rowAttrs.style = 'cursor:pointer;';
|
|
7740
|
+
rowAttrs.onclick = function(e) {
|
|
7741
|
+
if (selectable) {
|
|
7742
|
+
// Toggle selected class on this row
|
|
7743
|
+
var tr = e.currentTarget;
|
|
7744
|
+
tr.classList.toggle('bw_table_row_selected');
|
|
7745
|
+
}
|
|
7746
|
+
if (onRowClick) {
|
|
7747
|
+
onRowClick(row, globalIdx, e);
|
|
7748
|
+
}
|
|
7749
|
+
};
|
|
7750
|
+
}
|
|
7751
|
+
return {
|
|
7752
|
+
t: 'tr',
|
|
7753
|
+
a: rowAttrs,
|
|
7754
|
+
c: cols.map(col => ({
|
|
7755
|
+
t: 'td',
|
|
7756
|
+
c: col.render ? col.render(row[col.key], row) : String(row[col.key] || '')
|
|
7757
|
+
}))
|
|
7758
|
+
};
|
|
7759
|
+
})
|
|
7287
7760
|
};
|
|
7288
|
-
|
|
7289
|
-
|
|
7761
|
+
|
|
7762
|
+
const table = {
|
|
7290
7763
|
t: 'table',
|
|
7291
7764
|
a: { class: cls },
|
|
7292
7765
|
c: [thead, tbody]
|
|
7293
7766
|
};
|
|
7767
|
+
|
|
7768
|
+
// If no pagination, return table directly
|
|
7769
|
+
if (!pageSize) return table;
|
|
7770
|
+
|
|
7771
|
+
// Build pagination controls
|
|
7772
|
+
const pageButtons = [];
|
|
7773
|
+
// Previous button
|
|
7774
|
+
pageButtons.push({
|
|
7775
|
+
t: 'button',
|
|
7776
|
+
a: {
|
|
7777
|
+
class: 'bw_btn bw_btn_sm',
|
|
7778
|
+
disabled: page <= 1 ? 'disabled' : undefined,
|
|
7779
|
+
onclick: page > 1 && onPageChange ? function() { onPageChange(page - 1); } : undefined
|
|
7780
|
+
},
|
|
7781
|
+
c: 'Prev'
|
|
7782
|
+
});
|
|
7783
|
+
// Page info
|
|
7784
|
+
pageButtons.push({
|
|
7785
|
+
t: 'span',
|
|
7786
|
+
a: { style: 'margin:0 0.5rem;font-size:0.875rem;' },
|
|
7787
|
+
c: 'Page ' + page + ' of ' + totalPages
|
|
7788
|
+
});
|
|
7789
|
+
// Next button
|
|
7790
|
+
pageButtons.push({
|
|
7791
|
+
t: 'button',
|
|
7792
|
+
a: {
|
|
7793
|
+
class: 'bw_btn bw_btn_sm',
|
|
7794
|
+
disabled: page >= totalPages ? 'disabled' : undefined,
|
|
7795
|
+
onclick: page < totalPages && onPageChange ? function() { onPageChange(page + 1); } : undefined
|
|
7796
|
+
},
|
|
7797
|
+
c: 'Next'
|
|
7798
|
+
});
|
|
7799
|
+
|
|
7800
|
+
return {
|
|
7801
|
+
t: 'div',
|
|
7802
|
+
a: { class: 'bw_table_paginated' },
|
|
7803
|
+
c: [
|
|
7804
|
+
table,
|
|
7805
|
+
{
|
|
7806
|
+
t: 'div',
|
|
7807
|
+
a: { class: 'bw_table_pagination', style: 'display:flex;align-items:center;justify-content:flex-end;padding:0.5rem 0;gap:0.25rem;' },
|
|
7808
|
+
c: pageButtons
|
|
7809
|
+
}
|
|
7810
|
+
]
|
|
7811
|
+
};
|
|
7294
7812
|
};
|
|
7295
7813
|
|
|
7296
7814
|
/**
|
|
@@ -7329,7 +7847,7 @@
|
|
|
7329
7847
|
bw.makeTableFromArray = function(config) {
|
|
7330
7848
|
const { data = [], headerRow = true, columns, ...rest } = config;
|
|
7331
7849
|
|
|
7332
|
-
if (!
|
|
7850
|
+
if (!_isA(data) || data.length === 0) {
|
|
7333
7851
|
return bw.makeTable({ data: [], columns: columns || [], ...rest });
|
|
7334
7852
|
}
|
|
7335
7853
|
|
|
@@ -7411,7 +7929,7 @@
|
|
|
7411
7929
|
className = ''
|
|
7412
7930
|
} = config;
|
|
7413
7931
|
|
|
7414
|
-
if (!
|
|
7932
|
+
if (!_isA(data) || data.length === 0) {
|
|
7415
7933
|
return { t: 'div', a: { class: ('bw_bar_chart_container ' + className).trim() }, c: '' };
|
|
7416
7934
|
}
|
|
7417
7935
|
|
|
@@ -7560,7 +8078,7 @@
|
|
|
7560
8078
|
*/
|
|
7561
8079
|
bw.render = function(element, position, taco) {
|
|
7562
8080
|
// Get target element
|
|
7563
|
-
const targetEl =
|
|
8081
|
+
const targetEl = _is(element, 'string')
|
|
7564
8082
|
? document.querySelector(element)
|
|
7565
8083
|
: element;
|
|
7566
8084
|
|
|
@@ -7710,7 +8228,7 @@
|
|
|
7710
8228
|
setContent(content) {
|
|
7711
8229
|
this._taco.c = content;
|
|
7712
8230
|
if (this.element) {
|
|
7713
|
-
if (
|
|
8231
|
+
if (_is(content, 'string')) {
|
|
7714
8232
|
this.element.textContent = content;
|
|
7715
8233
|
} else {
|
|
7716
8234
|
// Re-render for complex content
|