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.
Files changed (68) hide show
  1. package/README.md +127 -38
  2. package/dist/bitwrench-bccl.cjs.js +13 -9
  3. package/dist/bitwrench-bccl.cjs.min.js +2 -2
  4. package/dist/bitwrench-bccl.esm.js +13 -9
  5. package/dist/bitwrench-bccl.esm.min.js +2 -2
  6. package/dist/bitwrench-bccl.umd.js +13 -9
  7. package/dist/bitwrench-bccl.umd.min.js +2 -2
  8. package/dist/bitwrench-code-edit.cjs.js +1 -1
  9. package/dist/bitwrench-code-edit.cjs.min.js +1 -1
  10. package/dist/bitwrench-code-edit.es5.js +1 -1
  11. package/dist/bitwrench-code-edit.es5.min.js +1 -1
  12. package/dist/bitwrench-code-edit.esm.js +1 -1
  13. package/dist/bitwrench-code-edit.esm.min.js +1 -1
  14. package/dist/bitwrench-code-edit.umd.js +1 -1
  15. package/dist/bitwrench-code-edit.umd.min.js +1 -1
  16. package/dist/bitwrench-lean.cjs.js +1438 -920
  17. package/dist/bitwrench-lean.cjs.min.js +20 -20
  18. package/dist/bitwrench-lean.es5.js +1518 -1105
  19. package/dist/bitwrench-lean.es5.min.js +18 -18
  20. package/dist/bitwrench-lean.esm.js +1437 -920
  21. package/dist/bitwrench-lean.esm.min.js +20 -20
  22. package/dist/bitwrench-lean.umd.js +1438 -920
  23. package/dist/bitwrench-lean.umd.min.js +20 -20
  24. package/dist/bitwrench-util-css.cjs.js +236 -0
  25. package/dist/bitwrench-util-css.cjs.min.js +22 -0
  26. package/dist/bitwrench-util-css.es5.js +414 -0
  27. package/dist/bitwrench-util-css.es5.min.js +21 -0
  28. package/dist/bitwrench-util-css.esm.js +230 -0
  29. package/dist/bitwrench-util-css.esm.min.js +21 -0
  30. package/dist/bitwrench-util-css.umd.js +242 -0
  31. package/dist/bitwrench-util-css.umd.min.js +21 -0
  32. package/dist/bitwrench.cjs.js +1450 -928
  33. package/dist/bitwrench.cjs.min.js +21 -21
  34. package/dist/bitwrench.css +456 -132
  35. package/dist/bitwrench.es5.js +1563 -1140
  36. package/dist/bitwrench.es5.min.js +19 -19
  37. package/dist/bitwrench.esm.js +1450 -929
  38. package/dist/bitwrench.esm.min.js +21 -21
  39. package/dist/bitwrench.min.css +1 -1
  40. package/dist/bitwrench.umd.js +1450 -928
  41. package/dist/bitwrench.umd.min.js +21 -21
  42. package/dist/builds.json +178 -90
  43. package/dist/bwserve.cjs.js +528 -68
  44. package/dist/bwserve.esm.js +527 -69
  45. package/dist/sri.json +44 -36
  46. package/package.json +5 -2
  47. package/readme.html +136 -49
  48. package/src/bitwrench-bccl.js +12 -8
  49. package/src/bitwrench-color-utils.js +31 -9
  50. package/src/bitwrench-esm-entry.js +11 -0
  51. package/src/bitwrench-styles.js +439 -232
  52. package/src/bitwrench-util-css.js +229 -0
  53. package/src/bitwrench.js +979 -630
  54. package/src/bwserve/attach.js +57 -0
  55. package/src/bwserve/bwclient.js +141 -0
  56. package/src/bwserve/bwshell.js +102 -0
  57. package/src/bwserve/client.js +151 -1
  58. package/src/bwserve/index.js +139 -29
  59. package/src/cli/attach.js +555 -0
  60. package/src/cli/convert.js +2 -5
  61. package/src/cli/index.js +7 -0
  62. package/src/cli/inject.js +1 -1
  63. package/src/cli/layout-default.js +47 -32
  64. package/src/cli/serve.js +6 -2
  65. package/src/generate-css.js +11 -4
  66. package/src/vendor/html2canvas.min.js +20 -0
  67. package/src/version.js +3 -3
  68. package/src/bwserve/shell.js +0 -103
@@ -1,10 +1,11 @@
1
- /*! bitwrench v2.0.16 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench 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
  function _arrayLikeToArray(r, a) {
9
10
  (null == a || a > r.length) && (a = r.length);
10
11
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
@@ -189,14 +190,14 @@
189
190
  */
190
191
 
191
192
  var VERSION_INFO = {
192
- version: '2.0.16',
193
+ version: '2.0.18',
193
194
  name: 'bitwrench',
194
195
  description: 'A library for javascript UI functions.',
195
196
  license: 'BSD-2-Clause',
196
197
  homepage: 'https://deftio.github.com/bitwrench/pages',
197
198
  repository: 'git+https://github.com/deftio/bitwrench.git',
198
199
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
199
- buildDate: '2026-03-12T08:05:52.043Z'
200
+ buildDate: '2026-03-17T00:50:09.505Z'
200
201
  };
201
202
 
202
203
  /**
@@ -485,13 +486,16 @@
485
486
  */
486
487
  function deriveShades(hex) {
487
488
  var rgb = colorParse(hex);
489
+ // For light input colors (L > 75), mixing toward white produces invisible borders.
490
+ // Darken instead so borders remain visible against light backgrounds.
491
+ var borderColor = hexToHsl(hex)[2] > 75 ? adjustLightness(hex, -18) : mixColor(hex, '#ffffff', 0.60);
488
492
  return {
489
493
  base: hex,
490
494
  hover: adjustLightness(hex, -10),
491
495
  active: adjustLightness(hex, -15),
492
496
  light: mixColor(hex, '#ffffff', 0.85),
493
497
  darkText: adjustLightness(hex, -40),
494
- border: mixColor(hex, '#ffffff', 0.60),
498
+ border: borderColor,
495
499
  focus: 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',0.25)',
496
500
  textOn: textOnColor(hex)
497
501
  };
@@ -550,18 +554,26 @@
550
554
  alt.secondary = deriveAlternateSeed(config.secondary);
551
555
  alt.tertiary = config.tertiary ? deriveAlternateSeed(config.tertiary) : alt.primary;
552
556
 
553
- // Derive alternate surface colors from primary hue
557
+ // Derive alternate surface colors from primary hue.
558
+ // Check actual page surface brightness (not seed color brightness) to decide
559
+ // whether alternate should be dark or light. The page surface is what the
560
+ // user sees; seeds can be dark while the page is still light (default L=96).
554
561
  var priHsl = hexToHsl(config.primary);
555
562
  var h = priHsl[0];
556
- var isLight = isLightPalette(config);
563
+ var primarySurface = config.surface || hslToHex([h, 8, 96]);
564
+ var isLight = relativeLuminance(primarySurface) > 0.179;
557
565
  if (isLight) {
558
- // Primary is light → alternate needs dark surfaces
566
+ // Page surface is light → alternate needs dark surfaces
559
567
  alt.light = hslToHex([h, Math.min(priHsl[1], 15), 15]);
560
568
  alt.dark = hslToHex([h, 5, 88]);
569
+ alt.surface = hslToHex([h, 12, 18]);
570
+ alt.background = hslToHex([h, 10, 14]);
561
571
  } else {
562
- // Primary is dark → alternate needs light surfaces
572
+ // Page surface is dark → alternate needs light surfaces
563
573
  alt.light = hslToHex([h, Math.min(priHsl[1], 10), 96]);
564
574
  alt.dark = hslToHex([h, 10, 18]);
575
+ alt.surface = hslToHex([h, 8, 96]);
576
+ alt.background = hslToHex([h, 6, 98]);
565
577
  }
566
578
 
567
579
  // Semantic colors: harmonize toward primary, then invert for alternate
@@ -610,10 +622,16 @@
610
622
  var darkBase = config.dark || hslToHex([h, 10, 13]);
611
623
 
612
624
  // Background & surface tokens — tinted with primary hue for theme personality.
613
- // Very subtle: bg at L=98/S=6, surface at L=96/S=8.
625
+ // Saturation high enough that the hue is visible (each theme feels distinct)
626
+ // but low enough to stay neutral and readable.
614
627
  // User can override with config.background / config.surface.
615
- var bgBase = config.background || hslToHex([h, 6, 98]);
616
- var surfBase = config.surface || hslToHex([h, 8, 96]);
628
+ var bgBase = config.background || hslToHex([h, 22, 96]);
629
+ var surfBase = config.surface || hslToHex([h, 25, 94]);
630
+
631
+ // surfaceAlt: subtle background variant for striped rows, hover states, headers.
632
+ // Slightly lighter than surface in dark mode, slightly darker in light mode.
633
+ var surfHsl = hexToHsl(surfBase);
634
+ var surfAlt = surfHsl[2] <= 50 ? hslToHex([surfHsl[0], surfHsl[1], Math.min(surfHsl[2] + 8, 100)]) : hslToHex([surfHsl[0], surfHsl[1], Math.max(surfHsl[2] - 3, 0)]);
617
635
  var palette = {
618
636
  primary: deriveShades(config.primary),
619
637
  secondary: deriveShades(config.secondary),
@@ -625,7 +643,8 @@
625
643
  light: deriveShades(lightBase),
626
644
  dark: deriveShades(darkBase),
627
645
  background: bgBase,
628
- surface: surfBase
646
+ surface: surfBase,
647
+ surfaceAlt: surfAlt
629
648
  };
630
649
  return palette;
631
650
  }
@@ -650,27 +669,28 @@
650
669
  5: '1.5rem',
651
670
  // 24px
652
671
  6: '2rem'};
672
+ var _S = SPACING_SCALE;
653
673
  var SPACING_PRESETS = {
654
674
  compact: {
655
- btn: SPACING_SCALE[1] + ' ' + SPACING_SCALE[3],
656
- card: SPACING_SCALE[3] + ' ' + SPACING_SCALE[4],
657
- alert: SPACING_SCALE[2] + ' ' + SPACING_SCALE[4],
658
- cell: SPACING_SCALE[2] + ' ' + SPACING_SCALE[3],
659
- input: SPACING_SCALE[1] + ' ' + SPACING_SCALE[3]
675
+ btn: _S[1] + ' ' + _S[3],
676
+ card: _S[3] + ' ' + _S[4],
677
+ alert: _S[2] + ' ' + _S[4],
678
+ cell: _S[2] + ' ' + _S[3],
679
+ input: _S[1] + ' ' + _S[3]
660
680
  },
661
681
  normal: {
662
- btn: SPACING_SCALE[2] + ' ' + SPACING_SCALE[4],
663
- card: SPACING_SCALE[5] + ' ' + SPACING_SCALE[5],
664
- alert: SPACING_SCALE[3] + ' ' + SPACING_SCALE[5],
665
- cell: SPACING_SCALE[3] + ' ' + SPACING_SCALE[4],
666
- input: SPACING_SCALE[2] + ' ' + SPACING_SCALE[3]
682
+ btn: _S[2] + ' ' + _S[4],
683
+ card: _S[5] + ' ' + _S[5],
684
+ alert: _S[3] + ' ' + _S[5],
685
+ cell: _S[3] + ' ' + _S[4],
686
+ input: _S[2] + ' ' + _S[3]
667
687
  },
668
688
  spacious: {
669
- btn: SPACING_SCALE[3] + ' ' + SPACING_SCALE[5],
670
- card: SPACING_SCALE[6] + ' ' + SPACING_SCALE[6],
671
- alert: SPACING_SCALE[4] + ' ' + SPACING_SCALE[5],
672
- cell: SPACING_SCALE[4] + ' ' + SPACING_SCALE[5],
673
- input: SPACING_SCALE[3] + ' ' + SPACING_SCALE[4]
689
+ btn: _S[3] + ' ' + _S[5],
690
+ card: _S[6] + ' ' + _S[6],
691
+ alert: _S[4] + ' ' + _S[5],
692
+ cell: _S[4] + ' ' + _S[5],
693
+ input: _S[3] + ' ' + _S[4]
674
694
  }
675
695
  };
676
696
  var RADIUS_PRESETS = {
@@ -812,68 +832,13 @@
812
832
  * Built-in theme presets — named color combinations
813
833
  * Each preset provides primary, secondary, and tertiary seed colors.
814
834
  */
815
- var THEME_PRESETS = {
816
- teal: {
817
- primary: '#006666',
818
- secondary: '#6c757d',
819
- tertiary: '#006666'
820
- },
821
- ocean: {
822
- primary: '#0077b6',
823
- secondary: '#90e0ef',
824
- tertiary: '#00b4d8'
825
- },
826
- sunset: {
827
- primary: '#e76f51',
828
- secondary: '#264653',
829
- tertiary: '#e9c46a'
830
- },
831
- forest: {
832
- primary: '#2d6a4f',
833
- secondary: '#95d5b2',
834
- tertiary: '#52b788'
835
- },
836
- slate: {
837
- primary: '#343a40',
838
- secondary: '#adb5bd',
839
- tertiary: '#6c757d'
840
- },
841
- rose: {
842
- primary: '#e11d48',
843
- secondary: '#fda4af',
844
- tertiary: '#fb7185'
845
- },
846
- indigo: {
847
- primary: '#4f46e5',
848
- secondary: '#a5b4fc',
849
- tertiary: '#818cf8'
850
- },
851
- amber: {
852
- primary: '#d97706',
853
- secondary: '#fbbf24',
854
- tertiary: '#f59e0b'
855
- },
856
- emerald: {
857
- primary: '#059669',
858
- secondary: '#6ee7b7',
859
- tertiary: '#34d399'
860
- },
861
- nord: {
862
- primary: '#5e81ac',
863
- secondary: '#88c0d0',
864
- tertiary: '#81a1c1'
865
- },
866
- coral: {
867
- primary: '#ef6461',
868
- secondary: '#4a7c7e',
869
- tertiary: '#e8a87c'
870
- },
871
- midnight: {
872
- primary: '#1e3a5f',
873
- secondary: '#7c8db5',
874
- tertiary: '#3d5a80'
875
- }
876
- };
835
+ var THEME_PRESETS = Object.fromEntries([['teal', '#006666', '#6c757d', '#006666'], ['ocean', '#0077b6', '#90e0ef', '#00b4d8'], ['sunset', '#e76f51', '#264653', '#e9c46a'], ['forest', '#2d6a4f', '#95d5b2', '#52b788'], ['slate', '#343a40', '#adb5bd', '#6c757d'], ['rose', '#e11d48', '#fda4af', '#fb7185'], ['indigo', '#4f46e5', '#a5b4fc', '#818cf8'], ['amber', '#d97706', '#fbbf24', '#f59e0b'], ['emerald', '#059669', '#6ee7b7', '#34d399'], ['nord', '#5e81ac', '#88c0d0', '#81a1c1'], ['coral', '#ef6461', '#4a7c7e', '#e8a87c'], ['midnight', '#1e3a5f', '#7c8db5', '#3d5a80']].map(function (e) {
836
+ return [e[0], {
837
+ primary: e[1],
838
+ secondary: e[2],
839
+ tertiary: e[3]
840
+ }];
841
+ }));
877
842
 
878
843
  /**
879
844
  * Resolve layout config to spacing, radius, typeScale, elevation, and motion objects.
@@ -921,6 +886,7 @@
921
886
  }).join(', ');
922
887
  return '.' + name + ' ' + sel;
923
888
  }
889
+ var _sx = scopeSelector;
924
890
 
925
891
  // =========================================================================
926
892
  // Themed CSS generators
@@ -929,12 +895,12 @@
929
895
  function generateTypographyThemed(scope, palette, layout) {
930
896
  var mot = layout.motion;
931
897
  var rules = {};
932
- rules[scopeSelector(scope, 'a')] = {
898
+ rules[_sx(scope, 'a')] = {
933
899
  'color': palette.primary.base,
934
900
  'text-decoration': 'none',
935
901
  'transition': 'color ' + mot.fast + ' ' + mot.easing
936
902
  };
937
- rules[scopeSelector(scope, 'a:hover')] = {
903
+ rules[_sx(scope, 'a:hover')] = {
938
904
  'color': palette.primary.hover,
939
905
  'text-decoration': 'underline'
940
906
  };
@@ -946,11 +912,11 @@
946
912
  var rd = layout.radius;
947
913
 
948
914
  // Base button (only when scoped — unscoped uses defaultStyles)
949
- rules[scopeSelector(scope, '.bw_btn')] = {
915
+ rules[_sx(scope, '.bw_btn')] = {
950
916
  'padding': sp.btn,
951
917
  'border-radius': rd.btn
952
918
  };
953
- rules[scopeSelector(scope, '.bw_btn:focus-visible')] = {
919
+ rules[_sx(scope, '.bw_btn:focus-visible')] = {
954
920
  'outline': '2px solid currentColor',
955
921
  'outline-offset': '2px',
956
922
  'box-shadow': '0 0 0 3px ' + palette.primary.focus
@@ -959,12 +925,12 @@
959
925
  // Variant colors handled by palette class on component root
960
926
 
961
927
  // Size variants (structural, reuse layout radius)
962
- rules[scopeSelector(scope, '.bw_btn_lg')] = {
928
+ rules[_sx(scope, '.bw_btn_lg')] = {
963
929
  'padding': '0.625rem 1.5rem',
964
930
  'font-size': '1rem',
965
931
  'border-radius': rd.btn === '50rem' ? '50rem' : parseInt(rd.btn) + 2 + 'px'
966
932
  };
967
- rules[scopeSelector(scope, '.bw_btn_sm')] = {
933
+ rules[_sx(scope, '.bw_btn_sm')] = {
968
934
  'padding': '0.25rem 0.75rem',
969
935
  'font-size': '0.8125rem',
970
936
  'border-radius': rd.btn === '50rem' ? '50rem' : Math.max(parseInt(rd.btn) - 1, 0) + 'px'
@@ -975,7 +941,7 @@
975
941
  var rules = {};
976
942
  var sp = layout.spacing;
977
943
  var rd = layout.radius;
978
- rules[scopeSelector(scope, '.bw_alert')] = {
944
+ rules[_sx(scope, '.bw_alert')] = {
979
945
  'padding': sp.alert,
980
946
  'border-radius': rd.alert
981
947
  };
@@ -993,38 +959,38 @@
993
959
  var rd = layout.radius;
994
960
  var elev = layout.elevation;
995
961
  var motion = layout.motion;
996
- rules[scopeSelector(scope, '.bw_card')] = {
962
+ rules[_sx(scope, '.bw_card')] = {
997
963
  'background-color': palette.surface || '#fff',
998
964
  'border': '1px solid ' + palette.light.border,
999
965
  'border-radius': rd.card,
1000
966
  'box-shadow': elev.sm,
1001
967
  'transition': 'box-shadow ' + motion.normal + ' ' + motion.easing + ', transform ' + motion.normal + ' ' + motion.easing
1002
968
  };
1003
- rules[scopeSelector(scope, '.bw_card:hover')] = {
969
+ rules[_sx(scope, '.bw_card:hover')] = {
1004
970
  'box-shadow': elev.md
1005
971
  };
1006
- rules[scopeSelector(scope, '.bw_card_hoverable:hover')] = {
972
+ rules[_sx(scope, '.bw_card_hoverable:hover')] = {
1007
973
  'box-shadow': elev.lg
1008
974
  };
1009
- rules[scopeSelector(scope, '.bw_card_body')] = {
975
+ rules[_sx(scope, '.bw_card_body')] = {
1010
976
  'padding': sp.card
1011
977
  };
1012
- rules[scopeSelector(scope, '.bw_card_header')] = {
978
+ rules[_sx(scope, '.bw_card_header')] = {
1013
979
  'padding': sp.card.split(' ').map(function (v) {
1014
980
  return (parseFloat(v) * 0.7).toFixed(3).replace(/\.?0+$/, '') + 'rem';
1015
981
  }).join(' '),
1016
- 'background-color': palette.light.light,
982
+ 'background-color': palette.surfaceAlt,
1017
983
  'border-bottom': '1px solid ' + palette.light.border
1018
984
  };
1019
- rules[scopeSelector(scope, '.bw_card_footer')] = {
1020
- 'background-color': palette.light.light,
985
+ rules[_sx(scope, '.bw_card_footer')] = {
986
+ 'background-color': palette.surfaceAlt,
1021
987
  'border-top': '1px solid ' + palette.light.border,
1022
988
  'color': palette.secondary.base
1023
989
  };
1024
- rules[scopeSelector(scope, '.bw_card_title')] = {
990
+ rules[_sx(scope, '.bw_card_title')] = {
1025
991
  'color': palette.dark.base
1026
992
  };
1027
- rules[scopeSelector(scope, '.bw_card_subtitle')] = {
993
+ rules[_sx(scope, '.bw_card_subtitle')] = {
1028
994
  'color': palette.secondary.base
1029
995
  };
1030
996
 
@@ -1036,101 +1002,104 @@
1036
1002
  var rules = {};
1037
1003
  var sp = layout.spacing;
1038
1004
  var rd = layout.radius;
1039
- rules[scopeSelector(scope, '.bw_form_control')] = {
1005
+ rules[_sx(scope, '.bw_form_control')] = {
1040
1006
  'padding': sp.input,
1041
1007
  'border-radius': rd.input,
1042
1008
  'color': palette.dark.base,
1043
1009
  'background-color': palette.surface || '#fff',
1044
1010
  'border-color': palette.light.border
1045
1011
  };
1046
- rules[scopeSelector(scope, '.bw_form_control:focus')] = {
1012
+ rules[_sx(scope, '.bw_form_control:focus')] = {
1047
1013
  'border-color': palette.primary.border,
1048
1014
  'outline': '2px solid ' + palette.primary.base,
1049
1015
  'outline-offset': '-1px',
1050
1016
  'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
1051
1017
  };
1052
- rules[scopeSelector(scope, '.bw_form_control::placeholder')] = {
1018
+ rules[_sx(scope, '.bw_form_control::placeholder')] = {
1053
1019
  'color': palette.secondary.base
1054
1020
  };
1055
- rules[scopeSelector(scope, '.bw_form_label')] = {
1021
+ rules[_sx(scope, '.bw_form_label')] = {
1056
1022
  'color': palette.dark.base
1057
1023
  };
1058
- rules[scopeSelector(scope, '.bw_form_text')] = {
1024
+ rules[_sx(scope, '.bw_form_text')] = {
1059
1025
  'color': palette.secondary.base
1060
1026
  };
1061
- rules[scopeSelector(scope, '.bw_form_check_input:checked')] = {
1027
+ rules[_sx(scope, '.bw_form_check_input:checked')] = {
1062
1028
  'background-color': palette.primary.base,
1063
1029
  'border-color': palette.primary.base
1064
1030
  };
1065
- rules[scopeSelector(scope, '.bw_form_check_input:focus')] = {
1031
+ rules[_sx(scope, '.bw_form_check_input:focus')] = {
1066
1032
  'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
1067
1033
  };
1068
1034
  // Validation states
1069
- rules[scopeSelector(scope, '.bw_form_control.bw_is_valid')] = {
1035
+ rules[_sx(scope, '.bw_form_control.bw_is_valid')] = {
1070
1036
  'border-color': palette.success.base
1071
1037
  };
1072
- rules[scopeSelector(scope, '.bw_form_control.bw_is_valid:focus')] = {
1038
+ rules[_sx(scope, '.bw_form_control.bw_is_valid:focus')] = {
1073
1039
  'border-color': palette.success.base,
1074
1040
  'box-shadow': '0 0 0 0.2rem ' + palette.success.focus
1075
1041
  };
1076
- rules[scopeSelector(scope, '.bw_form_control.bw_is_invalid')] = {
1042
+ rules[_sx(scope, '.bw_form_control.bw_is_invalid')] = {
1077
1043
  'border-color': palette.danger.base
1078
1044
  };
1079
- rules[scopeSelector(scope, '.bw_form_control.bw_is_invalid:focus')] = {
1045
+ rules[_sx(scope, '.bw_form_control.bw_is_invalid:focus')] = {
1080
1046
  'border-color': palette.danger.base,
1081
1047
  'box-shadow': '0 0 0 0.2rem ' + palette.danger.focus
1082
1048
  };
1083
1049
  // Form select
1084
- rules[scopeSelector(scope, '.bw_form_select')] = {
1050
+ rules[_sx(scope, '.bw_form_select')] = {
1085
1051
  'padding': sp.input,
1086
1052
  'border-radius': rd.input,
1087
1053
  'color': palette.dark.base,
1088
1054
  'background-color': palette.surface || '#fff',
1089
1055
  'border-color': palette.light.border
1090
1056
  };
1091
- rules[scopeSelector(scope, '.bw_form_select:focus')] = {
1057
+ rules[_sx(scope, '.bw_form_select:focus')] = {
1092
1058
  'border-color': palette.primary.border,
1093
1059
  'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
1094
1060
  };
1095
1061
  return rules;
1096
1062
  }
1097
- function generateNavigation(scope, palette) {
1063
+ function generateNavigation(scope, palette, layout) {
1098
1064
  var rules = {};
1099
- rules[scopeSelector(scope, '.bw_navbar')] = {
1100
- 'background-color': palette.light.light,
1065
+ rules[_sx(scope, '.bw_navbar')] = {
1066
+ 'background-color': palette.surfaceAlt,
1101
1067
  'border-bottom-color': palette.light.border
1102
1068
  };
1103
- rules[scopeSelector(scope, '.bw_navbar_brand')] = {
1069
+ rules[_sx(scope, '.bw_navbar_brand')] = {
1104
1070
  'color': palette.dark.base
1105
1071
  };
1106
- rules[scopeSelector(scope, '.bw_navbar_nav .bw_nav_link')] = {
1107
- 'color': palette.secondary.base
1072
+ rules[_sx(scope, '.bw_navbar_nav .bw_nav_link')] = {
1073
+ 'color': palette.secondary.base,
1074
+ 'border-radius': layout.radius.btn
1108
1075
  };
1109
- rules[scopeSelector(scope, '.bw_navbar_nav .bw_nav_link:hover')] = {
1110
- 'color': palette.dark.base
1076
+ rules[_sx(scope, '.bw_navbar_nav .bw_nav_link:hover')] = {
1077
+ 'color': palette.dark.base,
1078
+ 'background-color': palette.surfaceAlt
1111
1079
  };
1112
- rules[scopeSelector(scope, '.bw_navbar_nav .bw_nav_link.active')] = {
1080
+ rules[_sx(scope, '.bw_navbar_nav .bw_nav_link.active')] = {
1113
1081
  'color': palette.primary.base,
1114
- 'background-color': palette.primary.focus
1082
+ 'background-color': palette.primary.focus,
1083
+ 'font-weight': '600'
1115
1084
  };
1116
- rules[scopeSelector(scope, '.bw_navbar_dark')] = {
1085
+ rules[_sx(scope, '.bw_navbar_dark')] = {
1117
1086
  'background-color': palette.dark.base,
1118
1087
  'border-bottom-color': palette.dark.hover
1119
1088
  };
1120
- rules[scopeSelector(scope, '.bw_navbar_dark .bw_navbar_brand')] = {
1089
+ rules[_sx(scope, '.bw_navbar_dark .bw_navbar_brand')] = {
1121
1090
  'color': palette.light.base
1122
1091
  };
1123
- rules[scopeSelector(scope, '.bw_navbar_dark .bw_nav_link')] = {
1124
- 'color': 'rgba(255,255,255,.65)'
1092
+ rules[_sx(scope, '.bw_navbar_dark .bw_nav_link')] = {
1093
+ 'color': palette.light.border
1125
1094
  };
1126
- rules[scopeSelector(scope, '.bw_navbar_dark .bw_nav_link:hover')] = {
1127
- 'color': '#fff'
1095
+ rules[_sx(scope, '.bw_navbar_dark .bw_nav_link:hover')] = {
1096
+ 'color': palette.light.base
1128
1097
  };
1129
- rules[scopeSelector(scope, '.bw_navbar_dark .bw_nav_link.active')] = {
1130
- 'color': '#fff',
1098
+ rules[_sx(scope, '.bw_navbar_dark .bw_nav_link.active')] = {
1099
+ 'color': palette.light.base,
1131
1100
  'font-weight': '600'
1132
1101
  };
1133
- rules[scopeSelector(scope, '.bw_nav_pills .bw_nav_link.active')] = {
1102
+ rules[_sx(scope, '.bw_nav_pills .bw_nav_link.active')] = {
1134
1103
  'color': palette.primary.textOn,
1135
1104
  'background-color': palette.primary.base
1136
1105
  };
@@ -1139,47 +1108,57 @@
1139
1108
  function generateTables(scope, palette, layout) {
1140
1109
  var rules = {};
1141
1110
  var sp = layout.spacing;
1142
- rules[scopeSelector(scope, '.bw_table')] = {
1111
+ rules[_sx(scope, '.bw_table')] = {
1143
1112
  'color': palette.dark.base,
1144
1113
  'border-color': palette.light.border
1145
1114
  };
1146
- rules[scopeSelector(scope, '.bw_table > :not(caption) > * > *')] = {
1115
+ rules[_sx(scope, '.bw_table > :not(caption) > * > *')] = {
1147
1116
  'padding': sp.cell,
1148
1117
  'border-bottom-color': palette.light.border
1149
1118
  };
1150
- rules[scopeSelector(scope, '.bw_table > thead > tr > *')] = {
1119
+ rules[_sx(scope, '.bw_table > thead > tr > *')] = {
1151
1120
  'color': palette.secondary.base,
1152
1121
  'border-bottom-color': palette.light.border,
1153
- 'background-color': palette.light.light
1122
+ 'background-color': palette.surfaceAlt
1154
1123
  };
1155
- rules[scopeSelector(scope, '.bw_table_striped > tbody > tr:nth-of-type(odd) > *')] = {
1156
- 'background-color': 'rgba(0, 0, 0, 0.05)'
1124
+ rules[_sx(scope, '.bw_table_striped > tbody > tr:nth-of-type(odd) > *')] = {
1125
+ 'background-color': palette.surfaceAlt
1157
1126
  };
1158
- rules[scopeSelector(scope, '.bw_table_hover > tbody > tr:hover > *')] = {
1127
+ rules[_sx(scope, '.bw_table_hover > tbody > tr:hover > *')] = {
1159
1128
  'background-color': palette.primary.focus
1160
1129
  };
1161
- rules[scopeSelector(scope, '.bw_table_bordered')] = {
1130
+ rules[_sx(scope, '.bw_table_selectable > tbody > tr')] = {
1131
+ 'cursor': 'pointer'
1132
+ };
1133
+ rules[_sx(scope, '.bw_table > tbody > tr.bw_table_row_selected > *')] = {
1134
+ 'background-color': palette.primary.light
1135
+ };
1136
+ rules[_sx(scope, '.bw_table_bordered')] = {
1162
1137
  'border-color': palette.light.border
1163
1138
  };
1164
- rules[scopeSelector(scope, '.bw_table caption')] = {
1139
+ rules[_sx(scope, '.bw_table caption')] = {
1165
1140
  'color': palette.secondary.base
1166
1141
  };
1167
1142
  return rules;
1168
1143
  }
1169
- function generateTabs(scope, palette) {
1170
- var rules = {};
1171
- rules[scopeSelector(scope, '.bw_nav_tabs')] = {
1144
+ function generateTabs(scope, palette, layout) {
1145
+ var rules = {},
1146
+ mo = layout.motion;
1147
+ rules[_sx(scope, '.bw_nav_tabs')] = {
1172
1148
  'border-bottom-color': palette.light.border
1173
1149
  };
1174
- rules[scopeSelector(scope, '.bw_nav_link')] = {
1175
- 'color': palette.secondary.base
1150
+ rules[_sx(scope, '.bw_nav_link')] = {
1151
+ 'color': palette.secondary.base,
1152
+ 'transition': 'color ' + mo.fast + ' ' + mo.easing + ', border-color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
1176
1153
  };
1177
- rules[scopeSelector(scope, '.bw_nav_tabs .bw_nav_link:hover')] = {
1154
+ rules[_sx(scope, '.bw_nav_tabs .bw_nav_link:hover')] = {
1178
1155
  'color': palette.dark.base,
1156
+ 'background-color': palette.surfaceAlt,
1179
1157
  'border-bottom-color': palette.light.border
1180
1158
  };
1181
- rules[scopeSelector(scope, '.bw_nav_tabs .bw_nav_link.active')] = {
1159
+ rules[_sx(scope, '.bw_nav_tabs .bw_nav_link.active')] = {
1182
1160
  'color': palette.primary.base,
1161
+ 'background-color': palette.primary.focus,
1183
1162
  'border-bottom': '2px solid ' + palette.primary.base
1184
1163
  };
1185
1164
  return rules;
@@ -1187,49 +1166,62 @@
1187
1166
  function generateListGroups(scope, palette, layout) {
1188
1167
  var rules = {};
1189
1168
  var sp = layout.spacing;
1190
- rules[scopeSelector(scope, '.bw_list_group_item')] = {
1169
+ var mo = layout.motion;
1170
+ rules[_sx(scope, '.bw_list_group_item')] = {
1191
1171
  'padding': sp.cell,
1192
1172
  'color': palette.dark.base,
1193
1173
  'background-color': palette.surface || '#fff',
1194
- 'border-color': palette.light.border
1174
+ 'border-color': palette.light.border,
1175
+ 'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
1195
1176
  };
1196
- rules[scopeSelector(scope, 'a.bw_list_group_item:hover')] = {
1197
- 'background-color': palette.light.light,
1177
+ rules[_sx(scope, 'a.bw_list_group_item:hover')] = {
1178
+ 'background-color': palette.surfaceAlt,
1198
1179
  'color': palette.dark.hover
1199
1180
  };
1200
- rules[scopeSelector(scope, '.bw_list_group_item.active')] = {
1181
+ rules[_sx(scope, '.bw_list_group_item.active')] = {
1201
1182
  'color': palette.primary.textOn,
1202
1183
  'background-color': palette.primary.base,
1203
1184
  'border-color': palette.primary.base
1204
1185
  };
1205
- rules[scopeSelector(scope, '.bw_list_group_item.disabled')] = {
1186
+ rules[_sx(scope, '.bw_list_group_item.disabled')] = {
1206
1187
  'color': palette.secondary.base,
1207
1188
  'background-color': palette.surface || '#fff'
1208
1189
  };
1209
1190
  return rules;
1210
1191
  }
1211
- function generatePagination(scope, palette) {
1212
- var rules = {};
1213
- rules[scopeSelector(scope, '.bw_page_link')] = {
1192
+ function generatePagination(scope, palette, layout) {
1193
+ var rules = {},
1194
+ mo = layout.motion,
1195
+ rd = layout.radius;
1196
+ rules[_sx(scope, '.bw_page_item:first-child .bw_page_link')] = {
1197
+ 'border-top-left-radius': rd.btn,
1198
+ 'border-bottom-left-radius': rd.btn
1199
+ };
1200
+ rules[_sx(scope, '.bw_page_item:last-child .bw_page_link')] = {
1201
+ 'border-top-right-radius': rd.btn,
1202
+ 'border-bottom-right-radius': rd.btn
1203
+ };
1204
+ rules[_sx(scope, '.bw_page_link')] = {
1214
1205
  'color': palette.primary.base,
1215
1206
  'background-color': palette.surface || '#fff',
1216
- 'border-color': palette.light.border
1207
+ 'border-color': palette.light.border,
1208
+ 'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
1217
1209
  };
1218
- rules[scopeSelector(scope, '.bw_page_link:hover')] = {
1210
+ rules[_sx(scope, '.bw_page_link:hover')] = {
1219
1211
  'color': palette.primary.hover,
1220
- 'background-color': palette.light.light,
1212
+ 'background-color': palette.surfaceAlt,
1221
1213
  'border-color': palette.light.border
1222
1214
  };
1223
- rules[scopeSelector(scope, '.bw_page_link:focus')] = {
1215
+ rules[_sx(scope, '.bw_page_link:focus')] = {
1224
1216
  'outline': '2px solid ' + palette.primary.base,
1225
1217
  'outline-offset': '-2px'
1226
1218
  };
1227
- rules[scopeSelector(scope, '.bw_page_item.bw_active .bw_page_link')] = {
1219
+ rules[_sx(scope, '.bw_page_item.bw_active .bw_page_link')] = {
1228
1220
  'color': palette.primary.textOn,
1229
1221
  'background-color': palette.primary.base,
1230
1222
  'border-color': palette.primary.base
1231
1223
  };
1232
- rules[scopeSelector(scope, '.bw_page_item.bw_disabled .bw_page_link')] = {
1224
+ rules[_sx(scope, '.bw_page_item.bw_disabled .bw_page_link')] = {
1233
1225
  'color': palette.secondary.base,
1234
1226
  'background-color': palette.surface || '#fff',
1235
1227
  'border-color': palette.light.border
@@ -1238,12 +1230,12 @@
1238
1230
  }
1239
1231
  function generateProgress(scope, palette) {
1240
1232
  var rules = {};
1241
- rules[scopeSelector(scope, '.bw_progress')] = {
1242
- 'background-color': palette.light.light,
1233
+ rules[_sx(scope, '.bw_progress')] = {
1234
+ 'background-color': palette.surfaceAlt,
1243
1235
  'box-shadow': 'inset 0 1px 2px rgba(0,0,0,.1)'
1244
1236
  };
1245
- rules[scopeSelector(scope, '.bw_progress_bar')] = {
1246
- 'color': '#fff',
1237
+ rules[_sx(scope, '.bw_progress_bar')] = {
1238
+ 'color': palette.primary.textOn,
1247
1239
  'background-color': palette.primary.base,
1248
1240
  'box-shadow': 'inset 0 -1px 0 rgba(0,0,0,.15)'
1249
1241
  };
@@ -1262,25 +1254,31 @@
1262
1254
  'color': palette.dark.base,
1263
1255
  'background-color': bg
1264
1256
  };
1265
- rules[scopeSelector(scope, 'body')] = baseReset;
1266
- // Also apply to the scope element itself so themes work on any container, not just body
1267
- if (scope) {
1268
- rules['.' + scope] = baseReset;
1269
- }
1257
+ rules[_sx(scope, 'body')] = baseReset;
1270
1258
  return rules;
1271
1259
  }
1272
- function generateBreadcrumbThemed(scope, palette) {
1273
- var rules = {};
1274
- rules[scopeSelector(scope, '.bw_breadcrumb_item + .bw_breadcrumb_item::before')] = {
1275
- 'color': palette.secondary.base
1260
+ function generateBreadcrumbThemed(scope, palette, layout) {
1261
+ var rules = {},
1262
+ mo = layout.motion;
1263
+ rules[_sx(scope, '.bw_breadcrumb')] = {
1264
+ 'background-color': palette.surfaceAlt,
1265
+ 'padding': '0.625rem 1rem',
1266
+ 'border-radius': layout.radius.btn
1276
1267
  };
1277
- rules[scopeSelector(scope, '.bw_breadcrumb_item.active')] = {
1268
+ rules[_sx(scope, '.bw_breadcrumb_item + .bw_breadcrumb_item::before')] = {
1278
1269
  'color': palette.secondary.base
1279
1270
  };
1280
- rules[scopeSelector(scope, '.bw_breadcrumb_item a:hover')] = {
1271
+ rules[_sx(scope, '.bw_breadcrumb_item a')] = {
1272
+ 'color': palette.primary.base,
1273
+ 'transition': 'color ' + mo.fast + ' ' + mo.easing
1274
+ };
1275
+ rules[_sx(scope, '.bw_breadcrumb_item a:hover')] = {
1281
1276
  'color': palette.primary.hover,
1282
1277
  'text-decoration': 'underline'
1283
1278
  };
1279
+ rules[_sx(scope, '.bw_breadcrumb_item.active')] = {
1280
+ 'color': palette.dark.base
1281
+ };
1284
1282
  return rules;
1285
1283
  }
1286
1284
 
@@ -1288,240 +1286,273 @@
1288
1286
 
1289
1287
  function generateCloseButtonThemed(scope, palette) {
1290
1288
  var rules = {};
1291
- rules[scopeSelector(scope, '.bw_close')] = {
1289
+ rules[_sx(scope, '.bw_close')] = {
1292
1290
  'color': palette.dark.base,
1293
1291
  'opacity': '0.5'
1294
1292
  };
1295
- rules[scopeSelector(scope, '.bw_close:focus')] = {
1293
+ rules[_sx(scope, '.bw_close:focus')] = {
1296
1294
  'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
1297
1295
  };
1298
1296
  return rules;
1299
1297
  }
1300
1298
  function generateSectionsThemed(scope, palette) {
1301
1299
  var rules = {};
1302
- rules[scopeSelector(scope, '.bw_section_subtitle')] = {
1300
+ rules[_sx(scope, '.bw_section_subtitle')] = {
1303
1301
  'color': palette.secondary.base
1304
1302
  };
1305
- rules[scopeSelector(scope, '.bw_feature_description')] = {
1303
+ rules[_sx(scope, '.bw_feature_description')] = {
1306
1304
  'color': palette.secondary.base
1307
1305
  };
1308
- rules[scopeSelector(scope, '.bw_cta_description')] = {
1306
+ rules[_sx(scope, '.bw_cta_description')] = {
1309
1307
  'color': palette.secondary.base
1310
1308
  };
1311
1309
  return rules;
1312
1310
  }
1313
- function generateAccordionThemed(scope, palette) {
1311
+ function generateAccordionThemed(scope, palette, layout) {
1314
1312
  var rules = {};
1315
- rules[scopeSelector(scope, '.bw_accordion_item')] = {
1313
+ var rd = layout ? layout.radius : {
1314
+ card: '8px'
1315
+ };
1316
+ rules[_sx(scope, '.bw_accordion_item')] = {
1316
1317
  'background-color': palette.surface || '#fff',
1317
1318
  'border-color': palette.light.border
1318
1319
  };
1319
- rules[scopeSelector(scope, '.bw_accordion_button')] = {
1320
+ rules[_sx(scope, '.bw_accordion_item:first-child')] = {
1321
+ 'border-top-left-radius': rd.card,
1322
+ 'border-top-right-radius': rd.card
1323
+ };
1324
+ rules[_sx(scope, '.bw_accordion_item:last-child')] = {
1325
+ 'border-bottom-left-radius': rd.card,
1326
+ 'border-bottom-right-radius': rd.card
1327
+ };
1328
+ rules[_sx(scope, '.bw_accordion_button')] = {
1320
1329
  'color': palette.dark.base
1321
1330
  };
1322
- rules[scopeSelector(scope, '.bw_accordion_button:not(.bw_collapsed)')] = {
1331
+ rules[_sx(scope, '.bw_accordion_button:not(.bw_collapsed)')] = {
1323
1332
  'color': palette.primary.darkText,
1324
- 'background-color': palette.primary.light
1333
+ 'background-color': palette.primary.light,
1334
+ 'border-left': '3px solid ' + palette.primary.base
1325
1335
  };
1326
- rules[scopeSelector(scope, '.bw_accordion_button:hover')] = {
1327
- 'background-color': palette.light.light
1336
+ rules[_sx(scope, '.bw_accordion_button:hover')] = {
1337
+ 'background-color': palette.surfaceAlt
1328
1338
  };
1329
- rules[scopeSelector(scope, '.bw_accordion_button:not(.bw_collapsed):hover')] = {
1330
- 'background-color': palette.primary.hover
1339
+ rules[_sx(scope, '.bw_accordion_button:not(.bw_collapsed):hover')] = {
1340
+ 'background-color': palette.primary.base,
1341
+ 'color': palette.primary.textOn
1331
1342
  };
1332
- rules[scopeSelector(scope, '.bw_accordion_button:focus-visible')] = {
1343
+ rules[_sx(scope, '.bw_accordion_button:focus-visible')] = {
1333
1344
  'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
1334
1345
  };
1335
- rules[scopeSelector(scope, '.bw_accordion_body')] = {
1336
- 'border-top': '1px solid ' + palette.light.border
1346
+ rules[_sx(scope, '.bw_accordion_body')] = {
1347
+ 'border-top': '1px solid ' + palette.light.border,
1348
+ 'background-color': palette.surfaceAlt
1337
1349
  };
1338
1350
  return rules;
1339
1351
  }
1340
1352
  function generateCarouselThemed(scope, palette) {
1341
1353
  var rules = {};
1342
- rules[scopeSelector(scope, '.bw_carousel')] = {
1343
- 'background-color': palette.light.light
1354
+ rules[_sx(scope, '.bw_carousel')] = {
1355
+ 'background-color': palette.surfaceAlt
1344
1356
  };
1345
- rules[scopeSelector(scope, '.bw_carousel_indicator.active')] = {
1357
+ rules[_sx(scope, '.bw_carousel_indicator.active')] = {
1346
1358
  'background-color': palette.primary.base
1347
1359
  };
1348
- rules[scopeSelector(scope, '.bw_carousel_control')] = {
1349
- 'background-color': 'rgba(0,0,0,0.4)',
1350
- 'color': '#fff'
1360
+ rules[_sx(scope, '.bw_carousel_control')] = {
1361
+ 'background-color': palette.dark.base,
1362
+ 'color': palette.dark.textOn
1351
1363
  };
1352
- rules[scopeSelector(scope, '.bw_carousel_control:hover')] = {
1353
- 'background-color': 'rgba(0,0,0,0.6)'
1364
+ rules[_sx(scope, '.bw_carousel_control:hover')] = {
1365
+ 'background-color': palette.dark.hover
1354
1366
  };
1355
- rules[scopeSelector(scope, '.bw_carousel_caption')] = {
1356
- 'background': 'linear-gradient(transparent, rgba(0,0,0,0.6))',
1357
- 'color': '#fff'
1367
+ rules[_sx(scope, '.bw_carousel_caption')] = {
1368
+ 'background': 'linear-gradient(transparent, ' + palette.dark.base + ')',
1369
+ 'color': palette.dark.textOn
1358
1370
  };
1359
1371
  return rules;
1360
1372
  }
1361
1373
  function generateModalThemed(scope, palette, layout) {
1362
1374
  var rules = {};
1363
- rules[scopeSelector(scope, '.bw_modal_content')] = {
1375
+ rules[_sx(scope, '.bw_modal_content')] = {
1364
1376
  'background-color': palette.surface || '#fff',
1365
1377
  'border-color': palette.light.border,
1366
1378
  'box-shadow': layout.elevation.lg
1367
1379
  };
1368
- rules[scopeSelector(scope, '.bw_modal_header')] = {
1380
+ rules[_sx(scope, '.bw_modal_header')] = {
1369
1381
  'border-bottom-color': palette.light.border
1370
1382
  };
1371
- rules[scopeSelector(scope, '.bw_modal_footer')] = {
1383
+ rules[_sx(scope, '.bw_modal_footer')] = {
1372
1384
  'border-top-color': palette.light.border
1373
1385
  };
1374
- rules[scopeSelector(scope, '.bw_modal_title')] = {
1386
+ rules[_sx(scope, '.bw_modal_title')] = {
1375
1387
  'color': palette.dark.base
1376
1388
  };
1377
1389
  return rules;
1378
1390
  }
1379
1391
  function generateToastThemed(scope, palette, layout) {
1380
1392
  var rules = {};
1381
- rules[scopeSelector(scope, '.bw_toast')] = {
1393
+ rules[_sx(scope, '.bw_toast')] = {
1382
1394
  'background-color': palette.surface || '#fff',
1383
- 'border-color': 'rgba(0,0,0,0.1)',
1395
+ 'border-color': palette.light.border,
1384
1396
  'box-shadow': layout.elevation.lg
1385
1397
  };
1386
- rules[scopeSelector(scope, '.bw_toast_header')] = {
1387
- 'border-bottom-color': 'rgba(0,0,0,0.05)'
1398
+ rules[_sx(scope, '.bw_toast_header')] = {
1399
+ 'border-bottom-color': palette.light.border
1388
1400
  };
1389
1401
  // Variant toast borders handled by palette class
1390
1402
  return rules;
1391
1403
  }
1392
1404
  function generateDropdownThemed(scope, palette, layout) {
1393
1405
  var rules = {};
1394
- rules[scopeSelector(scope, '.bw_dropdown_menu')] = {
1406
+ rules[_sx(scope, '.bw_dropdown_menu')] = {
1395
1407
  'background-color': palette.surface || '#fff',
1396
1408
  'border-color': palette.light.border,
1397
1409
  'box-shadow': layout.elevation.md
1398
1410
  };
1399
- rules[scopeSelector(scope, '.bw_dropdown_item')] = {
1400
- 'color': palette.dark.base
1411
+ rules[_sx(scope, '.bw_dropdown_item')] = {
1412
+ 'color': palette.dark.base,
1413
+ 'transition': 'background-color ' + layout.motion.fast + ' ' + layout.motion.easing
1401
1414
  };
1402
- rules[scopeSelector(scope, '.bw_dropdown_item:hover')] = {
1415
+ rules[_sx(scope, '.bw_dropdown_item:hover')] = {
1403
1416
  'color': palette.dark.hover,
1404
- 'background-color': palette.light.light
1417
+ 'background-color': palette.surfaceAlt
1405
1418
  };
1406
- rules[scopeSelector(scope, '.bw_dropdown_item.disabled')] = {
1419
+ rules[_sx(scope, '.bw_dropdown_item.disabled')] = {
1407
1420
  'color': palette.secondary.base
1408
1421
  };
1409
- rules[scopeSelector(scope, '.bw_dropdown_divider')] = {
1422
+ rules[_sx(scope, '.bw_dropdown_divider')] = {
1410
1423
  'border-top-color': palette.light.border
1411
1424
  };
1412
1425
  return rules;
1413
1426
  }
1414
1427
  function generateSwitchThemed(scope, palette) {
1415
1428
  var rules = {};
1416
- rules[scopeSelector(scope, '.bw_form_switch .bw_switch_input')] = {
1429
+ rules[_sx(scope, '.bw_form_switch .bw_switch_input')] = {
1417
1430
  'background-color': palette.secondary.base,
1418
1431
  'border-color': palette.secondary.base
1419
1432
  };
1420
- rules[scopeSelector(scope, '.bw_form_switch .bw_switch_input:checked')] = {
1433
+ rules[_sx(scope, '.bw_form_switch .bw_switch_input:checked')] = {
1421
1434
  'background-color': palette.primary.base,
1422
1435
  'border-color': palette.primary.base
1423
1436
  };
1424
- rules[scopeSelector(scope, '.bw_form_switch .bw_switch_input:focus')] = {
1437
+ rules[_sx(scope, '.bw_form_switch .bw_switch_input:focus')] = {
1425
1438
  'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
1426
1439
  };
1427
1440
  return rules;
1428
1441
  }
1429
1442
  function generateSkeletonThemed(scope, palette) {
1430
1443
  var rules = {};
1431
- rules[scopeSelector(scope, '.bw_skeleton')] = {
1432
- 'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.light.light + ' 37%, ' + palette.light.border + ' 63%)'
1444
+ rules[_sx(scope, '.bw_skeleton')] = {
1445
+ 'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.surfaceAlt + ' 37%, ' + palette.light.border + ' 63%)'
1433
1446
  };
1434
1447
  return rules;
1435
1448
  }
1436
1449
 
1437
1450
  // generateAvatarThemed: removed — palette class on root handles variants
1438
1451
 
1439
- function generateStatCardThemed(scope, palette) {
1440
- var rules = {};
1452
+ function generateStatCardThemed(scope, palette, layout) {
1453
+ var rules = {},
1454
+ mo = layout.motion,
1455
+ el = layout.elevation,
1456
+ rd = layout.radius;
1457
+ rules[_sx(scope, '.bw_stat_card')] = {
1458
+ 'background-color': palette.surface || '#fff',
1459
+ 'color': palette.dark.base,
1460
+ 'border': '1px solid ' + palette.light.border,
1461
+ 'border-radius': rd.card,
1462
+ 'box-shadow': el.sm,
1463
+ 'transition': 'box-shadow ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
1464
+ };
1465
+ rules[_sx(scope, '.bw_stat_card:hover')] = {
1466
+ 'box-shadow': el.md
1467
+ };
1441
1468
  // Variant border colors handled by palette class
1442
- rules[scopeSelector(scope, '.bw_stat_change_up')] = {
1469
+ rules[_sx(scope, '.bw_stat_change_up')] = {
1443
1470
  'color': palette.success.base
1444
1471
  };
1445
- rules[scopeSelector(scope, '.bw_stat_change_down')] = {
1472
+ rules[_sx(scope, '.bw_stat_change_down')] = {
1446
1473
  'color': palette.danger.base
1447
1474
  };
1448
1475
  return rules;
1449
1476
  }
1450
1477
  function generateTimelineThemed(scope, palette) {
1451
1478
  var rules = {};
1452
- rules[scopeSelector(scope, '.bw_timeline::before')] = {
1479
+ rules[_sx(scope, '.bw_timeline::before')] = {
1453
1480
  'background-color': palette.light.border
1454
1481
  };
1455
1482
  // Variant marker colors handled by palette class
1456
- rules[scopeSelector(scope, '.bw_timeline_date')] = {
1483
+ rules[_sx(scope, '.bw_timeline_date')] = {
1457
1484
  'color': palette.secondary.base
1458
1485
  };
1459
1486
  return rules;
1460
1487
  }
1461
1488
  function generateStepperThemed(scope, palette) {
1462
1489
  var rules = {};
1463
- rules[scopeSelector(scope, '.bw_step_indicator')] = {
1464
- 'background-color': palette.light.light,
1490
+ rules[_sx(scope, '.bw_step_indicator')] = {
1491
+ 'background-color': palette.surfaceAlt,
1465
1492
  'border': '2px solid ' + palette.light.border,
1466
1493
  'color': palette.secondary.base
1467
1494
  };
1468
- rules[scopeSelector(scope, '.bw_step + .bw_step::before')] = {
1495
+ rules[_sx(scope, '.bw_step + .bw_step::before')] = {
1469
1496
  'background-color': palette.light.border
1470
1497
  };
1471
- rules[scopeSelector(scope, '.bw_step_active .bw_step_indicator')] = {
1498
+ rules[_sx(scope, '.bw_step_active .bw_step_indicator')] = {
1472
1499
  'background-color': palette.primary.base,
1473
1500
  'color': palette.primary.textOn
1474
1501
  };
1475
- rules[scopeSelector(scope, '.bw_step_active .bw_step_label')] = {
1502
+ rules[_sx(scope, '.bw_step_active .bw_step_label')] = {
1476
1503
  'color': palette.dark.base,
1477
1504
  'font-weight': '600'
1478
1505
  };
1479
- rules[scopeSelector(scope, '.bw_step_completed .bw_step_indicator')] = {
1506
+ rules[_sx(scope, '.bw_step_completed .bw_step_indicator')] = {
1480
1507
  'background-color': palette.primary.base,
1481
1508
  'color': palette.primary.textOn
1482
1509
  };
1483
- rules[scopeSelector(scope, '.bw_step_completed .bw_step_label')] = {
1510
+ rules[_sx(scope, '.bw_step_completed .bw_step_label')] = {
1484
1511
  'color': palette.primary.base
1485
1512
  };
1486
- rules[scopeSelector(scope, '.bw_step_completed + .bw_step::before')] = {
1513
+ rules[_sx(scope, '.bw_step_completed + .bw_step::before')] = {
1487
1514
  'background-color': palette.primary.base
1488
1515
  };
1489
1516
  return rules;
1490
1517
  }
1491
1518
  function generateChipInputThemed(scope, palette) {
1492
1519
  var rules = {};
1493
- rules[scopeSelector(scope, '.bw_chip_input')] = {
1494
- 'border-color': palette.light.border
1520
+ rules[_sx(scope, '.bw_chip_input')] = {
1521
+ 'border-color': palette.light.border,
1522
+ 'background-color': palette.surface || '#fff',
1523
+ 'color': palette.dark.base
1495
1524
  };
1496
- rules[scopeSelector(scope, '.bw_chip_input:focus-within')] = {
1525
+ rules[_sx(scope, '.bw_chip_input:focus-within')] = {
1497
1526
  'border-color': palette.primary.base,
1498
1527
  'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
1499
1528
  };
1500
- rules[scopeSelector(scope, '.bw_chip')] = {
1501
- 'background-color': palette.light.light,
1529
+ rules[_sx(scope, '.bw_chip')] = {
1530
+ 'background-color': palette.surfaceAlt,
1502
1531
  'color': palette.dark.base
1503
1532
  };
1504
- rules[scopeSelector(scope, '.bw_chip_remove:hover')] = {
1533
+ rules[_sx(scope, '.bw_chip_remove:hover')] = {
1505
1534
  'color': palette.danger.base,
1506
1535
  'background-color': palette.danger.light
1507
1536
  };
1508
1537
  return rules;
1509
1538
  }
1510
- function generateFileUploadThemed(scope, palette) {
1511
- var rules = {};
1512
- rules[scopeSelector(scope, '.bw_file_upload')] = {
1539
+ function generateFileUploadThemed(scope, palette, layout) {
1540
+ var rules = {},
1541
+ mo = layout.motion;
1542
+ rules[_sx(scope, '.bw_file_upload')] = {
1513
1543
  'border-color': palette.light.border,
1514
- 'background-color': palette.light.light
1544
+ 'background-color': palette.surfaceAlt,
1545
+ 'transition': 'border-color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
1515
1546
  };
1516
- rules[scopeSelector(scope, '.bw_file_upload:hover')] = {
1547
+ rules[_sx(scope, '.bw_file_upload:hover')] = {
1517
1548
  'border-color': palette.primary.base,
1518
1549
  'background-color': palette.primary.light
1519
1550
  };
1520
- rules[scopeSelector(scope, '.bw_file_upload:focus')] = {
1551
+ rules[_sx(scope, '.bw_file_upload:focus')] = {
1521
1552
  'outline': '2px solid ' + palette.primary.base,
1522
1553
  'outline-offset': '2px'
1523
1554
  };
1524
- rules[scopeSelector(scope, '.bw_file_upload.bw_file_upload_active')] = {
1555
+ rules[_sx(scope, '.bw_file_upload.bw_file_upload_active')] = {
1525
1556
  'border-color': palette.primary.base,
1526
1557
  'background-color': palette.primary.light,
1527
1558
  'border-style': 'solid'
@@ -1530,37 +1561,93 @@
1530
1561
  }
1531
1562
  function generateRangeThemed(scope, palette) {
1532
1563
  var rules = {};
1533
- rules[scopeSelector(scope, '.bw_range')] = {
1564
+ rules[_sx(scope, '.bw_range')] = {
1534
1565
  'background-color': palette.light.border
1535
1566
  };
1536
- rules[scopeSelector(scope, '.bw_range::-webkit-slider-thumb')] = {
1567
+ rules[_sx(scope, '.bw_range::-webkit-slider-thumb')] = {
1537
1568
  'background-color': palette.primary.base,
1538
- 'border-color': '#fff',
1569
+ 'border-color': palette.surface || '#fff',
1539
1570
  'box-shadow': '0 1px 3px rgba(0,0,0,0.2)',
1540
1571
  'transition': 'background-color 0.15s ease-out, transform 0.15s ease-out'
1541
1572
  };
1542
- rules[scopeSelector(scope, '.bw_range::-moz-range-thumb')] = {
1573
+ rules[_sx(scope, '.bw_range::-moz-range-thumb')] = {
1543
1574
  'background-color': palette.primary.base,
1544
- 'border-color': '#fff',
1575
+ 'border-color': palette.surface || '#fff',
1545
1576
  'box-shadow': '0 1px 3px rgba(0,0,0,0.2)'
1546
1577
  };
1547
1578
  return rules;
1548
1579
  }
1549
- function generateSearchThemed(scope, palette) {
1550
- var rules = {};
1551
- rules[scopeSelector(scope, '.bw_search_clear:hover')] = {
1580
+ function generateTooltipThemed(scope, palette, layout) {
1581
+ var rules = {},
1582
+ sp = layout.spacing,
1583
+ rd = layout.radius,
1584
+ el = layout.elevation,
1585
+ mo = layout.motion;
1586
+ rules[_sx(scope, '.bw_tooltip')] = {
1587
+ 'background-color': palette.dark.base,
1588
+ 'color': palette.dark.textOn,
1589
+ 'padding': sp.input,
1590
+ 'border-radius': rd.badge,
1591
+ 'box-shadow': el.md,
1592
+ 'transition': 'opacity ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
1593
+ };
1594
+ return rules;
1595
+ }
1596
+ function generatePopoverThemed(scope, palette, layout) {
1597
+ var rules = {},
1598
+ sp = layout.spacing,
1599
+ rd = layout.radius,
1600
+ el = layout.elevation,
1601
+ mo = layout.motion;
1602
+ rules[_sx(scope, '.bw_popover')] = {
1603
+ 'background-color': palette.surface || '#fff',
1604
+ 'color': palette.dark.base,
1605
+ 'border': '1px solid ' + palette.light.border,
1606
+ 'border-radius': rd.card,
1607
+ 'box-shadow': el.lg,
1608
+ 'transition': 'opacity ' + mo.fast + ' ' + mo.easing + ', transform ' + mo.fast + ' ' + mo.easing
1609
+ };
1610
+ rules[_sx(scope, '.bw_popover_header')] = {
1611
+ 'background-color': palette.surfaceAlt,
1612
+ 'border-bottom': '1px solid ' + palette.light.border,
1613
+ 'padding': sp.input
1614
+ };
1615
+ rules[_sx(scope, '.bw_popover_body')] = {
1616
+ 'padding': sp.card
1617
+ };
1618
+ return rules;
1619
+ }
1620
+ function generateSearchThemed(scope, palette, layout) {
1621
+ var rules = {},
1622
+ mo = layout.motion;
1623
+ rules[_sx(scope, '.bw_search_input')] = {
1624
+ 'background-color': palette.surface || '#fff',
1625
+ 'color': palette.dark.base
1626
+ };
1627
+ rules[_sx(scope, '.bw_search_clear')] = {
1628
+ 'transition': 'color ' + mo.fast + ' ' + mo.easing + ', background-color ' + mo.fast + ' ' + mo.easing
1629
+ };
1630
+ rules[_sx(scope, '.bw_search_clear:hover')] = {
1552
1631
  'color': palette.dark.base
1553
1632
  };
1554
1633
  return rules;
1555
1634
  }
1556
- function generateCodeDemoThemed(scope, palette) {
1635
+ function generateCodeDemoThemed(scope, palette, layout) {
1557
1636
  var rules = {};
1558
- rules[scopeSelector(scope, '.bw_code_copy_btn_copied')] = {
1637
+ var rd = layout ? layout.radius : {
1638
+ card: '0.375rem'
1639
+ };
1640
+ rules[_sx(scope, '.bw_code_demo')] = {
1641
+ 'background-color': palette.surface || '#fff',
1642
+ 'color': palette.dark.base,
1643
+ 'border-radius': rd.card
1644
+ };
1645
+ rules[_sx(scope, '.bw_code_copy_btn_copied')] = {
1559
1646
  'background': palette.success.base,
1560
1647
  'color': palette.success.textOn,
1561
1648
  'border-color': palette.success.base
1562
1649
  };
1563
- rules[scopeSelector(scope, '.bw_copy_btn:hover')] = {
1650
+ rules[_sx(scope, '.bw_copy_btn:hover')] = {
1564
1651
  'background': 'rgba(255,255,255,0.2)',
1565
1652
  'color': '#fff'
1566
1653
  };
@@ -1569,7 +1656,7 @@
1569
1656
  function generateNavPillsThemed(scope, palette, layout) {
1570
1657
  var rules = {};
1571
1658
  var rd = layout.radius;
1572
- rules[scopeSelector(scope, '.bw_nav_pills .bw_nav_link')] = {
1659
+ rules[_sx(scope, '.bw_nav_pills .bw_nav_link')] = {
1573
1660
  'border-radius': rd.btn
1574
1661
  };
1575
1662
  return rules;
@@ -1597,21 +1684,21 @@
1597
1684
  var s = palette[k];
1598
1685
 
1599
1686
  // --- Root palette class: sets default bg/color/border ---
1600
- rules[scopeSelector(scope, '.bw_' + k)] = {
1687
+ rules[_sx(scope, '.bw_' + k)] = {
1601
1688
  'background-color': s.base,
1602
1689
  'color': s.textOn,
1603
1690
  'border-color': s.base
1604
1691
  };
1605
1692
 
1606
1693
  // --- Pseudo-states (shared across all components) ---
1607
- rules[scopeSelector(scope, '.bw_' + k + ':hover')] = {
1694
+ rules[_sx(scope, '.bw_' + k + ':hover')] = {
1608
1695
  'background-color': s.hover,
1609
1696
  'border-color': s.active
1610
1697
  };
1611
- rules[scopeSelector(scope, '.bw_' + k + ':active')] = {
1698
+ rules[_sx(scope, '.bw_' + k + ':active')] = {
1612
1699
  'background-color': s.active
1613
1700
  };
1614
- rules[scopeSelector(scope, '.bw_' + k + ':focus-visible')] = {
1701
+ rules[_sx(scope, '.bw_' + k + ':focus-visible')] = {
1615
1702
  'box-shadow': '0 0 0 3px ' + s.focus,
1616
1703
  'outline': 'none'
1617
1704
  };
@@ -1619,71 +1706,110 @@
1619
1706
  // --- Component-specific overrides ---
1620
1707
 
1621
1708
  // Alerts: light bg, dark text, subtle border
1622
- rules[scopeSelector(scope, '.bw_alert.bw_' + k)] = {
1709
+ rules[_sx(scope, '.bw_alert.bw_' + k)] = {
1623
1710
  'background-color': s.light,
1624
1711
  'color': s.darkText,
1625
1712
  'border-color': s.border
1626
1713
  };
1627
1714
 
1628
1715
  // Toast: inherit bg, left border accent
1629
- rules[scopeSelector(scope, '.bw_toast.bw_' + k)] = {
1716
+ rules[_sx(scope, '.bw_toast.bw_' + k)] = {
1630
1717
  'background-color': 'inherit',
1631
1718
  'color': 'inherit',
1632
1719
  'border-left': '4px solid ' + s.base
1633
1720
  };
1634
1721
 
1635
1722
  // Stat card: inherit bg, left border accent
1636
- rules[scopeSelector(scope, '.bw_stat_card.bw_' + k)] = {
1723
+ rules[_sx(scope, '.bw_stat_card.bw_' + k)] = {
1637
1724
  'background-color': 'inherit',
1638
1725
  'color': 'inherit',
1639
1726
  'border-left-color': s.base
1640
1727
  };
1641
1728
 
1642
1729
  // Card accent: left border accent, inherit bg
1643
- rules[scopeSelector(scope, '.bw_card.bw_' + k)] = {
1730
+ rules[_sx(scope, '.bw_card.bw_' + k)] = {
1644
1731
  'background-color': 'inherit',
1645
1732
  'color': 'inherit',
1646
1733
  'border-left': '4px solid ' + s.base
1647
1734
  };
1648
1735
 
1649
1736
  // Timeline marker: colored dot
1650
- rules[scopeSelector(scope, '.bw_timeline_marker.bw_' + k)] = {
1737
+ rules[_sx(scope, '.bw_timeline_marker.bw_' + k)] = {
1651
1738
  'box-shadow': '0 0 0 2px ' + s.base
1652
1739
  };
1653
1740
 
1654
- // Spinner: text color only, transparent bg
1655
- rules[scopeSelector(scope, '.bw_spinner_border.bw_' + k + ',\n' + scopeSelector(scope, '.bw_spinner_grow.bw_' + k))] = {
1741
+ // Spinner: set color, re-apply border pattern so the root palette class
1742
+ // border-color doesn't fill in the transparent gap that makes it spin.
1743
+ // Also neutralize hover/active which would override border-right-color.
1744
+ rules[_sx(scope, '.bw_spinner_border.bw_' + k)] = {
1656
1745
  'background-color': 'transparent',
1657
1746
  'color': s.base,
1658
- 'border-color': 'currentColor'
1747
+ 'border-color': s.base,
1748
+ 'border-right-color': 'transparent'
1749
+ };
1750
+ rules[_sx(scope, '.bw_spinner_border.bw_' + k + ':hover')] = {
1751
+ 'background-color': 'transparent',
1752
+ 'border-color': s.base,
1753
+ 'border-right-color': 'transparent'
1754
+ };
1755
+ rules[_sx(scope, '.bw_spinner_grow.bw_' + k)] = {
1756
+ 'background-color': s.base,
1757
+ 'color': s.base
1659
1758
  };
1660
1759
 
1661
1760
  // Outline button: transparent bg, colored border+text, solid on hover
1662
- rules[scopeSelector(scope, '.bw_btn_outline.bw_' + k)] = {
1761
+ rules[_sx(scope, '.bw_btn_outline.bw_' + k)] = {
1663
1762
  'background-color': 'transparent',
1664
1763
  'color': s.base,
1665
1764
  'border-color': s.base
1666
1765
  };
1667
- rules[scopeSelector(scope, '.bw_btn_outline.bw_' + k + ':hover')] = {
1766
+ rules[_sx(scope, '.bw_btn_outline.bw_' + k + ':hover')] = {
1668
1767
  'background-color': s.base,
1669
1768
  'color': s.textOn
1670
1769
  };
1671
1770
 
1672
1771
  // Hero: gradient background
1673
- rules[scopeSelector(scope, '.bw_hero.bw_' + k)] = {
1772
+ rules[_sx(scope, '.bw_hero.bw_' + k)] = {
1674
1773
  'background': 'linear-gradient(135deg, ' + s.base + ' 0%, ' + s.hover + ' 100%)',
1675
1774
  'color': s.textOn
1676
1775
  };
1677
1776
 
1678
- // Progress bar: white text on colored bg (default is fine, just ensure text)
1679
- rules[scopeSelector(scope, '.bw_progress_bar.bw_' + k)] = {
1680
- 'color': '#fff'
1777
+ // Progress bar: contrasting text on colored bg
1778
+ rules[_sx(scope, '.bw_progress_bar.bw_' + k)] = {
1779
+ 'color': s.textOn
1780
+ };
1781
+
1782
+ // Background utility: .bw_bg_primary, .bw_bg_secondary, etc.
1783
+ rules[_sx(scope, '.bw_bg_' + k)] = {
1784
+ 'background-color': s.base,
1785
+ 'color': s.textOn
1786
+ };
1787
+
1788
+ // Text color utility: .bw_text_primary, .bw_text_secondary, etc.
1789
+ rules[_sx(scope, '.bw_text_' + k)] = {
1790
+ 'color': s.base
1681
1791
  };
1682
1792
  });
1683
1793
 
1684
- // Text muted
1685
- rules[scopeSelector(scope, '.bw_text_muted')] = {
1686
- 'color': palette.secondary.base
1794
+ // Text muted — always a neutral gray, never a brand color
1795
+ rules[_sx(scope, '.bw_text_muted')] = {
1796
+ 'color': '#6c757d'
1797
+ };
1798
+
1799
+ // Common bg/text utilities that aren't per-variant
1800
+ rules[_sx(scope, '.bw_bg_dark')] = {
1801
+ 'background-color': '#212529',
1802
+ 'color': '#f8f9fa'
1803
+ };
1804
+ rules[_sx(scope, '.bw_bg_light')] = {
1805
+ 'background-color': '#f8f9fa',
1806
+ 'color': '#212529'
1807
+ };
1808
+ rules[_sx(scope, '.bw_text_light')] = {
1809
+ 'color': '#f8f9fa'
1810
+ };
1811
+ rules[_sx(scope, '.bw_text_dark')] = {
1812
+ 'color': '#212529'
1687
1813
  };
1688
1814
  return rules;
1689
1815
  }
@@ -1698,7 +1824,7 @@
1698
1824
  * @returns {Object} CSS rules object
1699
1825
  */
1700
1826
  function generateThemedCSS(scopeName, palette, layout) {
1701
- return Object.assign({}, generateResetThemed(scopeName, palette), generateTypographyThemed(scopeName, palette, layout), generateButtons(scopeName, palette, layout), generateAlerts(scopeName, palette, layout), generateCards(scopeName, palette, layout), generateForms(scopeName, palette, layout), generateNavigation(scopeName, palette), generateTables(scopeName, palette, layout), generateTabs(scopeName, palette), generateListGroups(scopeName, palette, layout), generatePagination(scopeName, palette), generateProgress(scopeName, palette), generateBreadcrumbThemed(scopeName, palette), generateCloseButtonThemed(scopeName, palette), generateSectionsThemed(scopeName, palette), generateAccordionThemed(scopeName, palette), generateCarouselThemed(scopeName, palette), generateModalThemed(scopeName, palette, layout), generateToastThemed(scopeName, palette, layout), generateDropdownThemed(scopeName, palette, layout), generateSwitchThemed(scopeName, palette), generateSkeletonThemed(scopeName, palette), generateStatCardThemed(scopeName, palette), generateTimelineThemed(scopeName, palette), generateStepperThemed(scopeName, palette), generateChipInputThemed(scopeName, palette), generateFileUploadThemed(scopeName, palette), generateRangeThemed(scopeName, palette), generateSearchThemed(scopeName, palette), generateCodeDemoThemed(scopeName, palette), generateNavPillsThemed(scopeName, palette, layout), generatePaletteClasses(scopeName, palette));
1827
+ return Object.assign({}, generateResetThemed(scopeName, palette), generateTypographyThemed(scopeName, palette, layout), generateButtons(scopeName, palette, layout), generateAlerts(scopeName, palette, layout), generateCards(scopeName, palette, layout), generateForms(scopeName, palette, layout), generateNavigation(scopeName, palette, layout), generateTables(scopeName, palette, layout), generateTabs(scopeName, palette, layout), generateListGroups(scopeName, palette, layout), generatePagination(scopeName, palette, layout), generateProgress(scopeName, palette), generateBreadcrumbThemed(scopeName, palette, layout), generateCloseButtonThemed(scopeName, palette), generateSectionsThemed(scopeName, palette), generateAccordionThemed(scopeName, palette, layout), generateCarouselThemed(scopeName, palette), generateModalThemed(scopeName, palette, layout), generateToastThemed(scopeName, palette, layout), generateDropdownThemed(scopeName, palette, layout), generateSwitchThemed(scopeName, palette), generateSkeletonThemed(scopeName, palette), generateStatCardThemed(scopeName, palette, layout), generateTimelineThemed(scopeName, palette), generateStepperThemed(scopeName, palette), generateChipInputThemed(scopeName, palette), generateFileUploadThemed(scopeName, palette, layout), generateRangeThemed(scopeName, palette), generateSearchThemed(scopeName, palette, layout), generateTooltipThemed(scopeName, palette, layout), generatePopoverThemed(scopeName, palette, layout), generateCodeDemoThemed(scopeName, palette, layout), generateNavPillsThemed(scopeName, palette, layout), generatePaletteClasses(scopeName, palette));
1702
1828
  }
1703
1829
 
1704
1830
  // =========================================================================
@@ -2193,6 +2319,12 @@
2193
2319
  'border-width': '1px',
2194
2320
  'border-style': 'solid'
2195
2321
  },
2322
+ '.bw_table_selectable > tbody > tr': {
2323
+ 'cursor': 'pointer'
2324
+ },
2325
+ '.bw_table > tbody > tr.bw_table_row_selected > *': {
2326
+ 'background-color': 'rgba(0, 102, 102, 0.1)'
2327
+ },
2196
2328
  '.bw_table_responsive': {
2197
2329
  'overflow-x': 'auto',
2198
2330
  '-webkit-overflow-scrolling': 'touch'
@@ -2306,6 +2438,7 @@
2306
2438
  'display': 'block',
2307
2439
  'font-size': '0.875rem',
2308
2440
  'font-weight': '500',
2441
+ 'padding': '0.625rem 1rem',
2309
2442
  'text-decoration': 'none',
2310
2443
  'cursor': 'pointer',
2311
2444
  'border': 'none',
@@ -2408,16 +2541,15 @@
2408
2541
  'padding': '0.375rem 0.75rem',
2409
2542
  'margin-left': '-1px',
2410
2543
  'line-height': '1.25',
2411
- 'text-decoration': 'none'
2544
+ 'text-decoration': 'none',
2545
+ 'border': '1px solid transparent',
2546
+ 'cursor': 'pointer',
2547
+ 'font-family': 'inherit',
2548
+ 'font-size': 'inherit',
2549
+ 'background': 'none'
2412
2550
  },
2413
2551
  '.bw_page_item:first-child .bw_page_link': {
2414
- 'margin-left': '0',
2415
- 'border-top-left-radius': '0.375rem',
2416
- 'border-bottom-left-radius': '0.375rem'
2417
- },
2418
- '.bw_page_item:last-child .bw_page_link': {
2419
- 'border-top-right-radius': '0.375rem',
2420
- 'border-bottom-right-radius': '0.375rem'
2552
+ 'margin-left': '0'
2421
2553
  },
2422
2554
  '.bw_page_link:focus-visible': {
2423
2555
  'z-index': '3',
@@ -2788,6 +2920,7 @@
2788
2920
  'display': 'flex',
2789
2921
  'align-items': 'center',
2790
2922
  'width': '100%',
2923
+ 'padding': '0.875rem 1.25rem',
2791
2924
  'font-size': '1rem',
2792
2925
  'font-weight': '500',
2793
2926
  'text-align': 'left',
@@ -2810,20 +2943,16 @@
2810
2943
  '.bw_accordion_button:not(.bw_collapsed)::after': {
2811
2944
  'transform': 'rotate(-180deg)'
2812
2945
  },
2946
+ '.bw_accordion_body': {
2947
+ 'padding': '1rem 1.25rem'
2948
+ },
2813
2949
  '.bw_accordion_collapse': {
2814
2950
  'max-height': '0',
2815
- 'overflow': 'hidden'
2951
+ 'overflow': 'hidden',
2952
+ 'transition': 'max-height 0.3s ease'
2816
2953
  },
2817
2954
  '.bw_accordion_collapse.bw_collapse_show': {
2818
2955
  'max-height': 'none'
2819
- },
2820
- '.bw_accordion_item:first-child': {
2821
- 'border-top-left-radius': '8px',
2822
- 'border-top-right-radius': '8px'
2823
- },
2824
- '.bw_accordion_item:last-child': {
2825
- 'border-bottom-left-radius': '8px',
2826
- 'border-bottom-right-radius': '8px'
2827
2956
  }
2828
2957
  },
2829
2958
  // ---- Carousel ----
@@ -3191,7 +3320,11 @@
3191
3320
  // ---- Stat card ----
3192
3321
  statCard: {
3193
3322
  '.bw_stat_card': {
3194
- 'border-left': '4px solid transparent'
3323
+ 'padding': '1.25rem',
3324
+ 'border-left': '4px solid transparent',
3325
+ 'border-radius': '0.375rem',
3326
+ 'background-color': 'inherit',
3327
+ 'transition': 'transform 0.15s ease'
3195
3328
  },
3196
3329
  '.bw_stat_card:hover': {
3197
3330
  'transform': 'translateY(-1px)'
@@ -4209,6 +4342,52 @@
4209
4342
  'margin-right': '.5rem'
4210
4343
  };
4211
4344
 
4345
+ // Typography — bw_ prefixed utilities via loops
4346
+ var _imp = function _imp(p, v) {
4347
+ var o = {};
4348
+ o[p] = v + ' !important';
4349
+ return o;
4350
+ };
4351
+ [['fs', {
4352
+ 'xs': '0.75rem',
4353
+ 'sm': '0.875rem',
4354
+ 'base': '1rem',
4355
+ 'lg': '1.125rem',
4356
+ 'xl': '1.25rem',
4357
+ '2xl': '1.5rem'
4358
+ }, 'font-size'], ['fw', {
4359
+ light: '300',
4360
+ normal: '400',
4361
+ medium: '500',
4362
+ semibold: '600',
4363
+ bold: '700'
4364
+ }, 'font-weight'], ['lh', {
4365
+ tight: '1.25',
4366
+ normal: '1.5',
4367
+ relaxed: '1.75'
4368
+ }, 'line-height']].forEach(function (d) {
4369
+ for (var dk in d[1]) rules['.bw_' + d[0] + '_' + dk] = _imp(d[2], d[1][dk]);
4370
+ });
4371
+
4372
+ // Flex utilities
4373
+ rules['.bw_flex'] = {
4374
+ 'display': 'flex'
4375
+ };
4376
+ rules['.bw_flex_column'] = {
4377
+ 'flex-direction': 'column'
4378
+ };
4379
+ rules['.bw_flex_wrap'] = {
4380
+ 'flex-wrap': 'wrap'
4381
+ };
4382
+ rules['.bw_flex_center'] = {
4383
+ 'display': 'flex',
4384
+ 'align-items': 'center',
4385
+ 'justify-content': 'center'
4386
+ };
4387
+ for (var gk in spacingValues) rules['.bw_gap_' + gk] = {
4388
+ 'gap': spacingValues[gk] + ' !important'
4389
+ };
4390
+
4212
4391
  // Visibility
4213
4392
  rules['.bw_visible, .visible'] = {
4214
4393
  'visibility': 'visible !important'
@@ -4287,6 +4466,26 @@
4287
4466
  return getStructuralCSS();
4288
4467
  }
4289
4468
 
4469
+ /**
4470
+ * Get CSS reset rules only (box-sizing, html/body font, reduced-motion).
4471
+ * Separate from themed/structural rules for independent injection.
4472
+ * @returns {Object} CSS rules object for the reset layer
4473
+ */
4474
+ function getResetStyles() {
4475
+ var rules = {};
4476
+ Object.assign(rules, structuralRules.base);
4477
+ // Include reduced-motion preference
4478
+ rules['@media (prefers-reduced-motion: reduce)'] = {
4479
+ '*, *::before, *::after': {
4480
+ 'animation-duration': '0.01ms !important',
4481
+ 'animation-iteration-count': '1 !important',
4482
+ 'transition-duration': '0.01ms !important',
4483
+ 'scroll-behavior': 'auto !important'
4484
+ }
4485
+ };
4486
+ return rules;
4487
+ }
4488
+
4290
4489
  // =========================================================================
4291
4490
  // defaultStyles — backward-compatible categorized view
4292
4491
  // =========================================================================
@@ -4321,57 +4520,40 @@
4321
4520
  });
4322
4521
 
4323
4522
  /**
4324
- * Generate alternate-palette CSS scoped under `.bw_theme_alt`.
4325
- * Uses the same `generateThemedCSS()` pipeline as the primary palette —
4326
- * both sides go through identical code paths.
4327
- *
4328
- * @param {string} name - Theme scope name (e.g. 'ocean'). '' for global.
4329
- * @param {Object} altPalette - From derivePalette(deriveAlternateConfig(...))
4330
- * @param {Object} layout - From resolveLayout()
4331
- * @returns {Object} CSS rules object scoped under .bw_theme_alt (+ optional .name)
4523
+ * Prefix every selector in a rules object with a scope selector.
4524
+ * Handles @media/@keyframes blocks and comma-separated selectors.
4525
+ * @param {Object} rules - CSS rules object
4526
+ * @param {string} prefix - Scope prefix (e.g. '#my-dashboard', '.bw_theme_alt')
4527
+ * @param {boolean} [compound=false] - If true, use compound selector (no space)
4528
+ * for the first segment: `#scope.bw_theme_alt .sel` vs `#scope .sel`
4529
+ * @returns {Object} New rules object with scoped selectors
4332
4530
  */
4333
- function generateAlternateCSS(name, altPalette, layout) {
4334
- // Generate themed CSS using the same pipeline as primary
4335
- var rawRules = generateThemedCSS('', altPalette, layout);
4336
-
4337
- // Re-scope every selector under .bw_theme_alt (+ optional theme name)
4338
- var altPrefix = name ? '.' + name + '.bw_theme_alt' : '.bw_theme_alt';
4339
- var altRules = {};
4340
- for (var sel in rawRules) {
4341
- if (!rawRules.hasOwnProperty(sel)) continue;
4531
+ function scopeRulesUnder(rules, prefix, compound) {
4532
+ var scoped = {};
4533
+ for (var sel in rules) {
4534
+ if (!rules.hasOwnProperty(sel)) continue;
4342
4535
  if (sel.charAt(0) === '@') {
4343
4536
  // @media / @keyframes — recurse into the block
4344
- var innerBlock = rawRules[sel];
4345
- var altInner = {};
4537
+ var innerBlock = rules[sel];
4538
+ var scopedInner = {};
4346
4539
  for (var innerSel in innerBlock) {
4347
4540
  if (!innerBlock.hasOwnProperty(innerSel)) continue;
4348
- altInner[altPrefix + ' ' + innerSel] = innerBlock[innerSel];
4541
+ scopedInner[_prefixSelector(innerSel, prefix)] = innerBlock[innerSel];
4349
4542
  }
4350
- altRules[sel] = altInner;
4543
+ scoped[sel] = scopedInner;
4351
4544
  } else {
4352
- // Regular selector — prefix with alt scope
4353
- // Handle comma-separated selectors
4354
- var parts = sel.split(',');
4355
- var scopedParts = [];
4356
- for (var i = 0; i < parts.length; i++) {
4357
- var s = parts[i].trim();
4358
- // 'body' selector gets special treatment: .bw_theme_alt body
4359
- if (s === 'body' || s.indexOf('body') === 0) {
4360
- scopedParts.push(altPrefix + ' ' + s);
4361
- } else {
4362
- scopedParts.push(altPrefix + ' ' + s);
4363
- }
4364
- }
4365
- altRules[scopedParts.join(', ')] = rawRules[sel];
4545
+ scoped[_prefixSelector(sel, prefix)] = rules[sel];
4366
4546
  }
4367
4547
  }
4368
-
4369
- // Add body-level overrides for the alternate surface
4370
- altRules[altPrefix + ' body, :root' + altPrefix + ' body'] = {
4371
- 'color': altPalette.dark.base,
4372
- 'background-color': altPalette.light.base
4373
- };
4374
- return altRules;
4548
+ return scoped;
4549
+ }
4550
+ function _prefixSelector(sel, prefix) {
4551
+ var parts = sel.split(',');
4552
+ var result = [];
4553
+ for (var i = 0; i < parts.length; i++) {
4554
+ result.push(prefix + ' ' + parts[i].trim());
4555
+ }
4556
+ return result.join(', ');
4375
4557
  }
4376
4558
 
4377
4559
  /**
@@ -5413,7 +5595,7 @@
5413
5595
  if (breakpoint === 'xs') {
5414
5596
  classes.push("bw_col_".concat(value));
5415
5597
  } else {
5416
- classes.push("bw_col_".concat(breakpoint, "-").concat(value));
5598
+ classes.push("bw_col_".concat(breakpoint, "_").concat(value));
5417
5599
  }
5418
5600
  });
5419
5601
  } else if (size) {
@@ -6938,12 +7120,13 @@
6938
7120
  "class": "bw_page_item ".concat(currentPage <= 1 ? 'bw_disabled' : '').trim()
6939
7121
  },
6940
7122
  c: {
6941
- t: 'a',
7123
+ t: 'button',
6942
7124
  a: {
6943
7125
  "class": 'bw_page_link',
6944
- href: '#',
7126
+ type: 'button',
6945
7127
  onclick: handleClick(currentPage - 1),
6946
- 'aria-label': 'Previous'
7128
+ 'aria-label': 'Previous',
7129
+ disabled: currentPage <= 1 ? true : undefined
6947
7130
  },
6948
7131
  c: "\u2039"
6949
7132
  }
@@ -6958,11 +7141,12 @@
6958
7141
  "class": "bw_page_item ".concat(pageNum === currentPage ? 'bw_active' : '').trim()
6959
7142
  },
6960
7143
  c: {
6961
- t: 'a',
7144
+ t: 'button',
6962
7145
  a: {
6963
7146
  "class": 'bw_page_link',
6964
- href: '#',
6965
- onclick: handleClick(pageNum)
7147
+ type: 'button',
7148
+ onclick: handleClick(pageNum),
7149
+ 'aria-current': pageNum === currentPage ? 'page' : undefined
6966
7150
  },
6967
7151
  c: '' + pageNum
6968
7152
  }
@@ -6977,12 +7161,13 @@
6977
7161
  "class": "bw_page_item ".concat(currentPage >= pages ? 'bw_disabled' : '').trim()
6978
7162
  },
6979
7163
  c: {
6980
- t: 'a',
7164
+ t: 'button',
6981
7165
  a: {
6982
7166
  "class": 'bw_page_link',
6983
- href: '#',
7167
+ type: 'button',
6984
7168
  onclick: handleClick(currentPage + 1),
6985
- 'aria-label': 'Next'
7169
+ 'aria-label': 'Next',
7170
+ disabled: currentPage >= pages ? true : undefined
6986
7171
  },
6987
7172
  c: "\u203A"
6988
7173
  }
@@ -9080,7 +9265,14 @@
9080
9265
  function make(type, props) {
9081
9266
  var def = BCCL[type];
9082
9267
  if (!def) throw new Error('bw.make: unknown component type "' + type + '". Available: ' + Object.keys(BCCL).join(', '));
9083
- return def.make(props || {});
9268
+ var taco = def.make(props || {});
9269
+ if (taco && _typeof(taco) === 'object') {
9270
+ taco._bwFactory = {
9271
+ type: type,
9272
+ props: props || {}
9273
+ };
9274
+ }
9275
+ return taco;
9084
9276
  }
9085
9277
 
9086
9278
  var components = /*#__PURE__*/Object.freeze({
@@ -9193,7 +9385,7 @@
9193
9385
  __monkey_patch_is_nodejs__: {
9194
9386
  _value: 'ignore',
9195
9387
  set: function set(x) {
9196
- this._value = typeof x === 'boolean' ? x : 'ignore';
9388
+ this._value = _is(x, 'boolean') ? x : 'ignore';
9197
9389
  },
9198
9390
  get: function get() {
9199
9391
  return this._value;
@@ -9241,6 +9433,76 @@
9241
9433
  configurable: true
9242
9434
  });
9243
9435
 
9436
+ // ── Internal aliases ─────────────────────────────────────────────────────
9437
+ // Short names for frequently-used builtins and internal methods.
9438
+ // Same pattern as v1 (_to = bw.typeOf, etc.).
9439
+ //
9440
+ // Why: Terser can't shorten global property chains (console.warn,
9441
+ // Object.prototype.hasOwnProperty, Array.isArray, document.createElement)
9442
+ // because it can't prove they're side-effect-free. We can, so we alias
9443
+ // them here. Each alias saves bytes in the minified output, and the short
9444
+ // names also reduce visual noise in the hot paths (binding pipeline,
9445
+ // createDOM, etc.).
9446
+ //
9447
+ // Alias Target Sites
9448
+ // ───────── ────────────────────────────────────── ─────
9449
+ // _hop Object.prototype.hasOwnProperty 15
9450
+ // _isA Array.isArray 25
9451
+ // _keys Object.keys 7
9452
+ // _to bw.typeOf (type string) 26
9453
+ // _is type check boolean: _is(x,'string') ~50
9454
+ // _cw console.warn 8
9455
+ // _cl console.log 11
9456
+ // _ce console.error 4
9457
+ // _chp ComponentHandle.prototype 28 (defined after constructor)
9458
+ //
9459
+ // Note: document.createElement etc. are NOT aliased because they require
9460
+ // `this === document` and .bind() would add overhead on every call.
9461
+ // Console aliases use thin wrappers (not direct refs) so test monkey-
9462
+ // patching of console.warn/log/error continues to work.
9463
+ //
9464
+ // `typeof x` for UNDECLARED globals (window, document, process, require,
9465
+ // EventSource, navigator, Promise, __filename, import.meta) MUST stay as
9466
+ // raw `typeof` — calling _to(x) when x doesn't exist throws ReferenceError.
9467
+ //
9468
+ // ── v1 functional type helpers (kept for reference, not currently used) ──
9469
+ // _toa(x, type, trueVal, falseVal) — bw.typeAssign:
9470
+ // returns trueVal if _to(x)===type, else falseVal.
9471
+ // Replaces: (typeof x === 'string') ? A : B → _toa(x,'string',A,B)
9472
+ // _toc(x, type, trueVal, falseVal) — bw.typeConvert:
9473
+ // same as _toa but if trueVal/falseVal are functions, calls them with x.
9474
+ // Replaces: typeof x === 'string' ? fn(x) : default → _toc(x,'string',fn,default)
9475
+ // Uncomment if pattern frequency justifies them:
9476
+ // var _toa = function(x, t, y, n) { return _to(x) === t ? y : n; };
9477
+ // 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); };
9478
+ // ─────────────────────────────────────────────────────────────────────────
9479
+ var _hop = Object.prototype.hasOwnProperty;
9480
+ var _isA = Array.isArray;
9481
+ var _keys = Object.keys;
9482
+ var _to = typeOf; // imported from bitwrench-utils.js
9483
+ var _is = function _is(x, t) {
9484
+ var r = _to(x);
9485
+ return r === t || r.toLowerCase() === t;
9486
+ };
9487
+ // Console aliases use thin wrappers (not direct references) so that test
9488
+ // code can monkey-patch console.warn/log/error and the patches take effect.
9489
+ var _cw = function _cw() {
9490
+ console.warn.apply(console, arguments);
9491
+ };
9492
+ var _cl = function _cl() {
9493
+ console.log.apply(console, arguments);
9494
+ };
9495
+ var _ce = function _ce() {
9496
+ console.error.apply(console, arguments);
9497
+ };
9498
+
9499
+ /**
9500
+ * Debug flag. When true, emits console.warn for silent binding failures
9501
+ * (missing paths, null refs, auto-created intermediate objects).
9502
+ * @type {boolean}
9503
+ */
9504
+ bw.debug = false;
9505
+
9244
9506
  /**
9245
9507
  * Lazy-resolve Node.js `fs` module.
9246
9508
  * Tries require('fs') first (available in CJS/UMD Node.js builds),
@@ -9390,7 +9652,7 @@
9390
9652
  */
9391
9653
  bw._el = function (id) {
9392
9654
  // Pass-through for DOM elements
9393
- if (typeof id !== 'string') return id || null;
9655
+ if (!_is(id, 'string')) return id || null;
9394
9656
  if (!id) return null;
9395
9657
  if (!bw._isBrowser) return null;
9396
9658
 
@@ -9418,7 +9680,12 @@
9418
9680
  el = document.querySelector('[data-bw_id="' + id + '"]');
9419
9681
  }
9420
9682
 
9421
- // 5. Cache the result for next time
9683
+ // 5. Try class-based lookup for bw_uuid_* tokens (UUID addressing)
9684
+ if (!el && id.indexOf('bw_uuid_') === 0) {
9685
+ el = document.querySelector('.' + id);
9686
+ }
9687
+
9688
+ // 6. Cache the result for next time
9422
9689
  if (el) {
9423
9690
  bw._nodeMap[id] = el;
9424
9691
  }
@@ -9470,6 +9737,79 @@
9470
9737
  }
9471
9738
  };
9472
9739
 
9740
+ // ===================================================================================
9741
+ // bw.assignUUID() / bw.getUUID() — Explicit UUID addressing for TACO objects
9742
+ // ===================================================================================
9743
+
9744
+ /**
9745
+ * Regex to match a bw_uuid_* token in a class string.
9746
+ * @private
9747
+ */
9748
+ var _UUID_RE = /\bbw_uuid_[a-z0-9_]+\b/;
9749
+
9750
+ /**
9751
+ * Assign a UUID to a TACO object by appending a `bw_uuid_*` token to `taco.a.class`.
9752
+ *
9753
+ * Idempotent by default — calling twice returns the same UUID. Pass `forceNew=true`
9754
+ * to replace an existing UUID (useful in loops where each TACO needs a unique ID).
9755
+ *
9756
+ * @param {Object} taco - A TACO object `{t, a, c, o}`
9757
+ * @param {boolean} [forceNew=false] - If true, replaces any existing UUID with a new one
9758
+ * @returns {string} The UUID string (e.g. 'bw_uuid_a1b2c3d4e5')
9759
+ * @category Identifiers
9760
+ * @example
9761
+ * var card = bw.makeStatCard({ value: '0', label: 'Scans' });
9762
+ * var uuid = bw.assignUUID(card); // 'bw_uuid_a1b2c3d4e5'
9763
+ * var same = bw.assignUUID(card); // same UUID (idempotent)
9764
+ * var diff = bw.assignUUID(card, true); // new UUID (forced)
9765
+ */
9766
+ bw.assignUUID = function (taco, forceNew) {
9767
+ if (!taco || !_is(taco, 'object')) return null;
9768
+
9769
+ // Ensure taco.a exists
9770
+ if (!taco.a) taco.a = {};
9771
+ if (!_is(taco.a["class"], 'string')) taco.a["class"] = taco.a["class"] ? String(taco.a["class"]) : '';
9772
+ var existing = taco.a["class"].match(_UUID_RE);
9773
+ if (existing && !forceNew) {
9774
+ return existing[0];
9775
+ }
9776
+
9777
+ // Remove old UUID if forceNew
9778
+ if (existing) {
9779
+ taco.a["class"] = taco.a["class"].replace(_UUID_RE, '').replace(/\s+/g, ' ').trim();
9780
+ }
9781
+ var uuid = bw.uuid('uuid');
9782
+ taco.a["class"] = (taco.a["class"] ? taco.a["class"] + ' ' : '') + uuid;
9783
+ return uuid;
9784
+ };
9785
+
9786
+ /**
9787
+ * Read the UUID from a TACO object or DOM element. Pure getter, no side effects.
9788
+ *
9789
+ * @param {Object|Element} tacoOrElement - A TACO object or DOM element
9790
+ * @returns {string|null} The UUID string, or null if none assigned
9791
+ * @category Identifiers
9792
+ * @example
9793
+ * bw.getUUID(card) // 'bw_uuid_a1b2c3d4e5' (from TACO)
9794
+ * bw.getUUID(domEl) // 'bw_uuid_a1b2c3d4e5' (from DOM element)
9795
+ * bw.getUUID({t:'div'}) // null (no UUID)
9796
+ */
9797
+ bw.getUUID = function (tacoOrElement) {
9798
+ if (!tacoOrElement) return null;
9799
+ var classStr;
9800
+ // DOM element: check className
9801
+ if (tacoOrElement.className !== undefined && tacoOrElement.tagName) {
9802
+ classStr = tacoOrElement.className;
9803
+ }
9804
+ // TACO object: check a.class
9805
+ else if (tacoOrElement.a && _is(tacoOrElement.a["class"], 'string')) {
9806
+ classStr = tacoOrElement.a["class"];
9807
+ }
9808
+ if (!classStr) return null;
9809
+ var match = classStr.match(_UUID_RE);
9810
+ return match ? match[0] : null;
9811
+ };
9812
+
9473
9813
  /**
9474
9814
  * Escape HTML special characters to prevent XSS.
9475
9815
  *
@@ -9485,7 +9825,7 @@
9485
9825
  * // => '&lt;b&gt;Hello&lt;&#x2F;b&gt; &amp; &quot;world&quot;'
9486
9826
  */
9487
9827
  bw.escapeHTML = function (str) {
9488
- if (typeof str !== 'string') return '';
9828
+ if (!_is(str, 'string')) return '';
9489
9829
  var escapeMap = {
9490
9830
  '&': '&amp;',
9491
9831
  '<': '&lt;',
@@ -9522,6 +9862,45 @@
9522
9862
  };
9523
9863
  };
9524
9864
 
9865
+ /**
9866
+ * Hyperscript-style TACO constructor.
9867
+ *
9868
+ * A convenience helper that returns a canonical TACO object from positional
9869
+ * arguments. The return value is a plain object — serializable, works with
9870
+ * bwserve, and accepted everywhere TACO is accepted.
9871
+ *
9872
+ * @param {string} tag - HTML tag name (e.g. 'div', 'p', 'section')
9873
+ * @param {Object|null} [attrs] - HTML attributes object. Pass null or omit to skip.
9874
+ * @param {*} [content] - Content: string, number, TACO object, or array of children.
9875
+ * @param {Object} [options] - TACO options (state, lifecycle hooks, render fn).
9876
+ * @returns {Object} Plain TACO object {t, a?, c?, o?}
9877
+ * @category Utilities
9878
+ * @see bw.html
9879
+ * @see bw.createDOM
9880
+ * @see bw.DOM
9881
+ * @example
9882
+ * bw.h('div')
9883
+ * // => { t: 'div' }
9884
+ *
9885
+ * bw.h('p', { class: 'bw_text_muted' }, 'Hello')
9886
+ * // => { t: 'p', a: { class: 'bw_text_muted' }, c: 'Hello' }
9887
+ *
9888
+ * bw.h('ul', null, [
9889
+ * bw.h('li', null, 'one'),
9890
+ * bw.h('li', null, 'two')
9891
+ * ])
9892
+ * // => { t: 'ul', c: [{ t: 'li', c: 'one' }, { t: 'li', c: 'two' }] }
9893
+ */
9894
+ bw.h = function (tag, attrs, content, options) {
9895
+ var taco = {
9896
+ t: String(tag)
9897
+ };
9898
+ if (attrs !== null && attrs !== undefined) taco.a = attrs;
9899
+ if (content !== undefined) taco.c = content;
9900
+ if (options !== undefined) taco.o = options;
9901
+ return taco;
9902
+ };
9903
+
9525
9904
  /**
9526
9905
  * Convert a TACO object (or array of TACOs) to an HTML string.
9527
9906
  *
@@ -9562,7 +9941,7 @@
9562
9941
  }
9563
9942
 
9564
9943
  // Handle arrays of TACOs
9565
- if (Array.isArray(taco)) {
9944
+ if (_isA(taco)) {
9566
9945
  return taco.map(function (t) {
9567
9946
  return bw.html(t, options);
9568
9947
  }).join('');
@@ -9585,17 +9964,17 @@
9585
9964
  if (taco && taco._bwEach && options.state) {
9586
9965
  var eachExpr = taco.expr.replace(/^\$\{|\}$/g, '');
9587
9966
  var arr = bw._evaluatePath(options.state, eachExpr);
9588
- if (!Array.isArray(arr)) return '';
9967
+ if (!_isA(arr)) return '';
9589
9968
  return arr.map(function (item, idx) {
9590
9969
  return bw.html(taco.factory(item, idx), options);
9591
9970
  }).join('');
9592
9971
  }
9593
9972
 
9594
9973
  // Handle primitives and non-TACO objects
9595
- if (_typeof(taco) !== 'object' || !taco.t) {
9974
+ if (!_is(taco, 'object') || !taco.t) {
9596
9975
  var str = options.raw ? String(taco) : bw.escapeHTML(String(taco));
9597
9976
  // Resolve template bindings if state provided
9598
- if (options.state && typeof str === 'string' && str.indexOf('${') >= 0) {
9977
+ if (options.state && _is(str, 'string') && str.indexOf('${') >= 0) {
9599
9978
  str = bw._resolveTemplate(str, options.state, !!options.compile);
9600
9979
  }
9601
9980
  return str;
@@ -9620,9 +9999,17 @@
9620
9999
  // Skip null, undefined, false
9621
10000
  if (value == null || value === false) continue;
9622
10001
 
9623
- // Skip event handlers (they're for DOM only)
9624
- if (key.startsWith('on')) continue;
9625
- if (key === 'style' && _typeof(value) === 'object') {
10002
+ // Serialize event handlers via funcRegister
10003
+ if (key.startsWith('on')) {
10004
+ if (_is(value, 'function')) {
10005
+ var fnId = bw.funcRegister(value);
10006
+ attrStr += ' ' + key + '="' + bw.funcGetDispatchStr(fnId, 'event') + '"';
10007
+ } else if (_is(value, 'string')) {
10008
+ attrStr += ' ' + key + '="' + bw.escapeHTML(value) + '"';
10009
+ }
10010
+ continue;
10011
+ }
10012
+ if (key === 'style' && _is(value, 'object')) {
9626
10013
  // Convert style object to string
9627
10014
  var styleStr = Object.entries(value).filter(function (_ref) {
9628
10015
  var _ref2 = _slicedToArray(_ref, 2),
@@ -9639,7 +10026,7 @@
9639
10026
  }
9640
10027
  } else if (key === 'class') {
9641
10028
  // Handle class as array or string
9642
- var classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
10029
+ var classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
9643
10030
  if (classStr) {
9644
10031
  attrStr += " class=\"".concat(bw.escapeHTML(classStr), "\"");
9645
10032
  }
@@ -9675,45 +10062,215 @@
9675
10062
  // Process content recursively
9676
10063
  var contentStr = content != null ? bw.html(content, options) : '';
9677
10064
  // Resolve template bindings in content if state provided
9678
- if (options.state && typeof contentStr === 'string' && contentStr.indexOf('${') >= 0) {
10065
+ if (options.state && _is(contentStr, 'string') && contentStr.indexOf('${') >= 0) {
9679
10066
  contentStr = bw._resolveTemplate(contentStr, options.state, !!options.compile);
9680
10067
  }
9681
10068
  return "<".concat(tag).concat(attrStr, ">").concat(contentStr, "</").concat(tag, ">");
9682
10069
  };
9683
10070
 
9684
10071
  /**
9685
- * Create a live DOM element from a TACO object (browser only).
9686
- *
9687
- * Unlike `bw.html()` which returns a string, this creates real DOM elements
9688
- * with event handlers, lifecycle hooks (mounted/unmount), and state. Used
9689
- * internally by `bw.DOM()`. Throws in Node.js use `bw.html()` instead.
9690
- *
9691
- * @param {Object} taco - TACO object with {t, a, c, o}
9692
- * @param {Object} [options] - Creation options
9693
- * @returns {Element|Text} DOM element or text node
10072
+ * Generate a complete, self-contained HTML document from TACO content.
10073
+ *
10074
+ * Produces a full `<!DOCTYPE html>` page with configurable runtime injection,
10075
+ * func registry emission (so serialized event handlers work), optional theme,
10076
+ * and extra head elements. Designed for static site generation, offline/airgapped
10077
+ * use, and the "static site that isn't static" workflow.
10078
+ *
10079
+ * @param {Object} [opts={}] - Page options
10080
+ * @param {Object|string|Array} [opts.body=''] - Body content: TACO, string, or array
10081
+ * @param {string} [opts.title='bitwrench'] - Page title
10082
+ * @param {Object} [opts.state] - State for ${expr} resolution in bw.html()
10083
+ * @param {string} [opts.runtime='shim'] - Runtime level: 'inline'|'cdn'|'shim'|'none'
10084
+ * @param {string} [opts.css=''] - Additional CSS for <style> block
10085
+ * @param {string|Object} [opts.theme=null] - Theme preset name or config object
10086
+ * @param {Array} [opts.head=[]] - Extra TACO elements rendered into <head>
10087
+ * @param {string} [opts.favicon=''] - Favicon URL
10088
+ * @param {string} [opts.lang='en'] - HTML lang attribute
10089
+ * @returns {string} Complete HTML document string
9694
10090
  * @category DOM Generation
9695
10091
  * @see bw.html
9696
- * @see bw.DOM
9697
10092
  * @example
9698
- * var el = bw.createDOM({
9699
- * t: 'button',
9700
- * a: { class: 'bw_btn', onclick: () => alert('clicked') },
9701
- * c: 'Click Me'
9702
- * });
9703
- * document.body.appendChild(el);
10093
+ * bw.htmlPage({
10094
+ * title: 'My App',
10095
+ * body: { t: 'h1', c: 'Hello World' },
10096
+ * runtime: 'shim'
10097
+ * })
9704
10098
  */
9705
- bw.createDOM = function (taco) {
9706
- var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9707
- if (!bw._isBrowser) {
9708
- throw new Error('bw.createDOM requires a DOM environment (document/window). Use bw.html() instead.');
10099
+ bw.htmlPage = function (opts) {
10100
+ opts = opts || {};
10101
+ var title = opts.title || 'bitwrench';
10102
+ var body = opts.body || '';
10103
+ var state = opts.state || undefined;
10104
+ var runtime = opts.runtime || 'shim';
10105
+ var css = opts.css || '';
10106
+ var theme = opts.theme || null;
10107
+ var headExtra = opts.head || [];
10108
+ var favicon = opts.favicon || '';
10109
+ var lang = opts.lang || 'en';
10110
+
10111
+ // Snapshot funcRegistry counter before rendering
10112
+ var fnCounterBefore = bw._fnIDCounter;
10113
+
10114
+ // Render body content
10115
+ var bodyHTML = '';
10116
+ if (_is(body, 'string')) {
10117
+ bodyHTML = body;
10118
+ } else {
10119
+ var htmlOpts = {};
10120
+ if (state) htmlOpts.state = state;
10121
+ bodyHTML = bw.html(body, htmlOpts);
9709
10122
  }
9710
10123
 
9711
- // Handle null/undefined
9712
- if (taco == null) return document.createTextNode('');
10124
+ // Collect functions registered during this render
10125
+ var fnCounterAfter = bw._fnIDCounter;
10126
+ var registryEntries = '';
10127
+ for (var i = fnCounterBefore; i < fnCounterAfter; i++) {
10128
+ var fnKey = 'bw_fn_' + i;
10129
+ if (bw._fnRegistry[fnKey]) {
10130
+ registryEntries += 'bw._fnRegistry[\'' + fnKey + '\']=' + bw._fnRegistry[fnKey].toString() + ';\n';
10131
+ }
10132
+ }
9713
10133
 
9714
- // Handle bw.raw() marked content — inject as HTML
9715
- if (taco && taco.__bw_raw) {
9716
- var frag = document.createDocumentFragment();
10134
+ // Build runtime script for <head>
10135
+ var runtimeHead = '';
10136
+ if (runtime === 'inline') {
10137
+ // Read UMD bundle synchronously if in Node.js
10138
+ var umdSource = null;
10139
+ if (bw._isNode) {
10140
+ try {
10141
+ var fs = typeof require === 'function' ? require('fs') : null;
10142
+ var pathMod = typeof require === 'function' ? require('path') : null;
10143
+ if (fs && pathMod) {
10144
+ // Resolve dist/ relative to this source file
10145
+ var srcDir = '';
10146
+ try {
10147
+ srcDir = pathMod.dirname(typeof __filename !== 'undefined' ? __filename : '');
10148
+ } catch (e2) {/* ESM: __filename not available */}
10149
+ 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.es5.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.es5.js', document.baseURI).href))) {
10150
+ var url = typeof require === 'function' ? require('url') : null;
10151
+ 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.es5.js', document.baseURI).href))));
10152
+ }
10153
+ if (srcDir) {
10154
+ var distPath = pathMod.resolve(srcDir, '../dist/bitwrench.umd.min.js');
10155
+ umdSource = fs.readFileSync(distPath, 'utf8');
10156
+ }
10157
+ }
10158
+ } catch (e) {/* fall through */}
10159
+ }
10160
+ if (umdSource) {
10161
+ runtimeHead = '<script>' + umdSource + '</script>';
10162
+ } else {
10163
+ // Fallback to shim in browser or if dist not available
10164
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
10165
+ }
10166
+ } else if (runtime === 'cdn') {
10167
+ runtimeHead = '<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>';
10168
+ } else if (runtime === 'shim') {
10169
+ runtimeHead = '<script>' + bw._FUNC_REGISTRY_SHIM + '</script>';
10170
+ }
10171
+ // runtime === 'none' → empty
10172
+
10173
+ // Theme CSS
10174
+ var themeCSS = '';
10175
+ if (theme) {
10176
+ var themeConfig = _is(theme, 'string') ? THEME_PRESETS[theme.toLowerCase()] || null : theme;
10177
+ if (themeConfig) {
10178
+ var themeResult = bw.makeStyles(themeConfig);
10179
+ themeCSS = themeResult.css;
10180
+ }
10181
+ }
10182
+
10183
+ // Extra <head> elements
10184
+ var headHTML = '';
10185
+ if (_isA(headExtra) && headExtra.length > 0) {
10186
+ headHTML = headExtra.map(function (el) {
10187
+ return bw.html(el);
10188
+ }).join('\n');
10189
+ }
10190
+
10191
+ // Favicon
10192
+ var faviconTag = '';
10193
+ if (favicon) {
10194
+ var safeFavicon = favicon.replace(/[&<>"']/g, function (c) {
10195
+ return {
10196
+ '&': '&amp;',
10197
+ '<': '&lt;',
10198
+ '>': '&gt;',
10199
+ '"': '&quot;',
10200
+ "'": '&#39;'
10201
+ }[c];
10202
+ });
10203
+ faviconTag = '<link rel="icon" href="' + safeFavicon + '">';
10204
+ }
10205
+
10206
+ // Escaped title
10207
+ var safeTitle = bw.escapeHTML(title);
10208
+
10209
+ // Combine all CSS
10210
+ var allCSS = (themeCSS ? themeCSS + '\n' : '') + css;
10211
+
10212
+ // Body-end script: registry entries + optional loadStyles
10213
+ var bodyEndScript = '';
10214
+ var bodyEndParts = [];
10215
+ if (registryEntries) {
10216
+ bodyEndParts.push(registryEntries);
10217
+ }
10218
+ if (runtime === 'inline' || runtime === 'cdn') {
10219
+ bodyEndParts.push('if(typeof bw!=="undefined"){bw.loadStyles();}');
10220
+ }
10221
+ if (bodyEndParts.length > 0) {
10222
+ bodyEndScript = '<script>\n' + bodyEndParts.join('\n') + '\n</script>';
10223
+ }
10224
+
10225
+ // Assemble document
10226
+ var parts = ['<!DOCTYPE html>', '<html lang="' + lang + '">', '<head>', '<meta charset="UTF-8">', '<meta name="viewport" content="width=device-width, initial-scale=1">'];
10227
+ parts.push('<title>' + safeTitle + '</title>');
10228
+ if (faviconTag) parts.push(faviconTag);
10229
+ if (runtimeHead) parts.push(runtimeHead);
10230
+ if (headHTML) parts.push(headHTML);
10231
+ if (allCSS) parts.push('<style>' + allCSS + '</style>');
10232
+ parts.push('</head>');
10233
+ parts.push('<body>');
10234
+ parts.push(bodyHTML);
10235
+ if (bodyEndScript) parts.push(bodyEndScript);
10236
+ parts.push('</body>');
10237
+ parts.push('</html>');
10238
+ return parts.join('\n');
10239
+ };
10240
+
10241
+ /**
10242
+ * Create a live DOM element from a TACO object (browser only).
10243
+ *
10244
+ * Unlike `bw.html()` which returns a string, this creates real DOM elements
10245
+ * with event handlers, lifecycle hooks (mounted/unmount), and state. Used
10246
+ * internally by `bw.DOM()`. Throws in Node.js — use `bw.html()` instead.
10247
+ *
10248
+ * @param {Object} taco - TACO object with {t, a, c, o}
10249
+ * @param {Object} [options] - Creation options
10250
+ * @returns {Element|Text} DOM element or text node
10251
+ * @category DOM Generation
10252
+ * @see bw.html
10253
+ * @see bw.DOM
10254
+ * @example
10255
+ * var el = bw.createDOM({
10256
+ * t: 'button',
10257
+ * a: { class: 'bw_btn', onclick: () => alert('clicked') },
10258
+ * c: 'Click Me'
10259
+ * });
10260
+ * document.body.appendChild(el);
10261
+ */
10262
+ bw.createDOM = function (taco) {
10263
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
10264
+ if (!bw._isBrowser) {
10265
+ throw new Error('bw.createDOM requires a DOM environment (document/window). Use bw.html() instead.');
10266
+ }
10267
+
10268
+ // Handle null/undefined
10269
+ if (taco == null) return document.createTextNode('');
10270
+
10271
+ // Handle bw.raw() marked content — inject as HTML
10272
+ if (taco && taco.__bw_raw) {
10273
+ var frag = document.createDocumentFragment();
9717
10274
  var tmp = document.createElement('span');
9718
10275
  tmp.innerHTML = taco.v;
9719
10276
  while (tmp.firstChild) frag.appendChild(tmp.firstChild);
@@ -9726,7 +10283,7 @@
9726
10283
  }
9727
10284
 
9728
10285
  // Handle text nodes
9729
- if (_typeof(taco) !== 'object' || !taco.t) {
10286
+ if (!_is(taco, 'object') || !taco.t) {
9730
10287
  return document.createTextNode(String(taco));
9731
10288
  }
9732
10289
  var tag = taco.t,
@@ -9745,16 +10302,16 @@
9745
10302
  key = _Object$entries2$_i[0],
9746
10303
  value = _Object$entries2$_i[1];
9747
10304
  if (value == null || value === false) continue;
9748
- if (key === 'style' && _typeof(value) === 'object') {
10305
+ if (key === 'style' && _is(value, 'object')) {
9749
10306
  // Apply styles directly
9750
10307
  Object.assign(el.style, value);
9751
10308
  } else if (key === 'class') {
9752
10309
  // Handle class as array or string
9753
- var classStr = Array.isArray(value) ? value.filter(Boolean).join(' ') : String(value);
10310
+ var classStr = _isA(value) ? value.filter(Boolean).join(' ') : String(value);
9754
10311
  if (classStr) {
9755
10312
  el.className = classStr;
9756
10313
  }
9757
- } else if (key.startsWith('on') && typeof value === 'function') {
10314
+ } else if (key.startsWith('on') && _is(value, 'function')) {
9758
10315
  // Event handlers
9759
10316
  var eventName = key.slice(2).toLowerCase();
9760
10317
  el.addEventListener(eventName, value);
@@ -9774,7 +10331,7 @@
9774
10331
  // Children with data-bw_id or id attributes get local refs on the parent,
9775
10332
  // so o.render functions can access them without any DOM lookup.
9776
10333
  if (content != null) {
9777
- if (Array.isArray(content)) {
10334
+ if (_isA(content)) {
9778
10335
  content.forEach(function (child) {
9779
10336
  if (child != null) {
9780
10337
  // Handle ComponentHandle in content arrays (Level 2 children)
@@ -9794,20 +10351,20 @@
9794
10351
  if (childEl._bw_refs) {
9795
10352
  if (!el._bw_refs) el._bw_refs = {};
9796
10353
  for (var rk in childEl._bw_refs) {
9797
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
10354
+ if (_hop.call(childEl._bw_refs, rk)) {
9798
10355
  el._bw_refs[rk] = childEl._bw_refs[rk];
9799
10356
  }
9800
10357
  }
9801
10358
  }
9802
10359
  }
9803
10360
  });
9804
- } else if (_typeof(content) === 'object' && content.__bw_raw) {
10361
+ } else if (_is(content, 'object') && content.__bw_raw) {
9805
10362
  // Raw HTML content — inject via innerHTML
9806
10363
  el.innerHTML = content.v;
9807
10364
  } else if (content._bwComponent === true) {
9808
10365
  // Single ComponentHandle as content
9809
10366
  content.mount(el);
9810
- } else if (_typeof(content) === 'object' && content.t) {
10367
+ } else if (_is(content, 'object') && content.t) {
9811
10368
  var childEl = bw.createDOM(content, options);
9812
10369
  el.appendChild(childEl);
9813
10370
  var childBwId = content.a ? content.a['data-bw_id'] || content.a.id : null;
@@ -9818,7 +10375,7 @@
9818
10375
  if (childEl._bw_refs) {
9819
10376
  if (!el._bw_refs) el._bw_refs = {};
9820
10377
  for (var rk in childEl._bw_refs) {
9821
- if (Object.prototype.hasOwnProperty.call(childEl._bw_refs, rk)) {
10378
+ if (_hop.call(childEl._bw_refs, rk)) {
9822
10379
  el._bw_refs[rk] = childEl._bw_refs[rk];
9823
10380
  }
9824
10381
  }
@@ -9833,6 +10390,14 @@
9833
10390
  bw._registerNode(el, null);
9834
10391
  }
9835
10392
 
10393
+ // Register UUID class in node cache (bw_uuid_* tokens in class string)
10394
+ if (el.className) {
10395
+ var uuidMatch = el.className.match(_UUID_RE);
10396
+ if (uuidMatch) {
10397
+ bw._nodeMap[uuidMatch[0]] = el;
10398
+ }
10399
+ }
10400
+
9836
10401
  // Handle lifecycle hooks and state
9837
10402
  if (opts.mounted || opts.unmount || opts.render || opts.state) {
9838
10403
  var id = attrs['data-bw_id'] || bw.uuid();
@@ -9850,7 +10415,7 @@
9850
10415
  if (opts.render) {
9851
10416
  el._bw_render = opts.render;
9852
10417
  if (opts.mounted) {
9853
- console.warn('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
10418
+ _cw('bw.createDOM: o.render and o.mounted are mutually exclusive. o.render wins.');
9854
10419
  }
9855
10420
 
9856
10421
  // Queue initial render (same timing as mounted)
@@ -9922,7 +10487,7 @@
9922
10487
  // Get target element (use cache-backed lookup)
9923
10488
  var targetEl = bw._el(target);
9924
10489
  if (!targetEl) {
9925
- console.error('bw.DOM: Target element not found:', target);
10490
+ _ce('bw.DOM: Target element not found:', target);
9926
10491
  return null;
9927
10492
  }
9928
10493
 
@@ -9960,7 +10525,7 @@
9960
10525
  targetEl.appendChild(taco.element);
9961
10526
  }
9962
10527
  // Handle arrays
9963
- else if (Array.isArray(taco)) {
10528
+ else if (_isA(taco)) {
9964
10529
  taco.forEach(function (t) {
9965
10530
  if (t != null) {
9966
10531
  if (t._bwComponent === true) {
@@ -9995,7 +10560,7 @@
9995
10560
  bw.compileProps = function (handle) {
9996
10561
  var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
9997
10562
  var compiledProps = {};
9998
- Object.keys(props).forEach(function (key) {
10563
+ _keys(props).forEach(function (key) {
9999
10564
  // Create getter/setter for each prop
10000
10565
  Object.defineProperty(compiledProps, key, {
10001
10566
  get: function get() {
@@ -10192,6 +10757,16 @@
10192
10757
  bw.cleanup = function (element) {
10193
10758
  if (!bw._isBrowser || !element) return;
10194
10759
 
10760
+ // Deregister UUID classes from node cache (element + descendants)
10761
+ // Covers elements that have UUID but no data-bw_id
10762
+ var selfUuidMatch = element.className && element.className.match(_UUID_RE);
10763
+ if (selfUuidMatch) delete bw._nodeMap[selfUuidMatch[0]];
10764
+ var uuidEls = element.querySelectorAll('[class*="bw_uuid_"]');
10765
+ uuidEls.forEach(function (uel) {
10766
+ var m = uel.className && uel.className.match(_UUID_RE);
10767
+ if (m) delete bw._nodeMap[m[0]];
10768
+ });
10769
+
10195
10770
  // Find all elements with data-bw_id
10196
10771
  var elements = element.querySelectorAll('[data-bw_id]');
10197
10772
  elements.forEach(function (el) {
@@ -10205,6 +10780,10 @@
10205
10780
  // Deregister from node cache
10206
10781
  bw._deregisterNode(el, id);
10207
10782
 
10783
+ // Deregister UUID class from node cache
10784
+ var uuidMatch = el.className && el.className.match(_UUID_RE);
10785
+ if (uuidMatch) delete bw._nodeMap[uuidMatch[0]];
10786
+
10208
10787
  // Clean up pub/sub subscriptions tied to this element
10209
10788
  if (el._bw_subs) {
10210
10789
  el._bw_subs.forEach(function (unsub) {
@@ -10231,6 +10810,10 @@
10231
10810
  // Deregister from node cache
10232
10811
  bw._deregisterNode(element, id);
10233
10812
 
10813
+ // Deregister UUID class from node cache
10814
+ var elemUuidMatch = element.className && element.className.match(_UUID_RE);
10815
+ if (elemUuidMatch) delete bw._nodeMap[elemUuidMatch[0]];
10816
+
10234
10817
  // Clean up pub/sub subscriptions tied to element itself
10235
10818
  if (element._bw_subs) {
10236
10819
  element._bw_subs.forEach(function (unsub) {
@@ -10306,17 +10889,17 @@
10306
10889
  if (attr) {
10307
10890
  // Patch an attribute
10308
10891
  el.setAttribute(attr, String(content));
10309
- } else if (Array.isArray(content)) {
10892
+ } else if (_isA(content)) {
10310
10893
  // Patch with array of children (strings and/or TACOs)
10311
10894
  el.innerHTML = '';
10312
10895
  content.forEach(function (item) {
10313
- if (typeof item === 'string' || typeof item === 'number') {
10896
+ if (_is(item, 'string') || _is(item, 'number')) {
10314
10897
  el.appendChild(document.createTextNode(String(item)));
10315
10898
  } else if (item && item.t) {
10316
10899
  el.appendChild(bw.createDOM(item));
10317
10900
  }
10318
10901
  });
10319
- } else if (_typeof(content) === 'object' && content !== null && content.t) {
10902
+ } else if (_is(content, 'object') && content.t) {
10320
10903
  // Patch with a TACO — replace children
10321
10904
  el.innerHTML = '';
10322
10905
  el.appendChild(bw.createDOM(content));
@@ -10347,7 +10930,7 @@
10347
10930
  bw.patchAll = function (patches) {
10348
10931
  var results = {};
10349
10932
  for (var id in patches) {
10350
- if (Object.prototype.hasOwnProperty.call(patches, id)) {
10933
+ if (_hop.call(patches, id)) {
10351
10934
  results[id] = bw.patch(id, patches[id]);
10352
10935
  }
10353
10936
  }
@@ -10444,7 +11027,7 @@
10444
11027
  snapshot[i].handler(detail);
10445
11028
  called++;
10446
11029
  } catch (err) {
10447
- console.warn('bw.pub: subscriber error on topic "' + topic + '":', err);
11030
+ _cw('bw.pub: subscriber error on topic "' + topic + '":', err);
10448
11031
  }
10449
11032
  }
10450
11033
  return called;
@@ -10545,8 +11128,8 @@
10545
11128
  * @see bw.funcGetDispatchStr
10546
11129
  */
10547
11130
  bw.funcRegister = function (fn, name) {
10548
- if (typeof fn !== 'function') return '';
10549
- var fnID = typeof name === 'string' && name.length > 0 ? name : 'bw_fn_' + bw._fnIDCounter++;
11131
+ if (!_is(fn, 'function')) return '';
11132
+ var fnID = _is(name, 'string') && name.length > 0 ? name : 'bw_fn_' + bw._fnIDCounter++;
10550
11133
  bw._fnRegistry[fnID] = fn;
10551
11134
  return fnID;
10552
11135
  };
@@ -10565,8 +11148,8 @@
10565
11148
  bw.funcGetById = function (name, errFn) {
10566
11149
  name = String(name);
10567
11150
  if (name in bw._fnRegistry) return bw._fnRegistry[name];
10568
- return typeof errFn === 'function' ? errFn : function () {
10569
- console.warn('bw.funcGetById: unregistered fn "' + name + '"');
11151
+ return _is(errFn, 'function') ? errFn : function () {
11152
+ _cw('bw.funcGetById: unregistered fn "' + name + '"');
10570
11153
  };
10571
11154
  };
10572
11155
 
@@ -10608,13 +11191,23 @@
10608
11191
  bw.funcGetRegistry = function () {
10609
11192
  var copy = {};
10610
11193
  for (var k in bw._fnRegistry) {
10611
- if (Object.prototype.hasOwnProperty.call(bw._fnRegistry, k)) {
11194
+ if (_hop.call(bw._fnRegistry, k)) {
10612
11195
  copy[k] = bw._fnRegistry[k];
10613
11196
  }
10614
11197
  }
10615
11198
  return copy;
10616
11199
  };
10617
11200
 
11201
+ /**
11202
+ * Minimal runtime shim for funcRegister dispatch in static HTML.
11203
+ * When embedded in a `<script>` tag, provides just enough infrastructure
11204
+ * for `bw.funcGetById()` calls to resolve. The actual function bodies
11205
+ * are emitted separately as `bw._fnRegistry['bw_fn_X'] = ...;` assignments.
11206
+ * @type {string}
11207
+ * @category Function Registry
11208
+ */
11209
+ bw._FUNC_REGISTRY_SHIM = '(function(){var bw=window.bw||(window.bw={});' + 'if(!bw._fnRegistry)bw._fnRegistry={};' + 'bw.funcGetById=function(n){return bw._fnRegistry[n]||function(){' + 'console.warn("bw: unregistered fn "+n)};};' + 'bw.funcRegister=function(fn,name){' + 'var id=name||("bw_fn_"+(bw._fnIDCounter=(bw._fnIDCounter||0)+1));' + 'bw._fnRegistry[id]=fn;return id;};' + 'window.bw=bw;})();';
11210
+
10618
11211
  // ===================================================================================
10619
11212
  // Template Binding Utilities
10620
11213
  // ===================================================================================
@@ -10646,7 +11239,10 @@
10646
11239
  var parts = path.split('.');
10647
11240
  var val = state;
10648
11241
  for (var i = 0; i < parts.length; i++) {
10649
- if (val == null) return '';
11242
+ if (val == null) {
11243
+ if (bw.debug) _cw('bw.debug: _evaluatePath — null at key "' + parts[i] + '" in path "' + path + '"');
11244
+ return '';
11245
+ }
10650
11246
  val = val[parts[i]];
10651
11247
  }
10652
11248
  return val == null ? '' : val;
@@ -10666,7 +11262,7 @@
10666
11262
  */
10667
11263
  bw._compiledExprs = {};
10668
11264
  bw._resolveTemplate = function (str, state, compile) {
10669
- if (typeof str !== 'string' || str.indexOf('${') < 0) return str;
11265
+ if (!_is(str, 'string') || str.indexOf('${') < 0) return str;
10670
11266
  var bindings = bw._parseBindings(str);
10671
11267
  if (bindings.length === 0) return str;
10672
11268
  var result = '';
@@ -10689,6 +11285,7 @@
10689
11285
  try {
10690
11286
  val = bw._compiledExprs[b.expr](state);
10691
11287
  } catch (e) {
11288
+ if (bw.debug) _cw('bw.debug: _resolveTemplate — Tier 2 eval failed for "${' + b.expr + '}":', e.message);
10692
11289
  val = '';
10693
11290
  }
10694
11291
  } else {
@@ -10796,7 +11393,7 @@
10796
11393
  this._state = {};
10797
11394
  if (o.state) {
10798
11395
  for (var k in o.state) {
10799
- if (Object.prototype.hasOwnProperty.call(o.state, k)) {
11396
+ if (_hop.call(o.state, k)) {
10800
11397
  this._state[k] = o.state[k];
10801
11398
  }
10802
11399
  }
@@ -10805,7 +11402,7 @@
10805
11402
  this._actions = {};
10806
11403
  if (o.actions) {
10807
11404
  for (var k2 in o.actions) {
10808
- if (Object.prototype.hasOwnProperty.call(o.actions, k2)) {
11405
+ if (_hop.call(o.actions, k2)) {
10809
11406
  this._actions[k2] = o.actions[k2];
10810
11407
  }
10811
11408
  }
@@ -10815,7 +11412,7 @@
10815
11412
  if (o.methods) {
10816
11413
  var self = this;
10817
11414
  for (var k3 in o.methods) {
10818
- if (Object.prototype.hasOwnProperty.call(o.methods, k3)) {
11415
+ if (_hop.call(o.methods, k3)) {
10819
11416
  this._methods[k3] = o.methods[k3];
10820
11417
  (function (methodName, methodFn) {
10821
11418
  self[methodName] = function () {
@@ -10833,7 +11430,7 @@
10833
11430
  willMount: o.willMount || null,
10834
11431
  mounted: o.mounted || null,
10835
11432
  willUpdate: o.willUpdate || null,
10836
- onUpdate: o.onUpdate || null,
11433
+ onUpdate: o.onUpdate || o.updated || null,
10837
11434
  unmount: o.unmount || null,
10838
11435
  willDestroy: o.willDestroy || null
10839
11436
  };
@@ -10848,14 +11445,23 @@
10848
11445
  this._compile = !!o.compile;
10849
11446
  this._bw_refs = {};
10850
11447
  this._refCounter = 0;
11448
+ // Child component ownership (Bug #5)
11449
+ this._children = [];
11450
+ this._parent = null;
11451
+ // Factory metadata for BCCL rebuild (Bug #6)
11452
+ this._factory = taco._bwFactory || null;
10851
11453
  }
10852
11454
 
11455
+ // Short alias for ComponentHandle.prototype (see alias block at top of file).
11456
+ // 28 method definitions × 25 chars = ~700B raw savings in minified output.
11457
+ var _chp = ComponentHandle.prototype;
11458
+
10853
11459
  // ── State Methods ──
10854
11460
 
10855
11461
  /**
10856
11462
  * Get a state value. Dot-path supported: `get('user.name')`
10857
11463
  */
10858
- ComponentHandle.prototype.get = function (key) {
11464
+ _chp.get = function (key) {
10859
11465
  return bw._evaluatePath(this._state, key);
10860
11466
  };
10861
11467
 
@@ -10865,12 +11471,13 @@
10865
11471
  * @param {*} value - New value
10866
11472
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
10867
11473
  */
10868
- ComponentHandle.prototype.set = function (key, value, opts) {
11474
+ _chp.set = function (key, value, opts) {
10869
11475
  // Dot-path set
10870
11476
  var parts = key.split('.');
10871
11477
  var obj = this._state;
10872
11478
  for (var i = 0; i < parts.length - 1; i++) {
10873
- if (obj[parts[i]] == null || _typeof(obj[parts[i]]) !== 'object') {
11479
+ if (!_is(obj[parts[i]], 'object')) {
11480
+ if (bw.debug) _cw('bw.debug: set() — auto-creating intermediate "' + parts[i] + '" in path "' + key + '"');
10874
11481
  obj[parts[i]] = {};
10875
11482
  }
10876
11483
  obj = obj[parts[i]];
@@ -10890,10 +11497,10 @@
10890
11497
  /**
10891
11498
  * Get a shallow clone of the full state.
10892
11499
  */
10893
- ComponentHandle.prototype.getState = function () {
11500
+ _chp.getState = function () {
10894
11501
  var clone = {};
10895
11502
  for (var k in this._state) {
10896
- if (Object.prototype.hasOwnProperty.call(this._state, k)) {
11503
+ if (_hop.call(this._state, k)) {
10897
11504
  clone[k] = this._state[k];
10898
11505
  }
10899
11506
  }
@@ -10905,9 +11512,9 @@
10905
11512
  * @param {Object} updates - Key-value pairs to merge
10906
11513
  * @param {Object} [opts] - Options. `{sync: true}` for immediate flush.
10907
11514
  */
10908
- ComponentHandle.prototype.setState = function (updates, opts) {
11515
+ _chp.setState = function (updates, opts) {
10909
11516
  for (var k in updates) {
10910
- if (Object.prototype.hasOwnProperty.call(updates, k)) {
11517
+ if (_hop.call(updates, k)) {
10911
11518
  this._state[k] = updates[k];
10912
11519
  this._dirtyKeys[k] = true;
10913
11520
  }
@@ -10924,9 +11531,9 @@
10924
11531
  /**
10925
11532
  * Push a value onto an array in state. Clones the array.
10926
11533
  */
10927
- ComponentHandle.prototype.push = function (key, val) {
11534
+ _chp.push = function (key, val) {
10928
11535
  var arr = this.get(key);
10929
- var newArr = Array.isArray(arr) ? arr.slice() : [];
11536
+ var newArr = _isA(arr) ? arr.slice() : [];
10930
11537
  newArr.push(val);
10931
11538
  this.set(key, newArr);
10932
11539
  };
@@ -10934,9 +11541,9 @@
10934
11541
  /**
10935
11542
  * Splice an array in state. Clones the array.
10936
11543
  */
10937
- ComponentHandle.prototype.splice = function (key, start, deleteCount) {
11544
+ _chp.splice = function (key, start, deleteCount) {
10938
11545
  var arr = this.get(key);
10939
- var newArr = Array.isArray(arr) ? arr.slice() : [];
11546
+ var newArr = _isA(arr) ? arr.slice() : [];
10940
11547
  var args = [start, deleteCount].concat(Array.prototype.slice.call(arguments, 3));
10941
11548
  Array.prototype.splice.apply(newArr, args);
10942
11549
  this.set(key, newArr);
@@ -10944,7 +11551,7 @@
10944
11551
 
10945
11552
  // ── Scheduling ──
10946
11553
 
10947
- ComponentHandle.prototype._scheduleDirty = function () {
11554
+ _chp._scheduleDirty = function () {
10948
11555
  if (!this._scheduled) {
10949
11556
  this._scheduled = true;
10950
11557
  bw._dirtyComponents.push(this);
@@ -10959,16 +11566,16 @@
10959
11566
  * Creates binding descriptors with refIds for targeted DOM updates.
10960
11567
  * @private
10961
11568
  */
10962
- ComponentHandle.prototype._compileBindings = function () {
11569
+ _chp._compileBindings = function () {
10963
11570
  this._bindings = [];
10964
11571
  this._refCounter = 0;
10965
- var stateKeys = Object.keys(this._state);
11572
+ var stateKeys = _keys(this._state);
10966
11573
  var self = this;
10967
11574
  function walkTaco(taco, path) {
10968
- if (taco == null || _typeof(taco) !== 'object' || !taco.t) return taco;
11575
+ if (!_is(taco, 'object') || !taco.t) return taco;
10969
11576
 
10970
11577
  // Check content for bindings
10971
- if (typeof taco.c === 'string' && taco.c.indexOf('${') >= 0) {
11578
+ if (_is(taco.c, 'string') && taco.c.indexOf('${') >= 0) {
10972
11579
  var refId = 'bw_ref_' + self._refCounter++;
10973
11580
  var parsed = bw._parseBindings(taco.c);
10974
11581
  var deps = [];
@@ -10990,10 +11597,10 @@
10990
11597
  // Check attributes for bindings
10991
11598
  if (taco.a) {
10992
11599
  for (var attrName in taco.a) {
10993
- if (!Object.prototype.hasOwnProperty.call(taco.a, attrName)) continue;
11600
+ if (!_hop.call(taco.a, attrName)) continue;
10994
11601
  if (attrName === 'data-bw_ref') continue;
10995
11602
  var attrVal = taco.a[attrName];
10996
- if (typeof attrVal === 'string' && attrVal.indexOf('${') >= 0) {
11603
+ if (_is(attrVal, 'string') && attrVal.indexOf('${') >= 0) {
10997
11604
  var refId2 = 'bw_ref_' + self._refCounter++;
10998
11605
  var parsed2 = bw._parseBindings(attrVal);
10999
11606
  var deps2 = [];
@@ -11019,9 +11626,34 @@
11019
11626
  }
11020
11627
 
11021
11628
  // Recurse into children
11022
- if (Array.isArray(taco.c)) {
11629
+ if (_isA(taco.c)) {
11023
11630
  for (var i = 0; i < taco.c.length; i++) {
11024
- if (taco.c[i] && _typeof(taco.c[i]) === 'object' && taco.c[i].t) {
11631
+ // Wrap string children with ${expr} in a span so patches target the span, not the parent
11632
+ if (_is(taco.c[i], 'string') && taco.c[i].indexOf('${') >= 0) {
11633
+ var mixedRefId = 'bw_ref_' + self._refCounter++;
11634
+ var mixedParsed = bw._parseBindings(taco.c[i]);
11635
+ var mixedDeps = [];
11636
+ for (var mi = 0; mi < mixedParsed.length; mi++) {
11637
+ mixedDeps = mixedDeps.concat(bw._extractDeps(mixedParsed[mi].expr, stateKeys));
11638
+ }
11639
+ self._bindings.push({
11640
+ expr: taco.c[i],
11641
+ type: 'content',
11642
+ refId: mixedRefId,
11643
+ deps: mixedDeps,
11644
+ template: taco.c[i]
11645
+ });
11646
+ // Replace string with a span wrapper so textContent targets the span only
11647
+ taco.c[i] = {
11648
+ t: 'span',
11649
+ a: {
11650
+ 'data-bw_ref': mixedRefId,
11651
+ style: 'display:contents'
11652
+ },
11653
+ c: taco.c[i]
11654
+ };
11655
+ }
11656
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
11025
11657
  walkTaco(taco.c[i], path.concat(i));
11026
11658
  }
11027
11659
  // Handle bw.when/bw.each markers
@@ -11056,7 +11688,7 @@
11056
11688
  taco.c[i]._refId = eachRefId;
11057
11689
  }
11058
11690
  }
11059
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
11691
+ } else if (_is(taco.c, 'object') && taco.c.t) {
11060
11692
  walkTaco(taco.c, path.concat(0));
11061
11693
  }
11062
11694
  return taco;
@@ -11070,7 +11702,7 @@
11070
11702
  * Build ref map from the live DOM after createDOM.
11071
11703
  * @private
11072
11704
  */
11073
- ComponentHandle.prototype._collectRefs = function () {
11705
+ _chp._collectRefs = function () {
11074
11706
  this._bw_refs = {};
11075
11707
  if (!this.element) return;
11076
11708
  var els = this.element.querySelectorAll('[data-bw_ref]');
@@ -11091,7 +11723,7 @@
11091
11723
  * Creates DOM, compiles bindings, registers actions, and calls lifecycle hooks.
11092
11724
  * @param {Element} parentEl - DOM element to mount into
11093
11725
  */
11094
- ComponentHandle.prototype.mount = function (parentEl) {
11726
+ _chp.mount = function (parentEl) {
11095
11727
  // willMount hook
11096
11728
  if (this._hooks.willMount) this._hooks.willMount(this);
11097
11729
 
@@ -11113,7 +11745,7 @@
11113
11745
  // Register named actions in function registry
11114
11746
  var self = this;
11115
11747
  for (var actionName in this._actions) {
11116
- if (Object.prototype.hasOwnProperty.call(this._actions, actionName)) {
11748
+ if (_hop.call(this._actions, actionName)) {
11117
11749
  var registeredName = this._bwId + '_' + actionName;
11118
11750
  (function (aName) {
11119
11751
  bw.funcRegister(function (evt) {
@@ -11132,6 +11764,11 @@
11132
11764
  this.element = bw.createDOM(tacoForDOM);
11133
11765
  this.element._bwComponentHandle = this;
11134
11766
  this.element.setAttribute('data-bw_comp_id', this._bwId);
11767
+
11768
+ // Restore o.render from original TACO (stripped by _tacoForDOM)
11769
+ if (this.taco.o && this.taco.o.render) {
11770
+ this.element._bw_render = this.taco.o.render;
11771
+ }
11135
11772
  if (this._userTag) {
11136
11773
  this.element.classList.add(this._userTag);
11137
11774
  }
@@ -11146,6 +11783,16 @@
11146
11783
  this._resolveAndApplyAll();
11147
11784
  this.mounted = true;
11148
11785
 
11786
+ // Scan for child ComponentHandles and link parent/child (Bug #5)
11787
+ var childEls = this.element.querySelectorAll('[data-bw_comp_id]');
11788
+ for (var ci = 0; ci < childEls.length; ci++) {
11789
+ var ch = childEls[ci]._bwComponentHandle;
11790
+ if (ch && ch !== this && !ch._parent) {
11791
+ ch._parent = this;
11792
+ this._children.push(ch);
11793
+ }
11794
+ }
11795
+
11149
11796
  // mounted hook (backward compat: fn.length === 2 wraps (el, state))
11150
11797
  if (this._hooks.mounted) {
11151
11798
  if (this._hooks.mounted.length === 2) {
@@ -11154,15 +11801,20 @@
11154
11801
  this._hooks.mounted(this);
11155
11802
  }
11156
11803
  }
11804
+
11805
+ // Invoke o.render on initial mount (if present)
11806
+ if (this.element._bw_render) {
11807
+ this.element._bw_render(this.element, this._state);
11808
+ }
11157
11809
  };
11158
11810
 
11159
11811
  /**
11160
11812
  * Prepare TACO for initial render: resolve when/each markers.
11161
11813
  * @private
11162
11814
  */
11163
- ComponentHandle.prototype._prepareTaco = function (taco) {
11164
- if (!taco || _typeof(taco) !== 'object') return;
11165
- if (Array.isArray(taco.c)) {
11815
+ _chp._prepareTaco = function (taco) {
11816
+ if (!_is(taco, 'object')) return;
11817
+ if (_isA(taco.c)) {
11166
11818
  for (var i = taco.c.length - 1; i >= 0; i--) {
11167
11819
  var child = taco.c[i];
11168
11820
  if (child && child._bwWhen) {
@@ -11203,7 +11855,7 @@
11203
11855
  var eachExprStr = child.expr.replace(/^\$\{|\}$/g, '');
11204
11856
  var arr = bw._evaluatePath(this._state, eachExprStr);
11205
11857
  var items = [];
11206
- if (Array.isArray(arr)) {
11858
+ if (_isA(arr)) {
11207
11859
  for (var j = 0; j < arr.length; j++) {
11208
11860
  items.push(child.factory(arr[j], j));
11209
11861
  }
@@ -11217,11 +11869,11 @@
11217
11869
  c: items
11218
11870
  };
11219
11871
  }
11220
- if (taco.c[i] && _typeof(taco.c[i]) === 'object' && taco.c[i].t) {
11872
+ if (_is(taco.c[i], 'object') && taco.c[i].t) {
11221
11873
  this._prepareTaco(taco.c[i]);
11222
11874
  }
11223
11875
  }
11224
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
11876
+ } else if (_is(taco.c, 'object') && taco.c.t) {
11225
11877
  this._prepareTaco(taco.c);
11226
11878
  }
11227
11879
  };
@@ -11230,12 +11882,12 @@
11230
11882
  * Wire action name strings (in onclick etc.) to dispatch function calls.
11231
11883
  * @private
11232
11884
  */
11233
- ComponentHandle.prototype._wireActions = function (taco) {
11234
- if (!taco || _typeof(taco) !== 'object' || !taco.t) return;
11885
+ _chp._wireActions = function (taco) {
11886
+ if (!_is(taco, 'object') || !taco.t) return;
11235
11887
  if (taco.a) {
11236
11888
  for (var key in taco.a) {
11237
- if (!Object.prototype.hasOwnProperty.call(taco.a, key)) continue;
11238
- if (key.startsWith('on') && typeof taco.a[key] === 'string') {
11889
+ if (!_hop.call(taco.a, key)) continue;
11890
+ if (key.startsWith('on') && _is(taco.a[key], 'string')) {
11239
11891
  var actionName = taco.a[key];
11240
11892
  if (actionName in this._actions) {
11241
11893
  var registeredName = this._bwId + '_' + actionName;
@@ -11249,11 +11901,11 @@
11249
11901
  }
11250
11902
  }
11251
11903
  }
11252
- if (Array.isArray(taco.c)) {
11904
+ if (_isA(taco.c)) {
11253
11905
  for (var i = 0; i < taco.c.length; i++) {
11254
11906
  this._wireActions(taco.c[i]);
11255
11907
  }
11256
- } else if (taco.c && _typeof(taco.c) === 'object' && taco.c.t) {
11908
+ } else if (_is(taco.c, 'object') && taco.c.t) {
11257
11909
  this._wireActions(taco.c);
11258
11910
  }
11259
11911
  };
@@ -11262,7 +11914,7 @@
11262
11914
  * Deep-clone a TACO tree, preserving _bwWhen/_bwEach markers and their factories.
11263
11915
  * @private
11264
11916
  */
11265
- ComponentHandle.prototype._deepCloneTaco = function (taco) {
11917
+ _chp._deepCloneTaco = function (taco) {
11266
11918
  if (taco == null) return taco;
11267
11919
  // Preserve _bwWhen / _bwEach markers (contain functions)
11268
11920
  if (taco._bwWhen) {
@@ -11281,22 +11933,22 @@
11281
11933
  _refId: taco._refId
11282
11934
  };
11283
11935
  }
11284
- if (_typeof(taco) !== 'object' || !taco.t) return taco;
11936
+ if (!_is(taco, 'object') || !taco.t) return taco;
11285
11937
  var result = {
11286
11938
  t: taco.t
11287
11939
  };
11288
11940
  if (taco.a) {
11289
11941
  result.a = {};
11290
11942
  for (var k in taco.a) {
11291
- if (Object.prototype.hasOwnProperty.call(taco.a, k)) result.a[k] = taco.a[k];
11943
+ if (_hop.call(taco.a, k)) result.a[k] = taco.a[k];
11292
11944
  }
11293
11945
  }
11294
11946
  if (taco.c != null) {
11295
- if (Array.isArray(taco.c)) {
11947
+ if (_isA(taco.c)) {
11296
11948
  result.c = taco.c.map(function (child) {
11297
11949
  return this._deepCloneTaco(child);
11298
11950
  }.bind(this));
11299
- } else if (_typeof(taco.c) === 'object') {
11951
+ } else if (_is(taco.c, 'object')) {
11300
11952
  result.c = this._deepCloneTaco(taco.c);
11301
11953
  } else {
11302
11954
  result.c = taco.c;
@@ -11310,31 +11962,34 @@
11310
11962
  * Create a copy of TACO suitable for createDOM (strips o to prevent double lifecycle).
11311
11963
  * @private
11312
11964
  */
11313
- ComponentHandle.prototype._tacoForDOM = function (taco) {
11314
- if (!taco || _typeof(taco) !== 'object' || !taco.t) return taco;
11965
+ _chp._tacoForDOM = function (taco) {
11966
+ if (!_is(taco, 'object') || !taco.t) return taco;
11315
11967
  var result = {
11316
11968
  t: taco.t
11317
11969
  };
11318
11970
  if (taco.a) result.a = taco.a;
11319
11971
  if (taco.c != null) {
11320
- if (Array.isArray(taco.c)) {
11972
+ if (_isA(taco.c)) {
11321
11973
  result.c = taco.c.map(function (child) {
11322
11974
  return this._tacoForDOM(child);
11323
11975
  }.bind(this));
11324
- } else if (_typeof(taco.c) === 'object' && taco.c.t) {
11976
+ } else if (_is(taco.c, 'object') && taco.c.t) {
11325
11977
  result.c = this._tacoForDOM(taco.c);
11326
11978
  } else {
11327
11979
  result.c = taco.c;
11328
11980
  }
11329
11981
  }
11330
11982
  // Intentionally strip o (no mounted/unmount/state/render on sub-elements)
11983
+ if (taco.o && (taco.o.mounted || taco.o.render || taco.o.unmount)) {
11984
+ _cw('bw: _tacoForDOM stripped o.mounted/render/unmount from child <' + taco.t + '>. Use onclick attribute or bw.component() for child interactivity.');
11985
+ }
11331
11986
  return result;
11332
11987
  };
11333
11988
 
11334
11989
  /**
11335
11990
  * Unmount: remove from DOM, deactivate, preserve state for re-mount.
11336
11991
  */
11337
- ComponentHandle.prototype.unmount = function () {
11992
+ _chp.unmount = function () {
11338
11993
  if (!this.mounted) return;
11339
11994
 
11340
11995
  // unmount hook
@@ -11368,11 +12023,22 @@
11368
12023
  /**
11369
12024
  * Destroy: unmount + clear state + unregister actions.
11370
12025
  */
11371
- ComponentHandle.prototype.destroy = function () {
12026
+ _chp.destroy = function () {
11372
12027
  // willDestroy hook
11373
12028
  if (this._hooks.willDestroy) {
11374
12029
  this._hooks.willDestroy(this);
11375
12030
  }
12031
+
12032
+ // Cascade destroy to children depth-first (Bug #5)
12033
+ for (var ci = this._children.length - 1; ci >= 0; ci--) {
12034
+ this._children[ci].destroy();
12035
+ }
12036
+ this._children = [];
12037
+ if (this._parent) {
12038
+ var idx = this._parent._children.indexOf(this);
12039
+ if (idx >= 0) this._parent._children.splice(idx, 1);
12040
+ this._parent = null;
12041
+ }
11376
12042
  this.unmount();
11377
12043
 
11378
12044
  // Unregister actions from function registry
@@ -11399,12 +12065,37 @@
11399
12065
  * Flush dirty state: resolve changed bindings and apply to DOM.
11400
12066
  * @private
11401
12067
  */
11402
- ComponentHandle.prototype._flush = function () {
12068
+ _chp._flush = function () {
11403
12069
  this._scheduled = false;
11404
- var changedKeys = Object.keys(this._dirtyKeys);
12070
+ var changedKeys = _keys(this._dirtyKeys);
11405
12071
  this._dirtyKeys = {};
11406
12072
  if (changedKeys.length === 0 || !this.mounted) return;
11407
12073
 
12074
+ // Factory rebuild: if a BCCL factory exists and changed keys overlap factory props,
12075
+ // rebuild the TACO from the factory with merged state (Bug #6)
12076
+ if (this._factory) {
12077
+ var rebuildNeeded = false;
12078
+ for (var fi = 0; fi < changedKeys.length; fi++) {
12079
+ if (_hop.call(this._factory.props, changedKeys[fi])) {
12080
+ rebuildNeeded = true;
12081
+ break;
12082
+ }
12083
+ }
12084
+ if (rebuildNeeded) {
12085
+ var merged = {};
12086
+ for (var mk in this._factory.props) if (_hop.call(this._factory.props, mk)) merged[mk] = this._factory.props[mk];
12087
+ for (var sk in this._state) if (_hop.call(this._state, sk)) merged[sk] = this._state[sk];
12088
+ this._factory.props = merged;
12089
+ var newTaco = bw.make(this._factory.type, merged);
12090
+ newTaco._bwFactory = this._factory;
12091
+ this.taco = newTaco;
12092
+ this._originalTaco = this._deepCloneTaco(newTaco);
12093
+ this._render();
12094
+ if (this._hooks.onUpdate) this._hooks.onUpdate(this, changedKeys);
12095
+ return;
12096
+ }
12097
+ }
12098
+
11408
12099
  // willUpdate hook
11409
12100
  if (this._hooks.willUpdate) {
11410
12101
  this._hooks.willUpdate(this, changedKeys);
@@ -11442,7 +12133,7 @@
11442
12133
  * Returns list of patches to apply.
11443
12134
  * @private
11444
12135
  */
11445
- ComponentHandle.prototype._resolveBindings = function (changedKeys) {
12136
+ _chp._resolveBindings = function (changedKeys) {
11446
12137
  var patches = [];
11447
12138
  for (var i = 0; i < this._bindings.length; i++) {
11448
12139
  var b = this._bindings[i];
@@ -11478,11 +12169,14 @@
11478
12169
  * Apply patches to DOM.
11479
12170
  * @private
11480
12171
  */
11481
- ComponentHandle.prototype._applyPatches = function (patches) {
12172
+ _chp._applyPatches = function (patches) {
11482
12173
  for (var i = 0; i < patches.length; i++) {
11483
12174
  var p = patches[i];
11484
12175
  var el = this._bw_refs[p.refId];
11485
- if (!el) continue;
12176
+ if (!el) {
12177
+ if (bw.debug) _cw('bw.debug: _applyPatches — ref "' + p.refId + '" not found in DOM');
12178
+ continue;
12179
+ }
11486
12180
  if (p.type === 'content') {
11487
12181
  el.textContent = p.value;
11488
12182
  } else if (p.type === 'attribute') {
@@ -11499,7 +12193,7 @@
11499
12193
  * Resolve all bindings and apply (used for initial render).
11500
12194
  * @private
11501
12195
  */
11502
- ComponentHandle.prototype._resolveAndApplyAll = function () {
12196
+ _chp._resolveAndApplyAll = function () {
11503
12197
  var patches = [];
11504
12198
  for (var i = 0; i < this._bindings.length; i++) {
11505
12199
  var b = this._bindings[i];
@@ -11521,7 +12215,7 @@
11521
12215
  * Full re-render for structural changes (when/each branch switches).
11522
12216
  * @private
11523
12217
  */
11524
- ComponentHandle.prototype._render = function () {
12218
+ _chp._render = function () {
11525
12219
  if (!this.element || !this.element.parentNode) return;
11526
12220
  var parent = this.element.parentNode;
11527
12221
  var nextSibling = this.element.nextSibling;
@@ -11560,7 +12254,7 @@
11560
12254
  * @param {string} event - Event name (e.g., 'click')
11561
12255
  * @param {Function} handler - Event handler
11562
12256
  */
11563
- ComponentHandle.prototype.on = function (event, handler) {
12257
+ _chp.on = function (event, handler) {
11564
12258
  if (this.element) {
11565
12259
  this.element.addEventListener(event, handler);
11566
12260
  }
@@ -11575,7 +12269,7 @@
11575
12269
  * @param {string} event - Event name
11576
12270
  * @param {Function} handler - Handler to remove
11577
12271
  */
11578
- ComponentHandle.prototype.off = function (event, handler) {
12272
+ _chp.off = function (event, handler) {
11579
12273
  if (this.element) {
11580
12274
  this.element.removeEventListener(event, handler);
11581
12275
  }
@@ -11590,7 +12284,7 @@
11590
12284
  * @param {Function} handler - Handler function
11591
12285
  * @returns {Function} Unsubscribe function
11592
12286
  */
11593
- ComponentHandle.prototype.sub = function (topic, handler) {
12287
+ _chp.sub = function (topic, handler) {
11594
12288
  var unsub = bw.sub(topic, handler);
11595
12289
  this._subs.push(unsub);
11596
12290
  return unsub;
@@ -11601,10 +12295,10 @@
11601
12295
  * @param {string} name - Action name
11602
12296
  * @param {...*} args - Arguments passed after comp
11603
12297
  */
11604
- ComponentHandle.prototype.action = function (name) {
12298
+ _chp.action = function (name) {
11605
12299
  var fn = this._actions[name];
11606
12300
  if (!fn) {
11607
- console.warn('ComponentHandle.action: unknown action "' + name + '"');
12301
+ _cw('ComponentHandle.action: unknown action "' + name + '"');
11608
12302
  return;
11609
12303
  }
11610
12304
  var args = [this].concat(Array.prototype.slice.call(arguments, 1));
@@ -11616,7 +12310,7 @@
11616
12310
  * @param {string} sel - CSS selector
11617
12311
  * @returns {Element|null}
11618
12312
  */
11619
- ComponentHandle.prototype.select = function (sel) {
12313
+ _chp.select = function (sel) {
11620
12314
  return this.element ? this.element.querySelector(sel) : null;
11621
12315
  };
11622
12316
 
@@ -11625,7 +12319,7 @@
11625
12319
  * @param {string} sel - CSS selector
11626
12320
  * @returns {Element[]}
11627
12321
  */
11628
- ComponentHandle.prototype.selectAll = function (sel) {
12322
+ _chp.selectAll = function (sel) {
11629
12323
  if (!this.element) return [];
11630
12324
  return Array.prototype.slice.call(this.element.querySelectorAll(sel));
11631
12325
  };
@@ -11636,7 +12330,7 @@
11636
12330
  * @param {string} tag - User-defined identifier (e.g. 'dashboard_prod_east')
11637
12331
  * @returns {ComponentHandle} this (for chaining)
11638
12332
  */
11639
- ComponentHandle.prototype.userTag = function (tag) {
12333
+ _chp.userTag = function (tag) {
11640
12334
  this._userTag = tag;
11641
12335
  if (this.element) {
11642
12336
  this.element.classList.add(tag);
@@ -11721,7 +12415,7 @@
11721
12415
  * and calls the named method. This is the bitwrench equivalent of
11722
12416
  * Win32 SendMessage(hwnd, msg, wParam, lParam).
11723
12417
  *
11724
- * @param {string} target - Component UUID (data-bw_comp_id) or user tag (CSS class)
12418
+ * @param {string} target - Component UUID (bw_uuid_*), comp ID (data-bw_comp_id), or user tag (CSS class)
11725
12419
  * @param {string} action - Method name to call on the component
11726
12420
  * @param {*} data - Data to pass to the method
11727
12421
  * @returns {boolean} True if message was dispatched successfully
@@ -11738,15 +12432,20 @@
11738
12432
  * };
11739
12433
  */
11740
12434
  bw.message = function (target, action, data) {
11741
- // Try data-bw_comp_id attribute first, then CSS class (user tag)
11742
- var el = bw.$('[data-bw_comp_id="' + target + '"]')[0];
11743
- if (!el) {
12435
+ // Try bw._el() first (handles UUID class, nodeMap cache, getElementById)
12436
+ var el = bw._el(target);
12437
+ // Then try data-bw_comp_id attribute
12438
+ if (!el || !el._bwComponentHandle) {
12439
+ el = bw.$('[data-bw_comp_id="' + target + '"]')[0];
12440
+ }
12441
+ // Then try CSS class (user tag)
12442
+ if (!el || !el._bwComponentHandle) {
11744
12443
  el = bw.$('.' + target)[0];
11745
12444
  }
11746
12445
  if (!el || !el._bwComponentHandle) return false;
11747
12446
  var comp = el._bwComponentHandle;
11748
- if (typeof comp[action] !== 'function') {
11749
- console.warn('bw.message: unknown action "' + action + '" on component ' + target);
12447
+ if (!_is(comp[action], 'function')) {
12448
+ _cw('bw.message: unknown action "' + action + '" on component ' + target);
11750
12449
  return false;
11751
12450
  }
11752
12451
  comp[action](data);
@@ -11754,61 +12453,24 @@
11754
12453
  };
11755
12454
 
11756
12455
  // ===================================================================================
11757
- // bw.clientApply() / bw.clientConnect() — Server-driven UI protocol
12456
+ // bw.apply() / bw.parseJSONFlex() — Server-driven UI protocol
11758
12457
  // ===================================================================================
11759
12458
 
11760
12459
  /**
11761
12460
  * Registry of named functions sent via register messages.
11762
- * Populated by clientApply({ type: 'register', name, body }).
11763
- * Invoked by clientApply({ type: 'call', name, args }).
12461
+ * Populated by bw.apply({ type: 'register', name, body }).
12462
+ * Invoked by bw.apply({ type: 'call', name, args }).
11764
12463
  * @private
11765
12464
  */
11766
12465
  bw._clientFunctions = {};
11767
12466
 
11768
12467
  /**
11769
- * Whether exec messages are allowed. Set by clientConnect opts.allowExec.
12468
+ * Whether exec messages are allowed. Set by bwclient connect opts.allowExec.
11770
12469
  * Default false — exec messages are rejected unless explicitly opted in.
11771
12470
  * @private
11772
12471
  */
11773
12472
  bw._allowExec = false;
11774
12473
 
11775
- /**
11776
- * Built-in client functions available via call() without registration.
11777
- * @private
11778
- */
11779
- bw._builtinClientFunctions = {
11780
- scrollTo: function scrollTo(selector) {
11781
- var el = bw._el(selector);
11782
- if (el) el.scrollTop = el.scrollHeight;
11783
- },
11784
- focus: function focus(selector) {
11785
- var el = bw._el(selector);
11786
- if (el && typeof el.focus === 'function') el.focus();
11787
- },
11788
- download: function download(filename, content, mimeType) {
11789
- if (typeof document === 'undefined') return;
11790
- var blob = new Blob([content], {
11791
- type: mimeType || 'text/plain'
11792
- });
11793
- var a = document.createElement('a');
11794
- a.href = URL.createObjectURL(blob);
11795
- a.download = filename;
11796
- a.click();
11797
- URL.revokeObjectURL(a.href);
11798
- },
11799
- clipboard: function clipboard(text) {
11800
- if (typeof navigator !== 'undefined' && navigator.clipboard) {
11801
- navigator.clipboard.writeText(text);
11802
- }
11803
- },
11804
- redirect: function redirect(url) {
11805
- if (typeof window !== 'undefined') window.location.href = url;
11806
- },
11807
- log: function log() {
11808
- console.log.apply(console, arguments);
11809
- }
11810
- };
11811
-
11812
12474
  /**
11813
12475
  * Parse a bwserve protocol message string, supporting both strict JSON
11814
12476
  * and r-prefixed relaxed JSON (single-quoted strings, trailing commas).
@@ -11823,9 +12485,9 @@
11823
12485
  * @param {string} str - JSON or r-prefixed relaxed JSON string
11824
12486
  * @returns {Object} Parsed message object
11825
12487
  * @throws {SyntaxError} If the string is not valid JSON or relaxed JSON
11826
- * @category Server
12488
+ * @category Core
11827
12489
  */
11828
- bw.clientParse = function (str) {
12490
+ bw.parseJSONFlex = function (str) {
11829
12491
  str = (str || '').trim();
11830
12492
  if (str.charAt(0) !== 'r') return JSON.parse(str);
11831
12493
  str = str.slice(1);
@@ -11903,10 +12565,10 @@
11903
12565
  * append — target.appendChild(bw.createDOM(node))
11904
12566
  * remove — bw.cleanup(target); target.remove()
11905
12567
  * patch — bw.patch(target, content, attr)
11906
- * batch — iterate ops, call clientApply for each
12568
+ * batch — iterate ops, call bw.apply for each
11907
12569
  * message — bw.message(target, action, data)
11908
12570
  * register — store a named function for later call()
11909
- * call — invoke a registered or built-in function
12571
+ * call — invoke a registered function
11910
12572
  * exec — execute arbitrary JS (requires allowExec)
11911
12573
  *
11912
12574
  * Target resolution:
@@ -11915,9 +12577,9 @@
11915
12577
  *
11916
12578
  * @param {Object} msg - Protocol message
11917
12579
  * @returns {boolean} true if the message was applied successfully
11918
- * @category Server
12580
+ * @category Core
11919
12581
  */
11920
- bw.clientApply = function (msg) {
12582
+ bw.apply = function (msg) {
11921
12583
  if (!msg || !msg.type) return false;
11922
12584
  var type = msg.type;
11923
12585
  var target = msg.target;
@@ -11938,14 +12600,14 @@
11938
12600
  } else if (type === 'remove') {
11939
12601
  var toRemove = bw._el(target);
11940
12602
  if (!toRemove) return false;
11941
- if (typeof bw.cleanup === 'function') bw.cleanup(toRemove);
12603
+ if (_is(bw.cleanup, 'function')) bw.cleanup(toRemove);
11942
12604
  toRemove.remove();
11943
12605
  return true;
11944
12606
  } else if (type === 'batch') {
11945
- if (!Array.isArray(msg.ops)) return false;
12607
+ if (!_isA(msg.ops)) return false;
11946
12608
  var allOk = true;
11947
12609
  msg.ops.forEach(function (op) {
11948
- if (!bw.clientApply(op)) allOk = false;
12610
+ if (!bw.apply(op)) allOk = false;
11949
12611
  });
11950
12612
  return allOk;
11951
12613
  } else if (type === 'message') {
@@ -11956,24 +12618,24 @@
11956
12618
  bw._clientFunctions[msg.name] = new Function('return ' + msg.body)();
11957
12619
  return true;
11958
12620
  } catch (e) {
11959
- console.error('[bw] register error:', msg.name, e);
12621
+ _ce('[bw] register error:', msg.name, e);
11960
12622
  return false;
11961
12623
  }
11962
12624
  } else if (type === 'call') {
11963
12625
  if (!msg.name) return false;
11964
- var fn = bw._clientFunctions[msg.name] || bw._builtinClientFunctions[msg.name];
11965
- if (typeof fn !== 'function') return false;
12626
+ var fn = bw._clientFunctions[msg.name];
12627
+ if (!_is(fn, 'function')) return false;
11966
12628
  try {
11967
- var args = Array.isArray(msg.args) ? msg.args : [];
12629
+ var args = _isA(msg.args) ? msg.args : [];
11968
12630
  fn.apply(null, args);
11969
12631
  return true;
11970
12632
  } catch (e) {
11971
- console.error('[bw] call error:', msg.name, e);
12633
+ _ce('[bw] call error:', msg.name, e);
11972
12634
  return false;
11973
12635
  }
11974
12636
  } else if (type === 'exec') {
11975
12637
  if (!bw._allowExec) {
11976
- console.warn('[bw] exec rejected: allowExec is not enabled');
12638
+ _cw('[bw] exec rejected: allowExec is not enabled');
11977
12639
  return false;
11978
12640
  }
11979
12641
  if (!msg.code) return false;
@@ -11981,148 +12643,13 @@
11981
12643
  new Function(msg.code)();
11982
12644
  return true;
11983
12645
  } catch (e) {
11984
- console.error('[bw] exec error:', e);
12646
+ _ce('[bw] exec error:', e);
11985
12647
  return false;
11986
12648
  }
11987
12649
  }
11988
12650
  return false;
11989
12651
  };
11990
12652
 
11991
- /**
11992
- * Connect to a bwserve SSE endpoint and apply protocol messages automatically.
11993
- *
11994
- * Returns a connection object with sendAction(), on(), and close() methods.
11995
- *
11996
- * @param {string} url - SSE endpoint URL (e.g., '/__bw/events/client-1')
11997
- * @param {Object} [opts] - Connection options
11998
- * @param {string} [opts.transport='sse'] - Transport type: 'sse' (default) or 'poll'
11999
- * @param {number} [opts.interval=2000] - Poll interval in ms (only for 'poll' transport)
12000
- * @param {string} [opts.actionUrl] - POST endpoint for actions (default: derived from url)
12001
- * @param {boolean} [opts.reconnect=true] - Auto-reconnect on disconnect
12002
- * @param {boolean} [opts.allowExec=false] - Enable exec message type (arbitrary JS execution)
12003
- * @param {Function} [opts.onStatus] - Status callback: 'connecting'|'connected'|'disconnected'
12004
- * @param {Function} [opts.onMessage] - Raw message callback (before clientApply)
12005
- * @returns {Object} Connection object { sendAction, on, close, status }
12006
- * @category Server
12007
- */
12008
- bw.clientConnect = function (url, opts) {
12009
- opts = opts || {};
12010
- var transport = opts.transport || 'sse';
12011
- var actionUrl = opts.actionUrl || url.replace(/\/events\//, '/action/');
12012
- var reconnect = opts.reconnect !== false;
12013
- var onStatus = opts.onStatus || function () {};
12014
- var onMessage = opts.onMessage || null;
12015
- var handlers = {};
12016
- // Set the global allowExec flag from connection options
12017
- bw._allowExec = !!opts.allowExec;
12018
- var conn = {
12019
- status: 'connecting',
12020
- _es: null,
12021
- _pollTimer: null
12022
- };
12023
- function setStatus(s) {
12024
- conn.status = s;
12025
- onStatus(s);
12026
- }
12027
- function handleMessage(data) {
12028
- try {
12029
- var msg = typeof data === 'string' ? bw.clientParse(data) : data;
12030
- if (onMessage) onMessage(msg);
12031
- if (handlers.message) handlers.message(msg);
12032
- bw.clientApply(msg);
12033
- } catch (e) {
12034
- if (handlers.error) handlers.error(e);
12035
- }
12036
- }
12037
- if (transport === 'sse' && typeof EventSource !== 'undefined') {
12038
- setStatus('connecting');
12039
- var es = new EventSource(url);
12040
- conn._es = es;
12041
- es.onopen = function () {
12042
- setStatus('connected');
12043
- if (handlers.open) handlers.open();
12044
- };
12045
- es.onmessage = function (e) {
12046
- handleMessage(e.data);
12047
- };
12048
- es.onerror = function () {
12049
- if (conn.status === 'connected') {
12050
- setStatus('disconnected');
12051
- }
12052
- if (handlers.error) handlers.error(new Error('SSE connection error'));
12053
- if (!reconnect) {
12054
- es.close();
12055
- }
12056
- // EventSource auto-reconnects by default when reconnect=true
12057
- };
12058
- } else if (transport === 'poll') {
12059
- var interval = opts.interval || 2000;
12060
- setStatus('connected');
12061
- conn._pollTimer = setInterval(function () {
12062
- fetch(url).then(function (r) {
12063
- return r.json();
12064
- }).then(function (msgs) {
12065
- if (Array.isArray(msgs)) {
12066
- msgs.forEach(handleMessage);
12067
- } else if (msgs && msgs.type) {
12068
- handleMessage(msgs);
12069
- }
12070
- })["catch"](function (e) {
12071
- if (handlers.error) handlers.error(e);
12072
- });
12073
- }, interval);
12074
- }
12075
-
12076
- /**
12077
- * Send an action to the server via POST.
12078
- * @param {string} action - Action name
12079
- * @param {Object} [data] - Action payload
12080
- */
12081
- conn.sendAction = function (action, data) {
12082
- var body = JSON.stringify({
12083
- type: 'action',
12084
- action: action,
12085
- data: data || {}
12086
- });
12087
- fetch(actionUrl, {
12088
- method: 'POST',
12089
- headers: {
12090
- 'Content-Type': 'application/json'
12091
- },
12092
- body: body
12093
- })["catch"](function (e) {
12094
- if (handlers.error) handlers.error(e);
12095
- });
12096
- };
12097
-
12098
- /**
12099
- * Register an event handler.
12100
- * @param {string} event - 'open'|'message'|'error'|'close'
12101
- * @param {Function} handler
12102
- */
12103
- conn.on = function (event, handler) {
12104
- handlers[event] = handler;
12105
- return conn;
12106
- };
12107
-
12108
- /**
12109
- * Close the connection.
12110
- */
12111
- conn.close = function () {
12112
- if (conn._es) {
12113
- conn._es.close();
12114
- conn._es = null;
12115
- }
12116
- if (conn._pollTimer) {
12117
- clearInterval(conn._pollTimer);
12118
- conn._pollTimer = null;
12119
- }
12120
- setStatus('disconnected');
12121
- if (handlers.close) handlers.close();
12122
- };
12123
- return conn;
12124
- };
12125
-
12126
12653
  // ===================================================================================
12127
12654
  // bw.inspect() — Debug utility
12128
12655
  // ===================================================================================
@@ -12149,20 +12676,20 @@
12149
12676
  el = target.element;
12150
12677
  comp = target;
12151
12678
  } else {
12152
- if (typeof target === 'string') {
12679
+ if (_is(target, 'string')) {
12153
12680
  el = bw.$(target)[0];
12154
12681
  }
12155
12682
  if (!el) {
12156
- console.warn('bw.inspect: element not found');
12683
+ _cw('bw.inspect: element not found');
12157
12684
  return null;
12158
12685
  }
12159
12686
  comp = el._bwComponentHandle;
12160
12687
  }
12161
12688
  if (!comp) {
12162
- console.log('bw.inspect: no ComponentHandle on this element');
12163
- console.log(' Tag:', el.tagName);
12164
- console.log(' Classes:', el.className);
12165
- console.log(' _bw_state:', el._bw_state || '(none)');
12689
+ _cl('bw.inspect: no ComponentHandle on this element');
12690
+ _cl(' Tag:', el.tagName);
12691
+ _cl(' Classes:', el.className);
12692
+ _cl(' _bw_state:', el._bw_state || '(none)');
12166
12693
  return null;
12167
12694
  }
12168
12695
  var deps = comp._bindings.reduce(function (s, b) {
@@ -12171,13 +12698,13 @@
12171
12698
  return a.indexOf(v) === i;
12172
12699
  });
12173
12700
  console.group('Component: ' + comp._bwId);
12174
- console.log('State:', comp._state);
12175
- console.log('Bindings:', comp._bindings.length, '(deps:', deps, ')');
12176
- console.log('Methods:', Object.keys(comp._methods));
12177
- console.log('Actions:', Object.keys(comp._actions));
12178
- console.log('User tag:', comp._userTag || '(none)');
12179
- console.log('Mounted:', comp.mounted);
12180
- console.log('Element:', comp.element);
12701
+ _cl('State:', comp._state);
12702
+ _cl('Bindings:', comp._bindings.length, '(deps:', deps, ')');
12703
+ _cl('Methods:', _keys(comp._methods));
12704
+ _cl('Actions:', _keys(comp._actions));
12705
+ _cl('User tag:', comp._userTag || '(none)');
12706
+ _cl('Mounted:', comp.mounted);
12707
+ _cl('Element:', comp.element);
12181
12708
  console.groupEnd();
12182
12709
  return comp;
12183
12710
  };
@@ -12200,8 +12727,8 @@
12200
12727
  // Pre-extract all binding expressions
12201
12728
  var precompiled = [];
12202
12729
  function walkExpressions(node) {
12203
- if (!node || _typeof(node) !== 'object') return;
12204
- if (typeof node.c === 'string' && node.c.indexOf('${') >= 0) {
12730
+ if (!_is(node, 'object')) return;
12731
+ if (_is(node.c, 'string') && node.c.indexOf('${') >= 0) {
12205
12732
  var parsed = bw._parseBindings(node.c);
12206
12733
  for (var i = 0; i < parsed.length; i++) {
12207
12734
  try {
@@ -12221,9 +12748,9 @@
12221
12748
  }
12222
12749
  if (node.a) {
12223
12750
  for (var key in node.a) {
12224
- if (Object.prototype.hasOwnProperty.call(node.a, key)) {
12751
+ if (_hop.call(node.a, key)) {
12225
12752
  var v = node.a[key];
12226
- if (typeof v === 'string' && v.indexOf('${') >= 0) {
12753
+ if (_is(v, 'string') && v.indexOf('${') >= 0) {
12227
12754
  var parsed2 = bw._parseBindings(v);
12228
12755
  for (var j = 0; j < parsed2.length; j++) {
12229
12756
  try {
@@ -12244,9 +12771,9 @@
12244
12771
  }
12245
12772
  }
12246
12773
  }
12247
- if (Array.isArray(node.c)) {
12774
+ if (_isA(node.c)) {
12248
12775
  for (var k = 0; k < node.c.length; k++) walkExpressions(node.c[k]);
12249
- } else if (node.c && _typeof(node.c) === 'object' && node.c.t) {
12776
+ } else if (_is(node.c, 'object') && node.c.t) {
12250
12777
  walkExpressions(node.c);
12251
12778
  }
12252
12779
  }
@@ -12257,7 +12784,7 @@
12257
12784
  handle._precompiledBindings = precompiled;
12258
12785
  if (initialState) {
12259
12786
  for (var k in initialState) {
12260
- if (Object.prototype.hasOwnProperty.call(initialState, k)) {
12787
+ if (_hop.call(initialState, k)) {
12261
12788
  handle._state[k] = initialState[k];
12262
12789
  }
12263
12790
  }
@@ -12291,21 +12818,21 @@
12291
12818
  minify = _options$minify === void 0 ? false : _options$minify,
12292
12819
  _options$pretty = options.pretty,
12293
12820
  pretty = _options$pretty === void 0 ? !minify : _options$pretty;
12294
- if (typeof rules === 'string') return rules;
12821
+ if (_is(rules, 'string')) return rules;
12295
12822
  var css = '';
12296
12823
  var indent = pretty ? ' ' : '';
12297
12824
  var newline = pretty ? '\n' : '';
12298
12825
  var space = pretty ? ' ' : '';
12299
- if (Array.isArray(rules)) {
12826
+ if (_isA(rules)) {
12300
12827
  css = rules.map(function (rule) {
12301
12828
  return bw.css(rule, options);
12302
12829
  }).join(newline);
12303
- } else if (_typeof(rules) === 'object') {
12830
+ } else if (_is(rules, 'object')) {
12304
12831
  Object.entries(rules).forEach(function (_ref5) {
12305
12832
  var _ref6 = _slicedToArray(_ref5, 2),
12306
12833
  selector = _ref6[0],
12307
12834
  styles = _ref6[1];
12308
- if (_typeof(styles) === 'object' && !Array.isArray(styles)) {
12835
+ if (_is(styles, 'object')) {
12309
12836
  // Handle @media, @keyframes, @supports — recurse into nested block
12310
12837
  if (selector.charAt(0) === '@') {
12311
12838
  var inner = bw.css(styles, options);
@@ -12351,7 +12878,7 @@
12351
12878
  * @returns {Element} The style element
12352
12879
  * @category CSS & Styling
12353
12880
  * @see bw.css
12354
- * @see bw.loadDefaultStyles
12881
+ * @see bw.loadStyles
12355
12882
  * @example
12356
12883
  * bw.injectCSS('.my-class { color: red; }');
12357
12884
  * bw.injectCSS({ '.card': { padding: '1rem' } }, { id: 'card-styles' });
@@ -12359,7 +12886,7 @@
12359
12886
  bw.injectCSS = function (css) {
12360
12887
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
12361
12888
  if (!bw._isBrowser) {
12362
- console.warn('bw.injectCSS requires a DOM environment');
12889
+ _cw('bw.injectCSS requires a DOM environment');
12363
12890
  return null;
12364
12891
  }
12365
12892
  var _options$id = options.id,
@@ -12377,7 +12904,7 @@
12377
12904
  }
12378
12905
 
12379
12906
  // Convert CSS if needed
12380
- var cssStr = typeof css === 'string' ? css : bw.css(css, options);
12907
+ var cssStr = _is(css, 'string') ? css : bw.css(css, options);
12381
12908
 
12382
12909
  // Set or append CSS
12383
12910
  if (append && styleEl.textContent) {
@@ -12397,230 +12924,19 @@
12397
12924
  * @param {...Object} styles - Style objects to merge (left-to-right)
12398
12925
  * @returns {Object} Merged style object
12399
12926
  * @category CSS & Styling
12400
- * @see bw.u
12401
12927
  * @example
12402
- * var style = bw.s(bw.u.flex, bw.u.gap4, { color: 'red' });
12928
+ * var style = bw.s({ display: 'flex' }, { gap: '1rem' }, { color: 'red' });
12403
12929
  * // => { display: 'flex', gap: '1rem', color: 'red' }
12404
12930
  */
12405
12931
  bw.s = function () {
12406
12932
  var result = {};
12407
12933
  for (var i = 0; i < arguments.length; i++) {
12408
12934
  var arg = arguments[i];
12409
- if (arg && _typeof(arg) === 'object') Object.assign(result, arg);
12935
+ if (_is(arg, 'object')) Object.assign(result, arg);
12410
12936
  }
12411
12937
  return result;
12412
12938
  };
12413
12939
 
12414
- /**
12415
- * Pre-built CSS utility objects (like Tailwind utilities, but in JS).
12416
- *
12417
- * Compose with `bw.s()` to build inline styles without writing raw CSS strings.
12418
- * Includes flex, padding, margin, typography, color, border, and transition utilities.
12419
- *
12420
- * @category CSS & Styling
12421
- * @see bw.s
12422
- * @example
12423
- * { t: 'div', a: { style: bw.s(bw.u.flex, bw.u.gap4, bw.u.p4) },
12424
- * c: 'Flexbox with 1rem gap and padding' }
12425
- */
12426
- bw.u = {
12427
- // Display
12428
- flex: {
12429
- display: 'flex'
12430
- },
12431
- flexCol: {
12432
- display: 'flex',
12433
- flexDirection: 'column'
12434
- },
12435
- flexRow: {
12436
- display: 'flex',
12437
- flexDirection: 'row'
12438
- },
12439
- flexWrap: {
12440
- display: 'flex',
12441
- flexWrap: 'wrap'
12442
- },
12443
- block: {
12444
- display: 'block'
12445
- },
12446
- inline: {
12447
- display: 'inline'
12448
- },
12449
- hidden: {
12450
- display: 'none'
12451
- },
12452
- // Flex alignment
12453
- justifyCenter: {
12454
- justifyContent: 'center'
12455
- },
12456
- justifyBetween: {
12457
- justifyContent: 'space-between'
12458
- },
12459
- justifyEnd: {
12460
- justifyContent: 'flex-end'
12461
- },
12462
- alignCenter: {
12463
- alignItems: 'center'
12464
- },
12465
- alignStart: {
12466
- alignItems: 'flex-start'
12467
- },
12468
- alignEnd: {
12469
- alignItems: 'flex-end'
12470
- },
12471
- // Gap (0.25rem increments)
12472
- gap1: {
12473
- gap: '0.25rem'
12474
- },
12475
- gap2: {
12476
- gap: '0.5rem'
12477
- },
12478
- gap3: {
12479
- gap: '0.75rem'
12480
- },
12481
- gap4: {
12482
- gap: '1rem'
12483
- },
12484
- gap6: {
12485
- gap: '1.5rem'
12486
- },
12487
- gap8: {
12488
- gap: '2rem'
12489
- },
12490
- // Padding
12491
- p0: {
12492
- padding: '0'
12493
- },
12494
- p1: {
12495
- padding: '0.25rem'
12496
- },
12497
- p2: {
12498
- padding: '0.5rem'
12499
- },
12500
- p3: {
12501
- padding: '0.75rem'
12502
- },
12503
- p4: {
12504
- padding: '1rem'
12505
- },
12506
- p6: {
12507
- padding: '1.5rem'
12508
- },
12509
- p8: {
12510
- padding: '2rem'
12511
- },
12512
- px4: {
12513
- paddingLeft: '1rem',
12514
- paddingRight: '1rem'
12515
- },
12516
- py2: {
12517
- paddingTop: '0.5rem',
12518
- paddingBottom: '0.5rem'
12519
- },
12520
- py4: {
12521
- paddingTop: '1rem',
12522
- paddingBottom: '1rem'
12523
- },
12524
- // Margin (same scale)
12525
- m0: {
12526
- margin: '0'
12527
- },
12528
- m4: {
12529
- margin: '1rem'
12530
- },
12531
- mt2: {
12532
- marginTop: '0.5rem'
12533
- },
12534
- mt4: {
12535
- marginTop: '1rem'
12536
- },
12537
- mb2: {
12538
- marginBottom: '0.5rem'
12539
- },
12540
- mb4: {
12541
- marginBottom: '1rem'
12542
- },
12543
- mx_auto: {
12544
- marginLeft: 'auto',
12545
- marginRight: 'auto'
12546
- },
12547
- // Typography
12548
- textSm: {
12549
- fontSize: '0.875rem'
12550
- },
12551
- textBase: {
12552
- fontSize: '1rem'
12553
- },
12554
- textLg: {
12555
- fontSize: '1.125rem'
12556
- },
12557
- textXl: {
12558
- fontSize: '1.25rem'
12559
- },
12560
- text2xl: {
12561
- fontSize: '1.5rem'
12562
- },
12563
- text3xl: {
12564
- fontSize: '1.875rem'
12565
- },
12566
- bold: {
12567
- fontWeight: '700'
12568
- },
12569
- semibold: {
12570
- fontWeight: '600'
12571
- },
12572
- italic: {
12573
- fontStyle: 'italic'
12574
- },
12575
- textCenter: {
12576
- textAlign: 'center'
12577
- },
12578
- textRight: {
12579
- textAlign: 'right'
12580
- },
12581
- // Colors (from design tokens)
12582
- bgWhite: {
12583
- background: '#ffffff'
12584
- },
12585
- bgTeal: {
12586
- background: '#006666',
12587
- color: '#ffffff'
12588
- },
12589
- textWhite: {
12590
- color: '#ffffff'
12591
- },
12592
- textTeal: {
12593
- color: '#006666'
12594
- },
12595
- textMuted: {
12596
- color: '#888'
12597
- },
12598
- // Borders
12599
- rounded: {
12600
- borderRadius: '0.375rem'
12601
- },
12602
- roundedLg: {
12603
- borderRadius: '0.5rem'
12604
- },
12605
- roundedFull: {
12606
- borderRadius: '9999px'
12607
- },
12608
- border: {
12609
- border: '1px solid #d8d8d8'
12610
- },
12611
- // Sizing
12612
- wFull: {
12613
- width: '100%'
12614
- },
12615
- hFull: {
12616
- height: '100%'
12617
- },
12618
- // Transitions
12619
- transition: {
12620
- transition: 'all 0.2s ease'
12621
- }
12622
- };
12623
-
12624
12940
  /**
12625
12941
  * Generate responsive CSS with media query breakpoints.
12626
12942
  *
@@ -12651,7 +12967,7 @@
12651
12967
  xl: '1200px'
12652
12968
  };
12653
12969
  var parts = [];
12654
- Object.keys(breakpoints).forEach(function (key) {
12970
+ _keys(breakpoints).forEach(function (key) {
12655
12971
  var rules = {};
12656
12972
  if (key === 'base') {
12657
12973
  rules[selector] = breakpoints[key];
@@ -12723,18 +13039,18 @@
12723
13039
  if (!selector) return [];
12724
13040
 
12725
13041
  // Already an array
12726
- if (Array.isArray(selector)) return selector;
13042
+ if (_isA(selector)) return selector;
12727
13043
 
12728
13044
  // Single element
12729
13045
  if (selector.nodeType) return [selector];
12730
13046
 
12731
13047
  // NodeList or HTMLCollection
12732
- if (selector.length !== undefined && typeof selector !== 'string') {
13048
+ if (selector.length !== undefined && !_is(selector, 'string')) {
12733
13049
  return Array.from(selector);
12734
13050
  }
12735
13051
 
12736
13052
  // CSS selector string
12737
- if (typeof selector === 'string') {
13053
+ if (_is(selector, 'string')) {
12738
13054
  return Array.from(document.querySelectorAll(selector));
12739
13055
  }
12740
13056
  return [];
@@ -12746,111 +13062,48 @@
12746
13062
  };
12747
13063
  }
12748
13064
 
13065
+ // =========================================================================
13066
+ // v2.0.18 Clean Styles API — makeStyles / applyStyles / loadStyles / etc.
13067
+ // =========================================================================
13068
+
12749
13069
  /**
12750
- * Load the built-in Bootstrap-inspired default stylesheet.
12751
- *
12752
- * Injects bitwrench's batteries-included CSS (buttons, cards, grids, forms,
12753
- * alerts, badges, nav, tabs, etc.) into the document head. Call once at app startup.
12754
- * Returns null in Node.js (no DOM).
12755
- *
12756
- * @param {Object} [options] - Style loading options
12757
- * @param {boolean} [options.minify=true] - Minify the CSS output
12758
- * @returns {Element|null} Style element if in browser, null in Node.js
12759
- * @category CSS & Styling
12760
- * @see bw.setTheme
12761
- * @see bw.applyTheme
12762
- * @see bw.toggleTheme
12763
- * @example
12764
- * bw.loadDefaultStyles(); // inject all default CSS
13070
+ * Convert a scope selector to a <style> element id.
13071
+ * @private
13072
+ * @param {string} [scope] - Scope selector (e.g. '#my-dashboard', '.preview')
13073
+ * @returns {string} Style element id (e.g. 'bw_style_my_dashboard')
12765
13074
  */
12766
- bw.loadDefaultStyles = function () {
12767
- var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
12768
- var _options$minify2 = options.minify,
12769
- minify = _options$minify2 === void 0 ? true : _options$minify2,
12770
- palette = options.palette;
12771
-
12772
- // 1. Inject structural CSS (layout, sizing — never changes with theme)
12773
- if (bw._isBrowser) {
12774
- var structuralCSS = bw.css(getStructuralStyles());
12775
- bw.injectCSS(structuralCSS, {
12776
- id: 'bw_structural',
12777
- append: false,
12778
- minify: minify
12779
- });
12780
- }
12781
-
12782
- // 2. Inject cosmetic CSS via generateTheme (colors, shadows, radii)
12783
- var paletteConfig = Object.assign({}, DEFAULT_PALETTE_CONFIG, palette || {});
12784
- var result = bw.generateTheme('', Object.assign({}, paletteConfig, {
12785
- inject: true
12786
- }));
12787
- return result;
12788
- };
13075
+ function _scopeToStyleId(scope) {
13076
+ if (!scope || scope === '' || scope === 'global') return 'bw_style_global';
13077
+ if (scope === 'reset') return 'bw_style_reset';
13078
+ // Strip leading # or . and convert - to _
13079
+ var clean = scope.replace(/^[#.]/, '').replace(/-/g, '_');
13080
+ return 'bw_style_' + clean;
13081
+ }
12789
13082
 
12790
13083
  /**
12791
- * Generate a complete, scoped theme from seed colors.
12792
- *
12793
- * Produces CSS for all themed components (buttons, alerts, badges, cards,
12794
- * forms, nav, tables, tabs, list groups, pagination, progress, hero, utilities)
12795
- * scoped under `.name` class. Multiple themes can coexist in the stylesheet.
12796
- * Swap themes by changing the class on a container element.
12797
- *
12798
- * @param {string} name - CSS scope class (e.g. 'ocean'). Empty string = unscoped global.
12799
- * @param {Object} config - Theme configuration
12800
- * @param {string} config.primary - Primary brand color hex
12801
- * @param {string} config.secondary - Secondary color hex
12802
- * @param {string} [config.tertiary] - Tertiary/accent color hex (defaults to primary)
12803
- * @param {string} [config.success='#198754'] - Success color hex
12804
- * @param {string} [config.danger='#dc3545'] - Danger color hex
12805
- * @param {string} [config.warning='#ffc107'] - Warning color hex
12806
- * @param {string} [config.info='#0dcaf0'] - Info color hex
12807
- * @param {string} [config.light='#f8f9fa'] - Light color hex
12808
- * @param {string} [config.dark='#212529'] - Dark color hex
12809
- * @param {string} [config.background] - Page background hex (default: '#ffffff' light, derived dark)
12810
- * @param {string} [config.surface] - Surface/card background hex (default: '#f8f9fa' light, derived dark)
13084
+ * Generate a complete styles object from seed colors and layout config.
13085
+ * Pure function — no DOM, no state, no side effects.
13086
+ *
13087
+ * All parameters are optional. Defaults to the bitwrench default palette.
13088
+ *
13089
+ * @param {Object} [config] - Style configuration
13090
+ * @param {string} [config.primary='#006666'] - Primary brand color hex
13091
+ * @param {string} [config.secondary='#6c757d'] - Secondary color hex
13092
+ * @param {string} [config.tertiary] - Tertiary color hex (defaults to primary)
12811
13093
  * @param {string} [config.spacing='normal'] - 'compact' | 'normal' | 'spacious'
12812
13094
  * @param {string} [config.radius='md'] - 'none' | 'sm' | 'md' | 'lg' | 'pill'
12813
- * @param {number} [config.fontSize=1.0] - Base font size scale factor
12814
- * @param {string|number} [config.typeRatio='normal'] - 'tight' | 'normal' | 'relaxed' | 'dramatic' or a number
12815
- * @param {string} [config.elevation='md'] - 'flat' | 'sm' | 'md' | 'lg'
12816
- * @param {string} [config.motion='standard'] - 'reduced' | 'standard' | 'expressive'
12817
- * @param {number} [config.harmonize=0.20] - 0-1, semantic color hue shift toward primary
12818
- * @param {boolean} [config.inject=true] - Inject into DOM (browser only)
12819
- * @returns {Object} { css, palette, name, isLightPrimary, alternate: { css, palette } }
13095
+ * @returns {Object} { css, alternateCss, rules, alternateRules, palette, alternatePalette, isLightPrimary }
12820
13096
  * @category CSS & Styling
12821
- * @see bw.applyTheme
12822
- * @see bw.toggleTheme
12823
- * @see bw.loadDefaultStyles
13097
+ * @see bw.applyStyles
13098
+ * @see bw.loadStyles
12824
13099
  * @example
12825
- * // Generate and inject an ocean theme (primary + alternate)
12826
- * var theme = bw.generateTheme('ocean', {
12827
- * primary: '#0077b6',
12828
- * secondary: '#90e0ef',
12829
- * tertiary: '#00b4d8'
12830
- * });
12831
- *
12832
- * // Apply to a container
12833
- * document.getElementById('app').classList.add('ocean');
12834
- *
12835
- * // Toggle to alternate palette
12836
- * bw.toggleTheme();
12837
- *
12838
- * // Generate CSS for static export (Node.js)
12839
- * var result = bw.generateTheme('sunset', {
12840
- * primary: '#e76f51',
12841
- * secondary: '#264653',
12842
- * inject: false
12843
- * });
12844
- * fs.writeFileSync('sunset.css', result.css + result.alternate.css);
13100
+ * var styles = bw.makeStyles({ primary: '#4f46e5', secondary: '#d97706' });
13101
+ * console.log(styles.palette.primary.base); // '#4f46e5'
13102
+ * // styles.css contains all themed CSS — nothing injected
12845
13103
  */
12846
- bw.generateTheme = function (name, config) {
12847
- if (!config || !config.primary || !config.secondary) {
12848
- throw new Error('bw.generateTheme requires config.primary and config.secondary');
12849
- }
12850
-
12851
- // Merge with defaults; if user didn't supply tertiary, default to their primary
12852
- var fullConfig = Object.assign({}, DEFAULT_PALETTE_CONFIG, config);
12853
- if (!config.tertiary) fullConfig.tertiary = fullConfig.primary;
13104
+ bw.makeStyles = function (config) {
13105
+ var fullConfig = Object.assign({}, DEFAULT_PALETTE_CONFIG, config || {});
13106
+ if (config && !config.tertiary) fullConfig.tertiary = fullConfig.primary;
12854
13107
 
12855
13108
  // Derive primary palette
12856
13109
  var palette = derivePalette(fullConfig);
@@ -12858,136 +13111,211 @@
12858
13111
  // Resolve layout
12859
13112
  var layout = resolveLayout(fullConfig);
12860
13113
 
12861
- // Generate primary themed CSS rules
12862
- var themedRules = generateThemedCSS(name, palette, layout);
13114
+ // Generate primary themed CSS rules (unscoped)
13115
+ var themedRules = generateThemedCSS('', palette, layout);
12863
13116
  var cssStr = bw.css(themedRules);
12864
13117
 
12865
13118
  // Derive alternate palette (luminance-inverted)
12866
13119
  var altConfig = deriveAlternateConfig(fullConfig);
12867
13120
  var altPalette = derivePalette(altConfig);
12868
13121
 
12869
- // Generate alternate CSS scoped under .bw_theme_alt
12870
- var altRules = generateAlternateCSS(name, altPalette, layout);
12871
- var altCssStr = bw.css(altRules);
13122
+ // Generate alternate CSS rules WITHOUT .bw_theme_alt prefix (raw rules)
13123
+ // applyStyles() wraps them appropriately based on scope
13124
+ var altRawRules = generateThemedCSS('', altPalette, layout);
13125
+
13126
+ // Add body-level surface overrides for the alternate palette.
13127
+ // When .bw_theme_alt is on <html>, ".bw_theme_alt body" correctly matches.
13128
+ altRawRules['body'] = {
13129
+ 'color': altPalette.dark.base,
13130
+ 'background-color': altPalette.surface || altPalette.light.base
13131
+ };
13132
+ var altCssStr = bw.css(altRawRules);
12872
13133
 
12873
13134
  // Determine if primary is light-flavored
12874
13135
  var lightPrimary = isLightPalette(fullConfig);
13136
+ return {
13137
+ css: cssStr,
13138
+ alternateCss: altCssStr,
13139
+ rules: themedRules,
13140
+ alternateRules: altRawRules,
13141
+ palette: palette,
13142
+ alternatePalette: altPalette,
13143
+ isLightPrimary: lightPrimary
13144
+ };
13145
+ };
12875
13146
 
12876
- // Inject both CSS sets into DOM if requested
12877
- var shouldInject = config.inject !== false;
12878
- if (shouldInject && bw._isBrowser) {
12879
- var safeName = name ? name.replace(/-/g, '_') : '';
12880
- var styleId = safeName ? 'bw_theme_' + safeName : 'bw_theme_default';
12881
- var altStyleId = safeName ? 'bw_theme_' + safeName + '_alt' : 'bw_theme_default_alt';
12882
- bw.injectCSS(cssStr, {
12883
- id: styleId,
12884
- append: false
12885
- });
12886
- bw.injectCSS(altCssStr, {
12887
- id: altStyleId,
12888
- append: false
12889
- });
12890
- bw._activeThemeStyleIds = [styleId, altStyleId];
13147
+ /**
13148
+ * Inject styles into the DOM with optional scoping.
13149
+ *
13150
+ * Takes a styles object from `makeStyles()` and creates a single `<style>`
13151
+ * element in `<head>`. If a scope selector is provided, all CSS rules are
13152
+ * wrapped under that selector. Alternate CSS is wrapped under `.bw_theme_alt`.
13153
+ *
13154
+ * @param {Object} styles - Result of `bw.makeStyles()`
13155
+ * @param {string} [scope] - Scope selector (e.g. '#my-dashboard', '.preview'). Omit for global.
13156
+ * @returns {Element|null} The `<style>` element, or null in Node.js
13157
+ * @category CSS & Styling
13158
+ * @see bw.makeStyles
13159
+ * @see bw.loadStyles
13160
+ * @see bw.clearStyles
13161
+ * @example
13162
+ * var styles = bw.makeStyles({ primary: '#4f46e5' });
13163
+ * bw.applyStyles(styles); // global
13164
+ * bw.applyStyles(styles, '#my-dashboard'); // scoped
13165
+ */
13166
+ bw.applyStyles = function (styles, scope) {
13167
+ if (!bw._isBrowser) return null;
13168
+ if (!styles || !styles.rules) {
13169
+ _cw('bw.applyStyles: invalid styles object');
13170
+ return null;
12891
13171
  }
13172
+ var styleId = _scopeToStyleId(scope);
12892
13173
 
12893
- // Update bw.u color entries to reflect the palette
12894
- if (!name) {
12895
- bw.u.bgTeal = {
12896
- background: palette.primary.base,
12897
- color: palette.primary.textOn
12898
- };
12899
- bw.u.textTeal = {
12900
- color: palette.primary.base
12901
- };
12902
- bw.u.bgWhite = {
12903
- background: '#ffffff'
12904
- };
12905
- bw.u.textWhite = {
12906
- color: '#ffffff'
12907
- };
13174
+ // Scope the primary rules if a scope is provided
13175
+ var primaryRules = styles.rules;
13176
+ if (scope) {
13177
+ primaryRules = scopeRulesUnder(primaryRules, scope);
12908
13178
  }
12909
13179
 
12910
- // Store active theme state
12911
- var result = {
12912
- css: cssStr,
12913
- palette: palette,
12914
- name: name,
12915
- isLightPrimary: lightPrimary,
12916
- alternate: {
12917
- css: altCssStr,
12918
- palette: altPalette
13180
+ // Wrap alternate rules with .bw_theme_alt
13181
+ var altRules = styles.alternateRules;
13182
+ if (altRules) {
13183
+ if (scope) {
13184
+ // Scoped compound: #scope.bw_theme_alt .bw_card
13185
+ altRules = scopeRulesUnder(altRules, scope + '.bw_theme_alt');
13186
+ } else {
13187
+ // Global: .bw_theme_alt .bw_card
13188
+ altRules = scopeRulesUnder(altRules, '.bw_theme_alt');
12919
13189
  }
12920
- };
12921
- bw._activeTheme = result;
12922
- bw._activeThemeMode = 'primary';
12923
- return result;
13190
+ }
13191
+
13192
+ // Combine primary + alternate into one CSS string
13193
+ var combined = bw.css(primaryRules);
13194
+ if (altRules) {
13195
+ combined += '\n' + bw.css(altRules);
13196
+ }
13197
+ return bw.injectCSS(combined, {
13198
+ id: styleId,
13199
+ append: false
13200
+ });
12924
13201
  };
12925
13202
 
12926
13203
  /**
12927
- * Apply a theme mode. Switches between primary and alternate palettes
12928
- * by adding/removing the `bw_theme_alt` class on `<html>`.
13204
+ * Generate and apply styles in one call. Convenience wrapper.
13205
+ *
13206
+ * Equivalent to: `bw.applyStyles(bw.makeStyles(config), scope)`
12929
13207
  *
12930
- * @param {string} mode - 'primary' | 'alternate' | 'light' | 'dark'
12931
- * @returns {string} Active mode: 'primary' or 'alternate'
13208
+ * @param {Object} [config] - Style configuration (same as `makeStyles`)
13209
+ * @param {string} [scope] - Scope selector (same as `applyStyles`)
13210
+ * @returns {Element|null} The `<style>` element, or null in Node.js
12932
13211
  * @category CSS & Styling
12933
- * @see bw.generateTheme
12934
- * @see bw.toggleTheme
13212
+ * @see bw.makeStyles
13213
+ * @see bw.applyStyles
12935
13214
  * @example
12936
- * bw.applyTheme('alternate'); // switch to alternate palette
12937
- * bw.applyTheme('dark'); // switch to whichever palette is darker
12938
- * bw.applyTheme('primary'); // switch back to primary palette
12939
- */
12940
- bw.applyTheme = function (mode) {
12941
- if (!bw._isBrowser) return mode || 'primary';
12942
- var root = document.documentElement;
12943
- var isLight = bw._activeTheme ? bw._activeTheme.isLightPrimary : true;
12944
- var wantAlt;
12945
- if (mode === 'primary') wantAlt = false;else if (mode === 'alternate') wantAlt = true;else if (mode === 'light') wantAlt = !isLight;else if (mode === 'dark') wantAlt = isLight;else wantAlt = false;
12946
- if (wantAlt) {
12947
- root.classList.add('bw_theme_alt');
12948
- } else {
12949
- root.classList.remove('bw_theme_alt');
13215
+ * bw.loadStyles(); // defaults, global
13216
+ * bw.loadStyles({ primary: '#4f46e5' }); // custom, global
13217
+ * bw.loadStyles({ primary: '#4f46e5' }, '#my-dashboard'); // custom, scoped
13218
+ */
13219
+ bw.loadStyles = function (config, scope) {
13220
+ // Also inject structural CSS first (only once)
13221
+ if (bw._isBrowser) {
13222
+ var existing = document.getElementById('bw_structural');
13223
+ if (!existing) {
13224
+ var structuralCSS = bw.css(getStructuralStyles());
13225
+ bw.injectCSS(structuralCSS, {
13226
+ id: 'bw_structural',
13227
+ append: false
13228
+ });
13229
+ }
12950
13230
  }
12951
- bw._activeThemeMode = wantAlt ? 'alternate' : 'primary';
12952
- return bw._activeThemeMode;
13231
+ return bw.applyStyles(bw.makeStyles(config), scope);
13232
+ };
13233
+
13234
+ /**
13235
+ * Inject the CSS reset (box-sizing, html/body font, reduced-motion).
13236
+ * Idempotent — if already injected, returns the existing `<style>` element.
13237
+ *
13238
+ * @returns {Element|null} The `<style>` element, or null in Node.js
13239
+ * @category CSS & Styling
13240
+ * @see bw.loadStyles
13241
+ * @see bw.clearStyles
13242
+ * @example
13243
+ * bw.loadReset(); // inject once, safe to call multiple times
13244
+ */
13245
+ bw.loadReset = function () {
13246
+ if (!bw._isBrowser) return null;
13247
+ var existing = document.getElementById('bw_style_reset');
13248
+ if (existing) return existing;
13249
+ return bw.injectCSS(bw.css(getResetStyles()), {
13250
+ id: 'bw_style_reset',
13251
+ append: false
13252
+ });
12953
13253
  };
12954
13254
 
12955
13255
  /**
12956
- * Toggle between primary and alternate theme palettes.
13256
+ * Toggle between primary and alternate palettes.
13257
+ *
13258
+ * Adds/removes the `bw_theme_alt` class on the scoping element.
13259
+ * Without a scope, toggles on `<html>` (global).
13260
+ * With a scope, toggles on the first matching element.
12957
13261
  *
13262
+ * @param {string} [scope] - Scope selector (e.g. '#my-dashboard'). Omit for global.
12958
13263
  * @returns {string} Active mode after toggle: 'primary' or 'alternate'
12959
13264
  * @category CSS & Styling
12960
- * @see bw.applyTheme
12961
- * @see bw.generateTheme
13265
+ * @see bw.applyStyles
13266
+ * @see bw.clearStyles
12962
13267
  * @example
12963
- * bw.toggleTheme(); // flip between primary and alternate
13268
+ * bw.toggleStyles(); // global toggle on <html>
13269
+ * bw.toggleStyles('#my-dashboard'); // scoped toggle
12964
13270
  */
12965
- bw.toggleTheme = function () {
12966
- var current = bw._activeThemeMode || 'primary';
12967
- return bw.applyTheme(current === 'primary' ? 'alternate' : 'primary');
13271
+ bw.toggleStyles = function (scope) {
13272
+ if (!bw._isBrowser) return 'primary';
13273
+ var target;
13274
+ if (scope) {
13275
+ var els = bw.$(scope);
13276
+ target = els[0];
13277
+ } else {
13278
+ target = document.documentElement;
13279
+ }
13280
+ if (!target) return 'primary';
13281
+ var hasAlt = target.classList.contains('bw_theme_alt');
13282
+ if (hasAlt) {
13283
+ target.classList.remove('bw_theme_alt');
13284
+ return 'primary';
13285
+ } else {
13286
+ target.classList.add('bw_theme_alt');
13287
+ return 'alternate';
13288
+ }
12968
13289
  };
12969
13290
 
12970
13291
  /**
12971
- * Remove the currently active theme's injected style elements from the DOM.
12972
- * Use this before generating a new theme with a different name to prevent
12973
- * stale CSS accumulation.
13292
+ * Remove injected styles for a given scope.
12974
13293
  *
13294
+ * Finds the `<style>` element by id and removes it. Also removes
13295
+ * the `bw_theme_alt` class from the relevant element.
13296
+ *
13297
+ * @param {string} [scope] - Scope selector. Omit to remove global styles.
12975
13298
  * @category CSS & Styling
12976
- * @see bw.generateTheme
13299
+ * @see bw.applyStyles
13300
+ * @see bw.loadStyles
12977
13301
  * @example
12978
- * bw.clearTheme(); // remove current theme styles
12979
- * bw.generateTheme('sunset', conf); // inject fresh theme
12980
- */
12981
- bw.clearTheme = function () {
12982
- if (bw._activeThemeStyleIds && bw._isBrowser) {
12983
- bw._activeThemeStyleIds.forEach(function (id) {
12984
- var el = document.getElementById(id);
12985
- if (el) el.remove();
12986
- });
12987
- bw._activeThemeStyleIds = null;
13302
+ * bw.clearStyles(); // remove global styles
13303
+ * bw.clearStyles('#my-dashboard'); // remove scoped styles
13304
+ * bw.clearStyles('reset'); // remove the CSS reset
13305
+ */
13306
+ bw.clearStyles = function (scope) {
13307
+ if (!bw._isBrowser) return;
13308
+ var styleId = _scopeToStyleId(scope);
13309
+ var el = document.getElementById(styleId);
13310
+ if (el) el.remove();
13311
+
13312
+ // Also remove bw_theme_alt from the relevant element
13313
+ if (scope && scope !== 'reset' && scope !== 'global') {
13314
+ var targets = bw.$(scope);
13315
+ if (targets[0]) targets[0].classList.remove('bw_theme_alt');
13316
+ } else if (!scope || scope === 'global') {
13317
+ document.documentElement.classList.remove('bw_theme_alt');
12988
13318
  }
12989
- bw._activeTheme = null;
12990
- bw._activeThemeMode = 'primary';
12991
13319
  };
12992
13320
 
12993
13321
  // Expose color utility functions on bw namespace
@@ -13209,10 +13537,15 @@
13209
13537
  * @param {Object} config - Table configuration
13210
13538
  * @param {Array<Object>} config.data - Array of row objects to display
13211
13539
  * @param {Array<Object>} [config.columns] - Column definitions with key, label, render
13212
- * @param {string} [config.className='table'] - CSS class for table element
13540
+ * @param {string} [config.className=''] - Additional CSS classes for table element
13213
13541
  * @param {boolean} [config.sortable=true] - Enable click-to-sort headers
13214
13542
  * @param {Function} [config.onSort] - Sort callback (column, direction)
13215
- * @returns {Object} TACO object for table
13543
+ * @param {boolean} [config.selectable=false] - Enable row selection on click
13544
+ * @param {Function} [config.onRowClick] - Row click callback (row, index, event)
13545
+ * @param {number} [config.pageSize] - Rows per page (enables pagination when set)
13546
+ * @param {number} [config.currentPage=1] - Current page number (1-based)
13547
+ * @param {Function} [config.onPageChange] - Page change callback (newPage)
13548
+ * @returns {Object} TACO object for table (with optional pagination controls)
13216
13549
  * @category Component Builders
13217
13550
  * @see bw.makeDataTable
13218
13551
  * @example
@@ -13224,7 +13557,12 @@
13224
13557
  * columns: [
13225
13558
  * { key: 'name', label: 'Name' },
13226
13559
  * { key: 'age', label: 'Age' }
13227
- * ]
13560
+ * ],
13561
+ * selectable: true,
13562
+ * onRowClick: function(row, i) { console.log('clicked', row.name); },
13563
+ * pageSize: 10,
13564
+ * currentPage: 1,
13565
+ * onPageChange: function(page) { console.log('page', page); }
13228
13566
  * });
13229
13567
  */
13230
13568
  bw.makeTable = function (config) {
@@ -13242,17 +13580,25 @@
13242
13580
  onSort = config.onSort,
13243
13581
  sortColumn = config.sortColumn,
13244
13582
  _config$sortDirection = config.sortDirection,
13245
- sortDirection = _config$sortDirection === void 0 ? 'asc' : _config$sortDirection;
13246
-
13247
- // Build class list: always include bw_table, add striped/hover, append user className
13583
+ sortDirection = _config$sortDirection === void 0 ? 'asc' : _config$sortDirection,
13584
+ _config$selectable = config.selectable,
13585
+ selectable = _config$selectable === void 0 ? false : _config$selectable,
13586
+ onRowClick = config.onRowClick,
13587
+ pageSize = config.pageSize,
13588
+ _config$currentPage = config.currentPage,
13589
+ currentPage = _config$currentPage === void 0 ? 1 : _config$currentPage,
13590
+ onPageChange = config.onPageChange;
13591
+
13592
+ // Build class list: always include bw_table, add striped/hover/selectable, append user className
13248
13593
  var cls = 'bw_table';
13249
13594
  if (striped) cls += ' bw_table_striped';
13250
- if (hover) cls += ' bw_table_hover';
13595
+ if (hover || selectable) cls += ' bw_table_hover';
13596
+ if (selectable) cls += ' bw_table_selectable';
13251
13597
  if (className) cls += ' ' + className;
13252
13598
  cls = cls.trim();
13253
13599
 
13254
13600
  // Auto-detect columns if not provided
13255
- var cols = columns || (data.length > 0 ? Object.keys(data[0]).map(function (key) {
13601
+ var cols = columns || (data.length > 0 ? _keys(data[0]).map(function (key) {
13256
13602
  return {
13257
13603
  key: key,
13258
13604
  label: key
@@ -13271,7 +13617,7 @@
13271
13617
  var bVal = b[currentSortColumn];
13272
13618
 
13273
13619
  // Handle different types
13274
- if (typeof aVal === 'number' && typeof bVal === 'number') {
13620
+ if (_is(aVal, 'number') && _is(bVal, 'number')) {
13275
13621
  return currentSortDirection === 'asc' ? aVal - bVal : bVal - aVal;
13276
13622
  }
13277
13623
 
@@ -13286,6 +13632,15 @@
13286
13632
  });
13287
13633
  }
13288
13634
 
13635
+ // Pagination
13636
+ var totalRows = sortedData.length;
13637
+ var totalPages = pageSize ? Math.max(1, Math.ceil(totalRows / pageSize)) : 1;
13638
+ var page = Math.max(1, Math.min(currentPage, totalPages));
13639
+ if (pageSize) {
13640
+ var start = (page - 1) * pageSize;
13641
+ sortedData = sortedData.slice(start, start + pageSize);
13642
+ }
13643
+
13289
13644
  // Create sort handler
13290
13645
  var handleSort = function handleSort(column) {
13291
13646
  if (!sortable) return;
@@ -13331,12 +13686,28 @@
13331
13686
  }
13332
13687
  };
13333
13688
 
13334
- // Build table body
13689
+ // Build table body with selectable/onRowClick support
13335
13690
  var tbody = {
13336
13691
  t: 'tbody',
13337
- c: sortedData.map(function (row) {
13692
+ c: sortedData.map(function (row, idx) {
13693
+ var globalIdx = pageSize ? (page - 1) * pageSize + idx : idx;
13694
+ var rowAttrs = {};
13695
+ if (selectable || onRowClick) {
13696
+ rowAttrs.style = 'cursor:pointer;';
13697
+ rowAttrs.onclick = function (e) {
13698
+ if (selectable) {
13699
+ // Toggle selected class on this row
13700
+ var tr = e.currentTarget;
13701
+ tr.classList.toggle('bw_table_row_selected');
13702
+ }
13703
+ if (onRowClick) {
13704
+ onRowClick(row, globalIdx, e);
13705
+ }
13706
+ };
13707
+ }
13338
13708
  return {
13339
13709
  t: 'tr',
13710
+ a: rowAttrs,
13340
13711
  c: cols.map(function (col) {
13341
13712
  return {
13342
13713
  t: 'td',
@@ -13346,13 +13717,65 @@
13346
13717
  };
13347
13718
  })
13348
13719
  };
13349
- return {
13720
+ var table = {
13350
13721
  t: 'table',
13351
13722
  a: {
13352
13723
  "class": cls
13353
13724
  },
13354
13725
  c: [thead, tbody]
13355
13726
  };
13727
+
13728
+ // If no pagination, return table directly
13729
+ if (!pageSize) return table;
13730
+
13731
+ // Build pagination controls
13732
+ var pageButtons = [];
13733
+ // Previous button
13734
+ pageButtons.push({
13735
+ t: 'button',
13736
+ a: {
13737
+ "class": 'bw_btn bw_btn_sm',
13738
+ disabled: page <= 1 ? 'disabled' : undefined,
13739
+ onclick: page > 1 && onPageChange ? function () {
13740
+ onPageChange(page - 1);
13741
+ } : undefined
13742
+ },
13743
+ c: 'Prev'
13744
+ });
13745
+ // Page info
13746
+ pageButtons.push({
13747
+ t: 'span',
13748
+ a: {
13749
+ style: 'margin:0 0.5rem;font-size:0.875rem;'
13750
+ },
13751
+ c: 'Page ' + page + ' of ' + totalPages
13752
+ });
13753
+ // Next button
13754
+ pageButtons.push({
13755
+ t: 'button',
13756
+ a: {
13757
+ "class": 'bw_btn bw_btn_sm',
13758
+ disabled: page >= totalPages ? 'disabled' : undefined,
13759
+ onclick: page < totalPages && onPageChange ? function () {
13760
+ onPageChange(page + 1);
13761
+ } : undefined
13762
+ },
13763
+ c: 'Next'
13764
+ });
13765
+ return {
13766
+ t: 'div',
13767
+ a: {
13768
+ "class": 'bw_table_paginated'
13769
+ },
13770
+ c: [table, {
13771
+ t: 'div',
13772
+ a: {
13773
+ "class": 'bw_table_pagination',
13774
+ style: 'display:flex;align-items:center;justify-content:flex-end;padding:0.5rem 0;gap:0.25rem;'
13775
+ },
13776
+ c: pageButtons
13777
+ }]
13778
+ };
13356
13779
  };
13357
13780
 
13358
13781
  /**
@@ -13395,7 +13818,7 @@
13395
13818
  headerRow = _config$headerRow === void 0 ? true : _config$headerRow,
13396
13819
  columns = config.columns,
13397
13820
  rest = _objectWithoutProperties(config, _excluded);
13398
- if (!Array.isArray(data) || data.length === 0) {
13821
+ if (!_isA(data) || data.length === 0) {
13399
13822
  return bw.makeTable(_objectSpread2({
13400
13823
  data: [],
13401
13824
  columns: columns || []
@@ -13492,7 +13915,7 @@
13492
13915
  showLabels = _config$showLabels === void 0 ? true : _config$showLabels,
13493
13916
  _config$className2 = config.className,
13494
13917
  className = _config$className2 === void 0 ? '' : _config$className2;
13495
- if (!Array.isArray(data) || data.length === 0) {
13918
+ if (!_isA(data) || data.length === 0) {
13496
13919
  return {
13497
13920
  t: 'div',
13498
13921
  a: {
@@ -13675,7 +14098,7 @@
13675
14098
  bw.render = function (element, position, taco) {
13676
14099
  var _taco$o4, _taco$o5, _taco$o6;
13677
14100
  // Get target element
13678
- var targetEl = typeof element === 'string' ? document.querySelector(element) : element;
14101
+ var targetEl = _is(element, 'string') ? document.querySelector(element) : element;
13679
14102
  if (!targetEl) {
13680
14103
  return {
13681
14104
  object_type: 'error',
@@ -13813,7 +14236,7 @@
13813
14236
  setContent: function setContent(content) {
13814
14237
  this._taco.c = content;
13815
14238
  if (this.element) {
13816
- if (typeof content === 'string') {
14239
+ if (_is(content, 'string')) {
13817
14240
  this.element.textContent = content;
13818
14241
  } else {
13819
14242
  // Re-render for complex content