bitwrench 2.0.10 → 2.0.12

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.
@@ -1,4 +1,4 @@
1
- /*! bitwrench v2.0.10 | BSD-2-Clause | http://deftio.com/bitwrench */
1
+ /*! bitwrench v2.0.12 | 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) :
@@ -11,14 +11,14 @@
11
11
  */
12
12
 
13
13
  const VERSION_INFO = {
14
- version: '2.0.10',
14
+ version: '2.0.12',
15
15
  name: 'bitwrench',
16
16
  description: 'A library for javascript UI functions.',
17
17
  license: 'BSD-2-Clause',
18
- homepage: 'http://deftio.com/bitwrench',
18
+ homepage: 'https://deftio.github.com/bitwrench/pages',
19
19
  repository: 'git+https://github.com/deftio/bitwrench.git',
20
20
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
21
- buildDate: '2026-03-07T03:14:16.606Z'
21
+ buildDate: '2026-03-07T22:31:35.755Z'
22
22
  };
23
23
 
24
24
  /**
@@ -690,7 +690,7 @@
690
690
  'background-color': palette.light.light
691
691
  };
692
692
  rules[scopeSelector(scope, '.bw-table-striped > tbody > tr:nth-of-type(odd) > *')] = {
693
- 'background-color': 'rgba(0, 0, 0, 0.025)'
693
+ 'background-color': 'rgba(0, 0, 0, 0.05)'
694
694
  };
695
695
  rules[scopeSelector(scope, '.bw-table-hover > tbody > tr:hover > *')] = {
696
696
  'background-color': palette.primary.focus
@@ -884,6 +884,142 @@
884
884
  return rules;
885
885
  }
886
886
 
887
+ function generateAccordionThemed(scope, palette) {
888
+ var rules = {};
889
+ rules[scopeSelector(scope, '.bw-accordion-item')] = {
890
+ 'background-color': '#fff',
891
+ 'border-color': palette.light.border
892
+ };
893
+ rules[scopeSelector(scope, '.bw-accordion-button')] = {
894
+ 'color': palette.dark.base
895
+ };
896
+ rules[scopeSelector(scope, '.bw-accordion-button:not(.bw-collapsed)')] = {
897
+ 'color': palette.primary.darkText,
898
+ 'background-color': palette.primary.light
899
+ };
900
+ rules[scopeSelector(scope, '.bw-accordion-button:hover')] = {
901
+ 'background-color': palette.light.light
902
+ };
903
+ rules[scopeSelector(scope, '.bw-accordion-button:not(.bw-collapsed):hover')] = {
904
+ 'background-color': palette.primary.hover
905
+ };
906
+ rules[scopeSelector(scope, '.bw-accordion-button:focus-visible')] = {
907
+ 'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
908
+ };
909
+ rules[scopeSelector(scope, '.bw-accordion-body')] = {
910
+ 'border-top': '1px solid ' + palette.light.border
911
+ };
912
+ return rules;
913
+ }
914
+
915
+ function generateCarouselThemed(scope, palette) {
916
+ var rules = {};
917
+ rules[scopeSelector(scope, '.bw-carousel')] = {
918
+ 'background-color': palette.light.light
919
+ };
920
+ rules[scopeSelector(scope, '.bw-carousel-indicator.active')] = {
921
+ 'background-color': palette.primary.base
922
+ };
923
+ return rules;
924
+ }
925
+
926
+ function generateModalThemed(scope, palette) {
927
+ var rules = {};
928
+ rules[scopeSelector(scope, '.bw-modal-content')] = {
929
+ 'background-color': '#fff',
930
+ 'border-color': palette.light.border,
931
+ 'box-shadow': '0 0.5rem 1rem rgba(0,0,0,0.15)'
932
+ };
933
+ rules[scopeSelector(scope, '.bw-modal-header')] = {
934
+ 'border-bottom-color': palette.light.border
935
+ };
936
+ rules[scopeSelector(scope, '.bw-modal-footer')] = {
937
+ 'border-top-color': palette.light.border
938
+ };
939
+ rules[scopeSelector(scope, '.bw-modal-title')] = {
940
+ 'color': palette.dark.base
941
+ };
942
+ return rules;
943
+ }
944
+
945
+ function generateToastThemed(scope, palette) {
946
+ var rules = {};
947
+ rules[scopeSelector(scope, '.bw-toast')] = {
948
+ 'background-color': '#fff',
949
+ 'border-color': 'rgba(0,0,0,0.1)',
950
+ 'box-shadow': '0 0.5rem 1rem rgba(0,0,0,0.15)'
951
+ };
952
+ rules[scopeSelector(scope, '.bw-toast-header')] = {
953
+ 'border-bottom-color': 'rgba(0,0,0,0.05)'
954
+ };
955
+ var variants = ['primary', 'secondary', 'success', 'danger', 'warning', 'info'];
956
+ variants.forEach(function(v) {
957
+ rules[scopeSelector(scope, '.bw-toast-' + v)] = {
958
+ 'border-left': '4px solid ' + palette[v].base
959
+ };
960
+ });
961
+ return rules;
962
+ }
963
+
964
+ function generateDropdownThemed(scope, palette) {
965
+ var rules = {};
966
+ rules[scopeSelector(scope, '.bw-dropdown-menu')] = {
967
+ 'background-color': '#fff',
968
+ 'border-color': palette.light.border,
969
+ 'box-shadow': '0 0.5rem 1rem rgba(0,0,0,0.15)'
970
+ };
971
+ rules[scopeSelector(scope, '.bw-dropdown-item')] = {
972
+ 'color': palette.dark.base
973
+ };
974
+ rules[scopeSelector(scope, '.bw-dropdown-item:hover')] = {
975
+ 'color': palette.dark.hover,
976
+ 'background-color': palette.light.light
977
+ };
978
+ rules[scopeSelector(scope, '.bw-dropdown-item.disabled')] = {
979
+ 'color': palette.secondary.base
980
+ };
981
+ rules[scopeSelector(scope, '.bw-dropdown-divider')] = {
982
+ 'border-top-color': palette.light.border
983
+ };
984
+ return rules;
985
+ }
986
+
987
+ function generateSwitchThemed(scope, palette) {
988
+ var rules = {};
989
+ rules[scopeSelector(scope, '.bw-form-switch .bw-switch-input')] = {
990
+ 'background-color': palette.secondary.base,
991
+ 'border-color': palette.secondary.base
992
+ };
993
+ rules[scopeSelector(scope, '.bw-form-switch .bw-switch-input:checked')] = {
994
+ 'background-color': palette.primary.base,
995
+ 'border-color': palette.primary.base
996
+ };
997
+ rules[scopeSelector(scope, '.bw-form-switch .bw-switch-input:focus')] = {
998
+ 'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
999
+ };
1000
+ return rules;
1001
+ }
1002
+
1003
+ function generateSkeletonThemed(scope, palette) {
1004
+ var rules = {};
1005
+ rules[scopeSelector(scope, '.bw-skeleton')] = {
1006
+ 'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.light.light + ' 37%, ' + palette.light.border + ' 63%)'
1007
+ };
1008
+ return rules;
1009
+ }
1010
+
1011
+ function generateAvatarThemed(scope, palette) {
1012
+ var rules = {};
1013
+ var variants = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'];
1014
+ variants.forEach(function(v) {
1015
+ rules[scopeSelector(scope, '.bw-avatar-' + v)] = {
1016
+ 'background-color': palette[v].base,
1017
+ 'color': palette[v].textOn
1018
+ };
1019
+ });
1020
+ return rules;
1021
+ }
1022
+
887
1023
  /**
888
1024
  * Generate all themed CSS rules from a palette and layout.
889
1025
  * Returns a flat CSS rules object (selector → declarations).
@@ -913,6 +1049,14 @@
913
1049
  generateSpinnerThemed(scopeName, palette),
914
1050
  generateCloseButtonThemed(scopeName, palette),
915
1051
  generateSectionsThemed(scopeName, palette),
1052
+ generateAccordionThemed(scopeName, palette),
1053
+ generateCarouselThemed(scopeName, palette),
1054
+ generateModalThemed(scopeName, palette),
1055
+ generateToastThemed(scopeName, palette),
1056
+ generateDropdownThemed(scopeName, palette),
1057
+ generateSwitchThemed(scopeName, palette),
1058
+ generateSkeletonThemed(scopeName, palette),
1059
+ generateAvatarThemed(scopeName, palette),
916
1060
  generateUtilityColors(scopeName, palette)
917
1061
  );
918
1062
  }
@@ -1182,7 +1326,7 @@
1182
1326
  'position': 'relative', 'display': 'flex', 'flex-wrap': 'wrap',
1183
1327
  'align-items': 'center', 'justify-content': 'space-between', 'padding': '0.5rem 1.5rem'
1184
1328
  };
1185
- rules['.bw-navbar > .container'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'space-between' };
1329
+ rules['.bw-navbar > .bw-container, .bw-navbar > .container'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'space-between' };
1186
1330
  rules['.bw-navbar-brand'] = {
1187
1331
  'display': 'inline-flex', 'align-items': 'center', 'gap': '0.5rem',
1188
1332
  'padding-top': '0.25rem', 'padding-bottom': '0.25rem', 'margin-right': '1.5rem',
@@ -1227,11 +1371,13 @@
1227
1371
 
1228
1372
  // Badges (structural)
1229
1373
  rules['.bw-badge'] = {
1230
- 'display': 'inline-block', 'padding': '.35em .65em', 'font-size': '.75em',
1231
- 'font-weight': '700', 'line-height': '1', 'text-align': 'center',
1374
+ 'display': 'inline-block', 'padding': '.4em .75em', 'font-size': '.875em',
1375
+ 'font-weight': '600', 'line-height': '1.3', 'text-align': 'center',
1232
1376
  'white-space': 'nowrap', 'vertical-align': 'baseline', 'border-radius': '.375rem'
1233
1377
  };
1234
1378
  rules['.bw-badge:empty'] = { 'display': 'none' };
1379
+ rules['.bw-badge-sm'] = { 'font-size': '.75em', 'padding': '.25em .5em' };
1380
+ rules['.bw-badge-lg'] = { 'font-size': '1em', 'padding': '.5em .9em' };
1235
1381
  rules['.bw-badge-pill'] = { 'border-radius': '50rem' };
1236
1382
 
1237
1383
  // Progress (structural)
@@ -1264,6 +1410,8 @@
1264
1410
  rules['.bw-tab-content'] = { 'padding': '1.25rem 0' };
1265
1411
  rules['.bw-tab-pane'] = { 'display': 'none' };
1266
1412
  rules['.bw-tab-pane.active'] = { 'display': 'block' };
1413
+ rules['.bw-nav-scrollable'] = { 'flex-wrap': 'nowrap', 'overflow-x': 'auto', '-webkit-overflow-scrolling': 'touch', 'scrollbar-width': 'none' };
1414
+ rules['.bw-nav-scrollable .bw-nav-link'] = { 'white-space': 'nowrap' };
1267
1415
 
1268
1416
  // List groups (structural)
1269
1417
  rules['.bw-list-group'] = { 'display': 'flex', 'flex-direction': 'column', 'padding-left': '0', 'margin-bottom': '0', 'border-radius': '0.375rem' };
@@ -1298,7 +1446,8 @@
1298
1446
  rules['.bw-hero'] = { 'position': 'relative', 'overflow': 'hidden' };
1299
1447
  rules['.bw-hero-overlay'] = { 'position': 'absolute', 'top': '0', 'left': '0', 'right': '0', 'bottom': '0', 'z-index': '1' };
1300
1448
  rules['.bw-hero-content'] = { 'position': 'relative', 'z-index': '2' };
1301
- rules['.bw-hero-title'] = { 'font-weight': '300', 'letter-spacing': '-0.05rem' };
1449
+ rules['.bw-hero-title'] = { 'font-weight': '300', 'letter-spacing': '-0.05rem', 'color': 'inherit' };
1450
+ rules['.bw-hero-subtitle'] = { 'color': 'inherit' };
1302
1451
  rules['.bw-hero-actions'] = { 'display': 'flex', 'gap': '1rem', 'justify-content': 'center', 'flex-wrap': 'wrap' };
1303
1452
  rules['.bw-display-4'] = { 'font-size': 'calc(1.475rem + 2.7vw)', 'font-weight': '300', 'line-height': '1.2' };
1304
1453
  rules['.bw-lead'] = { 'font-size': '1.25rem', 'font-weight': '300' };
@@ -1371,6 +1520,179 @@
1371
1520
 
1372
1521
  // Code demo (structural)
1373
1522
  rules['.bw-code-demo'] = { 'margin-bottom': '2rem' };
1523
+ rules['.bw-code-pre'] = { 'margin': '0', 'border': 'none', 'border-radius': '6px', 'overflow-x': 'auto' };
1524
+ rules['.bw-code-block'] = { 'display': 'block', 'padding': '1.25rem', 'font-family': '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace', 'font-size': '0.8125rem', 'line-height': '1.6' };
1525
+ rules['.bw-code-copy-btn'] = { 'position': 'absolute', 'top': '0.5rem', 'right': '0.5rem', 'padding': '0.25rem 0.625rem', 'font-size': '0.6875rem', 'border-radius': '4px', 'cursor': 'pointer', 'font-family': 'inherit', 'transition': 'all 0.15s' };
1526
+
1527
+ // Button group (structural)
1528
+ rules['.bw-btn-group, .bw-btn-group-vertical'] = { 'position': 'relative', 'display': 'inline-flex', 'vertical-align': 'middle' };
1529
+ rules['.bw-btn-group > .bw-btn, .bw-btn-group-vertical > .bw-btn'] = { 'position': 'relative', 'flex': '1 1 auto', 'border-radius': '0', 'margin-left': '-1px' };
1530
+ rules['.bw-btn-group > .bw-btn:first-child'] = { 'margin-left': '0', 'border-top-left-radius': '6px', 'border-bottom-left-radius': '6px' };
1531
+ rules['.bw-btn-group > .bw-btn:last-child'] = { 'border-top-right-radius': '6px', 'border-bottom-right-radius': '6px' };
1532
+ rules['.bw-btn-group-vertical'] = { 'flex-direction': 'column', 'align-items': 'flex-start', 'justify-content': 'center' };
1533
+ rules['.bw-btn-group-vertical > .bw-btn'] = { 'width': '100%', 'margin-left': '0', 'margin-top': '-1px' };
1534
+ rules['.bw-btn-group-vertical > .bw-btn:first-child'] = { 'margin-top': '0', 'border-top-left-radius': '6px', 'border-top-right-radius': '6px', 'border-bottom-left-radius': '0', 'border-bottom-right-radius': '0' };
1535
+ rules['.bw-btn-group-vertical > .bw-btn:last-child'] = { 'border-top-left-radius': '0', 'border-top-right-radius': '0', 'border-bottom-left-radius': '6px', 'border-bottom-right-radius': '6px' };
1536
+
1537
+ // Accordion (structural)
1538
+ rules['.bw-accordion'] = { 'border-radius': '8px', 'overflow': 'hidden' };
1539
+ rules['.bw-accordion-item'] = { 'border': '1px solid transparent' };
1540
+ rules['.bw-accordion-item + .bw-accordion-item'] = { 'border-top': '0' };
1541
+ rules['.bw-accordion-header'] = { 'margin': '0' };
1542
+ rules['.bw-accordion-button'] = {
1543
+ 'position': 'relative', 'display': 'flex', 'align-items': 'center', 'width': '100%',
1544
+ 'padding': '1rem 1.25rem', 'font-size': '1rem', 'font-weight': '500', 'text-align': 'left',
1545
+ 'background-color': 'transparent', 'border': '0', 'overflow-anchor': 'none', 'cursor': 'pointer',
1546
+ 'font-family': 'inherit', 'transition': 'color 0.15s ease-in-out, background-color 0.15s ease-in-out'
1547
+ };
1548
+ rules['.bw-accordion-button::after'] = {
1549
+ 'flex-shrink': '0', 'width': '1.25rem', 'height': '1.25rem', 'margin-left': 'auto',
1550
+ 'content': '""', 'background-repeat': 'no-repeat', 'background-size': '1.25rem',
1551
+ 'transition': 'transform 0.2s ease-in-out'
1552
+ };
1553
+ rules['.bw-accordion-button:not(.bw-collapsed)::after'] = { 'transform': 'rotate(-180deg)' };
1554
+ rules['.bw-accordion-collapse'] = { 'max-height': '0', 'overflow': 'hidden', 'transition': 'max-height 0.3s ease' };
1555
+ rules['.bw-accordion-collapse.bw-collapse-show'] = { 'max-height': 'none' };
1556
+ rules['.bw-accordion-body'] = { 'padding': '1rem 1.25rem' };
1557
+
1558
+ // Modal (structural)
1559
+ rules['.bw-modal'] = {
1560
+ 'display': 'none', 'position': 'fixed', 'top': '0', 'left': '0', 'width': '100%', 'height': '100%',
1561
+ 'z-index': '1050', 'overflow-x': 'hidden', 'overflow-y': 'auto', 'opacity': '0', 'transition': 'opacity 0.15s linear'
1562
+ };
1563
+ rules['.bw-modal.bw-modal-show'] = { 'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'opacity': '1' };
1564
+ rules['.bw-modal-dialog'] = {
1565
+ 'position': 'relative', 'width': '100%', 'max-width': '500px', 'margin': '1.75rem auto',
1566
+ 'pointer-events': 'none', 'transform': 'translateY(-20px)', 'transition': 'transform 0.2s ease-out'
1567
+ };
1568
+ rules['.bw-modal.bw-modal-show .bw-modal-dialog'] = { 'transform': 'translateY(0)' };
1569
+ rules['.bw-modal-sm'] = { 'max-width': '300px' };
1570
+ rules['.bw-modal-lg'] = { 'max-width': '800px' };
1571
+ rules['.bw-modal-xl'] = { 'max-width': '1140px' };
1572
+ rules['.bw-modal-content'] = {
1573
+ 'position': 'relative', 'display': 'flex', 'flex-direction': 'column', 'pointer-events': 'auto',
1574
+ 'background-clip': 'padding-box', 'border': '1px solid transparent', 'border-radius': '8px', 'outline': '0'
1575
+ };
1576
+ rules['.bw-modal-header'] = { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '1rem 1.5rem' };
1577
+ rules['.bw-modal-title'] = { 'margin': '0', 'font-size': '1.25rem', 'font-weight': '600', 'line-height': '1.3' };
1578
+ rules['.bw-modal-body'] = { 'position': 'relative', 'flex': '1 1 auto', 'padding': '1.5rem' };
1579
+ rules['.bw-modal-footer'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'flex-end', 'padding': '0.75rem 1.5rem', 'gap': '0.5rem' };
1580
+
1581
+ // Carousel (structural)
1582
+ rules['.bw-carousel'] = { 'position': 'relative', 'overflow': 'hidden', 'border-radius': '8px' };
1583
+ rules['.bw-carousel-track'] = { 'display': 'flex', 'transition': 'transform 0.4s ease', 'height': '100%' };
1584
+ rules['.bw-carousel-slide'] = { 'min-width': '100%', 'flex-shrink': '0', 'overflow': 'hidden', 'position': 'relative', 'display': 'flex', 'align-items': 'center', 'justify-content': 'center' };
1585
+ rules['.bw-carousel-slide img'] = { 'width': '100%', 'height': '100%', 'object-fit': 'cover' };
1586
+ rules['.bw-carousel-caption'] = { 'position': 'absolute', 'bottom': '0', 'left': '0', 'right': '0', 'padding': '0.75rem 1rem' };
1587
+ rules['.bw-carousel-control'] = {
1588
+ 'position': 'absolute', 'top': '50%', 'transform': 'translateY(-50%)', 'width': '40px', 'height': '40px',
1589
+ 'border': 'none', 'border-radius': '50%', 'cursor': 'pointer', 'display': 'flex', 'align-items': 'center',
1590
+ 'justify-content': 'center', 'z-index': '2', 'padding': '0', 'transition': 'background-color 0.2s ease'
1591
+ };
1592
+ rules['.bw-carousel-control img'] = { 'width': '20px', 'height': '20px', 'pointer-events': 'none' };
1593
+ rules['.bw-carousel-control-prev'] = { 'left': '10px' };
1594
+ rules['.bw-carousel-control-next'] = { 'right': '10px' };
1595
+ rules['.bw-carousel-indicators'] = {
1596
+ 'position': 'absolute', 'bottom': '12px', 'left': '50%', 'transform': 'translateX(-50%)',
1597
+ 'display': 'flex', 'gap': '6px', 'z-index': '2'
1598
+ };
1599
+ rules['.bw-carousel-indicator'] = {
1600
+ 'width': '10px', 'height': '10px', 'border-radius': '50%', 'border': '2px solid transparent',
1601
+ 'padding': '0', 'cursor': 'pointer', 'transition': 'opacity 0.2s ease, background-color 0.2s ease'
1602
+ };
1603
+
1604
+ // Toast (structural)
1605
+ rules['.bw-toast-container'] = {
1606
+ 'position': 'fixed', 'z-index': '1080', 'pointer-events': 'none',
1607
+ 'display': 'flex', 'flex-direction': 'column', 'gap': '0.5rem', 'padding': '1rem'
1608
+ };
1609
+ rules['.bw-toast'] = {
1610
+ 'pointer-events': 'auto', 'width': '350px', 'max-width': '100%', 'background-clip': 'padding-box',
1611
+ 'border-radius': '8px', 'opacity': '0', 'transform': 'translateY(-10px)',
1612
+ 'transition': 'opacity 0.3s ease, transform 0.3s ease'
1613
+ };
1614
+ rules['.bw-toast.bw-toast-show'] = { 'opacity': '1', 'transform': 'translateY(0)' };
1615
+ rules['.bw-toast.bw-toast-hiding'] = { 'opacity': '0', 'transform': 'translateY(-10px)' };
1616
+ rules['.bw-toast-header'] = { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '0.5rem 0.75rem', 'font-size': '0.875rem' };
1617
+ rules['.bw-toast-body'] = { 'padding': '0.75rem', 'font-size': '0.9375rem' };
1618
+
1619
+ // Dropdown (structural)
1620
+ rules['.bw-dropdown'] = { 'position': 'relative', 'display': 'inline-block' };
1621
+ rules['.bw-dropdown-toggle::after'] = {
1622
+ 'display': 'inline-block', 'margin-left': '0.255em', 'vertical-align': '0.255em',
1623
+ 'content': '""', 'border-top': '0.3em solid', 'border-right': '0.3em solid transparent',
1624
+ 'border-bottom': '0', 'border-left': '0.3em solid transparent'
1625
+ };
1626
+ rules['.bw-dropdown-menu'] = {
1627
+ 'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': 'none',
1628
+ 'min-width': '10rem', 'padding': '0.5rem 0', 'margin': '0.125rem 0 0',
1629
+ 'background-clip': 'padding-box', 'border-radius': '6px'
1630
+ };
1631
+ rules['.bw-dropdown-menu.bw-dropdown-show'] = { 'display': 'block' };
1632
+ rules['.bw-dropdown-menu-end'] = { 'left': 'auto', 'right': '0' };
1633
+ rules['.bw-dropdown-item'] = {
1634
+ 'display': 'block', 'width': '100%', 'padding': '0.375rem 1rem', 'clear': 'both',
1635
+ 'font-weight': '400', 'text-align': 'inherit', 'text-decoration': 'none', 'white-space': 'nowrap',
1636
+ 'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem',
1637
+ 'transition': 'background-color 0.15s, color 0.15s'
1638
+ };
1639
+ rules['.bw-dropdown-divider'] = { 'height': '0', 'margin': '0.5rem 0', 'overflow': 'hidden', 'opacity': '1' };
1640
+
1641
+ // Switch (structural)
1642
+ rules['.bw-form-switch'] = { 'padding-left': '2.5em' };
1643
+ rules['.bw-form-switch .bw-switch-input'] = {
1644
+ 'width': '2em', 'height': '1.125em', 'margin-left': '-2.5em', 'border-radius': '2em',
1645
+ 'appearance': 'none', 'background-position': 'left center', 'background-repeat': 'no-repeat',
1646
+ 'background-size': 'contain', 'transition': 'background-position 0.15s ease-in-out, background-color 0.15s ease-in-out',
1647
+ 'cursor': 'pointer'
1648
+ };
1649
+ rules['.bw-form-switch .bw-switch-input:checked'] = { 'background-position': 'right center' };
1650
+ rules['.bw-form-switch .bw-switch-input:disabled'] = { 'opacity': '0.5', 'cursor': 'not-allowed' };
1651
+
1652
+ // Skeleton (structural)
1653
+ rules['.bw-skeleton'] = { 'border-radius': '4px', 'background-size': '400% 100%', 'animation': 'bw-skeleton-shimmer 1.4s ease infinite' };
1654
+ rules['.bw-skeleton-text'] = { 'height': '1em', 'margin-bottom': '0.5rem' };
1655
+ rules['.bw-skeleton-circle'] = { 'border-radius': '50%' };
1656
+ rules['.bw-skeleton-rect'] = { 'border-radius': '8px' };
1657
+ rules['.bw-skeleton-group'] = { 'display': 'flex', 'flex-direction': 'column' };
1658
+ rules['@keyframes bw-skeleton-shimmer'] = { '0%': { 'background-position': '100% 50%' }, '100%': { 'background-position': '0 50%' } };
1659
+
1660
+ // Avatar (structural)
1661
+ rules['.bw-avatar'] = {
1662
+ 'display': 'inline-flex', 'align-items': 'center', 'justify-content': 'center',
1663
+ 'border-radius': '50%', 'overflow': 'hidden', 'font-weight': '600',
1664
+ 'text-transform': 'uppercase', 'vertical-align': 'middle', 'object-fit': 'cover'
1665
+ };
1666
+ rules['.bw-avatar-sm'] = { 'width': '2rem', 'height': '2rem', 'font-size': '0.75rem' };
1667
+ rules['.bw-avatar-md'] = { 'width': '3rem', 'height': '3rem', 'font-size': '1rem' };
1668
+ rules['.bw-avatar-lg'] = { 'width': '4rem', 'height': '4rem', 'font-size': '1.25rem' };
1669
+ rules['.bw-avatar-xl'] = { 'width': '5rem', 'height': '5rem', 'font-size': '1.5rem' };
1670
+
1671
+ // Bar chart (structural)
1672
+ rules['.bw-bar-chart-container'] = {
1673
+ 'padding': '1rem', 'border': '1px solid transparent', 'border-radius': '8px'
1674
+ };
1675
+ rules['.bw-bar-chart'] = {
1676
+ 'display': 'flex', 'align-items': 'flex-end', 'gap': '6px', 'padding': '0 0.5rem'
1677
+ };
1678
+ rules['.bw-bar-group'] = {
1679
+ 'flex': '1', 'display': 'flex', 'flex-direction': 'column',
1680
+ 'align-items': 'center', 'height': '100%', 'justify-content': 'flex-end'
1681
+ };
1682
+ rules['.bw-bar'] = {
1683
+ 'width': '100%', 'border-radius': '3px 3px 0 0',
1684
+ 'transition': 'height 0.5s ease', 'min-height': '4px'
1685
+ };
1686
+ rules['.bw-bar:hover'] = { 'opacity': '0.85' };
1687
+ rules['.bw-bar-value'] = {
1688
+ 'font-size': '0.65rem', 'font-weight': '600', 'margin-bottom': '2px', 'text-align': 'center'
1689
+ };
1690
+ rules['.bw-bar-label'] = {
1691
+ 'font-size': '0.7rem', 'margin-top': '4px', 'text-align': 'center'
1692
+ };
1693
+ rules['.bw-bar-chart-title'] = {
1694
+ 'font-size': '1.1rem', 'font-weight': '600', 'margin': '0 0 0.75rem 0'
1695
+ };
1374
1696
 
1375
1697
  // Spacing utilities (structural)
1376
1698
  var spacingValues = { '0': '0', '1': '.25rem', '2': '.5rem', '3': '1rem', '4': '1.5rem', '5': '3rem' };
@@ -1688,6 +2010,75 @@
1688
2010
  '.bw-dark .bw-close': {
1689
2011
  'color': textColor
1690
2012
  },
2013
+ '.bw-dark .bw-accordion-item': {
2014
+ 'background-color': surfaceBg,
2015
+ 'border-color': borderColor
2016
+ },
2017
+ '.bw-dark .bw-accordion-button': {
2018
+ 'color': textColor
2019
+ },
2020
+ '.bw-dark .bw-accordion-button:not(.bw-collapsed)': {
2021
+ 'color': '#7dd3e0',
2022
+ 'background-color': 'rgba(125, 211, 224, 0.1)'
2023
+ },
2024
+ '.bw-dark .bw-accordion-button:hover': {
2025
+ 'background-color': bodyBg
2026
+ },
2027
+ '.bw-dark .bw-accordion-button:not(.bw-collapsed):hover': {
2028
+ 'background-color': 'rgba(125, 211, 224, 0.15)'
2029
+ },
2030
+ '.bw-dark .bw-accordion-button:focus-visible': {
2031
+ 'box-shadow': '0 0 0 0.2rem rgba(125, 211, 224, 0.3)'
2032
+ },
2033
+ '.bw-dark .bw-accordion-body': {
2034
+ 'border-top-color': borderColor
2035
+ },
2036
+ '.bw-dark .bw-carousel': {
2037
+ 'background-color': bodyBg
2038
+ },
2039
+ '.bw-dark .bw-carousel-control': {
2040
+ 'background-color': 'rgba(255,255,255,0.15)'
2041
+ },
2042
+ '.bw-dark .bw-carousel-control:hover': {
2043
+ 'background-color': 'rgba(255,255,255,0.25)'
2044
+ },
2045
+ '.bw-dark .bw-modal-content': {
2046
+ 'background-color': surfaceBg,
2047
+ 'border-color': borderColor
2048
+ },
2049
+ '.bw-dark .bw-modal-header': {
2050
+ 'border-bottom-color': borderColor
2051
+ },
2052
+ '.bw-dark .bw-modal-footer': {
2053
+ 'border-top-color': borderColor
2054
+ },
2055
+ '.bw-dark .bw-modal-title': {
2056
+ 'color': textColor
2057
+ },
2058
+ '.bw-dark .bw-toast': {
2059
+ 'background-color': surfaceBg,
2060
+ 'border-color': borderColor
2061
+ },
2062
+ '.bw-dark .bw-toast-header': {
2063
+ 'border-bottom-color': borderColor,
2064
+ 'color': textColor
2065
+ },
2066
+ '.bw-dark .bw-dropdown-menu': {
2067
+ 'background-color': surfaceBg,
2068
+ 'border-color': borderColor
2069
+ },
2070
+ '.bw-dark .bw-dropdown-item': {
2071
+ 'color': textColor
2072
+ },
2073
+ '.bw-dark .bw-dropdown-item:hover': {
2074
+ 'background-color': bodyBg
2075
+ },
2076
+ '.bw-dark .bw-dropdown-divider': {
2077
+ 'border-top-color': borderColor
2078
+ },
2079
+ '.bw-dark .bw-skeleton': {
2080
+ 'background': 'linear-gradient(90deg, ' + borderColor + ' 25%, ' + surfaceBg + ' 37%, ' + borderColor + ' 63%)'
2081
+ },
1691
2082
  '.bw-dark h1, .bw-dark h2, .bw-dark h3, .bw-dark h4, .bw-dark h5, .bw-dark h6': {
1692
2083
  'color': textColor
1693
2084
  },
@@ -2281,7 +2672,11 @@
2281
2672
  a: {
2282
2673
  type: 'button',
2283
2674
  class: 'bw-close',
2284
- 'aria-label': 'Close'
2675
+ 'aria-label': 'Close',
2676
+ onclick: function(e) {
2677
+ var alert = e.target.closest('.bw-alert');
2678
+ if (alert) { alert.remove(); }
2679
+ }
2285
2680
  },
2286
2681
  c: '×'
2287
2682
  }
@@ -2295,25 +2690,30 @@
2295
2690
  * @param {Object} [props] - Badge configuration
2296
2691
  * @param {string} [props.text] - Badge display text
2297
2692
  * @param {string} [props.variant="primary"] - Color variant
2693
+ * @param {string} [props.size] - Size variant: 'sm' or 'lg' (default is medium)
2298
2694
  * @param {boolean} [props.pill=false] - Use pill (rounded) shape
2299
2695
  * @param {string} [props.className] - Additional CSS classes
2300
2696
  * @returns {Object} TACO object representing a badge span
2301
2697
  * @category Component Builders
2302
2698
  * @example
2303
2699
  * const badge = makeBadge({ text: "New", variant: "danger", pill: true });
2700
+ * const small = makeBadge({ text: "3", variant: "info", size: "sm" });
2304
2701
  */
2305
2702
  function makeBadge(props = {}) {
2306
2703
  const {
2307
2704
  text,
2308
2705
  variant = 'primary',
2706
+ size,
2309
2707
  pill = false,
2310
2708
  className = ''
2311
2709
  } = props;
2312
2710
 
2711
+ const sizeClass = size === 'sm' ? ' bw-badge-sm' : size === 'lg' ? ' bw-badge-lg' : '';
2712
+
2313
2713
  return {
2314
2714
  t: 'span',
2315
2715
  a: {
2316
- class: `bw-badge bw-badge-${variant} ${pill ? 'bw-badge-pill' : ''} ${className}`.trim()
2716
+ class: `bw-badge bw-badge-${variant}${sizeClass} ${pill ? 'bw-badge-pill' : ''} ${className}`.trim()
2317
2717
  },
2318
2718
  c: text
2319
2719
  };
@@ -2772,12 +3172,14 @@
2772
3172
  id,
2773
3173
  name,
2774
3174
  disabled = false,
2775
- value
3175
+ value,
3176
+ className = '',
3177
+ ...eventHandlers
2776
3178
  } = props;
2777
3179
 
2778
3180
  return {
2779
3181
  t: 'div',
2780
- a: { class: 'bw-form-check' },
3182
+ a: { class: `bw-form-check ${className}`.trim() },
2781
3183
  c: [
2782
3184
  {
2783
3185
  t: 'input',
@@ -2788,7 +3190,8 @@
2788
3190
  id,
2789
3191
  name,
2790
3192
  disabled,
2791
- value
3193
+ value,
3194
+ ...eventHandlers
2792
3195
  }
2793
3196
  },
2794
3197
  label && {
@@ -3016,8 +3419,8 @@
3016
3419
  feature.icon && {
3017
3420
  t: 'div',
3018
3421
  a: {
3019
- class: 'bw-feature-icon bw-mb-3',
3020
- style: `font-size: ${iconSize}; color: var(--bw-primary);`
3422
+ class: 'bw-feature-icon bw-mb-3 bw-text-primary',
3423
+ style: `font-size: ${iconSize};`
3021
3424
  },
3022
3425
  c: feature.icon
3023
3426
  },
@@ -3458,177 +3861,1201 @@
3458
3861
  } else {
3459
3862
  item.classList.remove('active');
3460
3863
  }
3461
- });
3864
+ });
3865
+
3866
+ this.children.tabPanes.forEach((pane, i) => {
3867
+ if (i === index) {
3868
+ pane.classList.add('active');
3869
+ } else {
3870
+ pane.classList.remove('active');
3871
+ }
3872
+ });
3873
+
3874
+ this.state.activeIndex = index;
3875
+ return this;
3876
+ }
3877
+ }
3878
+
3879
+ /**
3880
+ * Create a code demo component for documentation pages
3881
+ *
3882
+ * Displays a live result alongside source code in a tabbed interface.
3883
+ * Includes a copy-to-clipboard button on the code tab.
3884
+ *
3885
+ * @param {Object} [props] - Code demo configuration
3886
+ * @param {string} [props.title] - Demo title heading
3887
+ * @param {string} [props.description] - Demo description text
3888
+ * @param {string} [props.code] - Source code to display (adds a "Code" tab when present)
3889
+ * @param {string|Object|Array} [props.result] - Live result content for the "Result" tab
3890
+ * @param {string} [props.language="javascript"] - Code language for syntax class
3891
+ * @returns {Object} TACO object representing a code demo with tabbed Result/Code views
3892
+ * @category Component Builders
3893
+ * @example
3894
+ * const demo = makeCodeDemo({
3895
+ * title: "Button Example",
3896
+ * description: "A simple primary button",
3897
+ * code: 'makeButton({ text: "Click me" })',
3898
+ * result: makeButton({ text: "Click me" })
3899
+ * });
3900
+ */
3901
+ function makeCodeDemo(props = {}) {
3902
+ const {
3903
+ title,
3904
+ description,
3905
+ code,
3906
+ result,
3907
+ language = 'javascript'
3908
+ } = props;
3909
+
3910
+ // Generate unique ID for this demo
3911
+ `demo-${Math.random().toString(36).substr(2, 9)}`;
3912
+
3913
+ const tabs = [
3914
+ {
3915
+ label: 'Result',
3916
+ active: true,
3917
+ content: result
3918
+ }
3919
+ ];
3920
+
3921
+ // Only add Code tab if code is provided
3922
+ if (code) {
3923
+ tabs.push({
3924
+ label: 'Code',
3925
+ content: {
3926
+ t: 'div',
3927
+ a: { style: 'position: relative;' },
3928
+ c: [
3929
+ {
3930
+ t: 'button',
3931
+ a: {
3932
+ class: 'bw-copy-btn bw-code-copy-btn',
3933
+ onclick: (e) => {
3934
+ navigator.clipboard.writeText(code).then(() => {
3935
+ const btn = e.target;
3936
+ const originalText = btn.textContent;
3937
+ btn.textContent = 'Copied!';
3938
+ btn.style.background = '#006666';
3939
+ btn.style.color = '#fff';
3940
+ setTimeout(() => {
3941
+ btn.textContent = originalText;
3942
+ btn.style.background = 'rgba(255,255,255,0.12)';
3943
+ btn.style.color = '#aaa';
3944
+ }, 2000);
3945
+ });
3946
+ }
3947
+ },
3948
+ c: 'Copy'
3949
+ },
3950
+ (typeof globalThis !== 'undefined' && typeof globalThis.bw !== 'undefined' && typeof globalThis.bw.codeEditor === 'function')
3951
+ ? globalThis.bw.codeEditor({ code: code, lang: language === 'javascript' ? 'js' : language, readOnly: true, height: 'auto' })
3952
+ : {
3953
+ t: 'pre',
3954
+ a: { class: 'bw-code-pre' },
3955
+ c: {
3956
+ t: 'code',
3957
+ a: { class: `bw-code-block language-${language}` },
3958
+ c: code
3959
+ }
3960
+ }
3961
+ ]
3962
+ }
3963
+ });
3964
+ }
3965
+
3966
+ const content = [
3967
+ title && { t: 'h3', c: title },
3968
+ description && {
3969
+ t: 'p',
3970
+ a: { class: 'bw-text-muted', style: 'margin-bottom: 1rem;' },
3971
+ c: description
3972
+ },
3973
+ makeTabs({ tabs})
3974
+ ].filter(Boolean);
3975
+
3976
+ return {
3977
+ t: 'div',
3978
+ a: { class: 'bw-code-demo' },
3979
+ c: content
3980
+ };
3981
+ }
3982
+
3983
+ /**
3984
+ * Registry mapping component type names to their handle classes
3985
+ *
3986
+ * Used by bw.createCard(), bw.createTable(), etc. to wrap rendered
3987
+ * DOM elements in the appropriate imperative handle.
3988
+ *
3989
+ * @type {Object.<string, Function>}
3990
+ */
3991
+ // =========================================================================
3992
+ // Phase 1: Quick Wins
3993
+ // =========================================================================
3994
+
3995
+ /**
3996
+ * Create a pagination navigation component
3997
+ *
3998
+ * @param {Object} [props] - Pagination configuration
3999
+ * @param {number} [props.pages=1] - Total number of pages
4000
+ * @param {number} [props.currentPage=1] - Currently active page (1-based)
4001
+ * @param {Function} [props.onPageChange] - Callback when page changes, receives page number
4002
+ * @param {string} [props.size] - Size variant ("sm" or "lg")
4003
+ * @param {string} [props.className] - Additional CSS classes
4004
+ * @returns {Object} TACO object representing a pagination nav
4005
+ * @category Component Builders
4006
+ * @example
4007
+ * const pager = makePagination({
4008
+ * pages: 10,
4009
+ * currentPage: 3,
4010
+ * onPageChange: (page) => loadPage(page)
4011
+ * });
4012
+ */
4013
+ function makePagination(props = {}) {
4014
+ const {
4015
+ pages = 1,
4016
+ currentPage = 1,
4017
+ onPageChange,
4018
+ size,
4019
+ className = ''
4020
+ } = props;
4021
+
4022
+ function handleClick(page) {
4023
+ return function(e) {
4024
+ e.preventDefault();
4025
+ if (page < 1 || page > pages || page === currentPage) return;
4026
+ if (onPageChange) onPageChange(page);
4027
+ };
4028
+ }
4029
+
4030
+ const items = [];
4031
+
4032
+ // Previous arrow
4033
+ items.push({
4034
+ t: 'li',
4035
+ a: { class: `bw-page-item ${currentPage <= 1 ? 'bw-disabled' : ''}`.trim() },
4036
+ c: {
4037
+ t: 'a',
4038
+ a: { class: 'bw-page-link', href: '#', onclick: handleClick(currentPage - 1), 'aria-label': 'Previous' },
4039
+ c: '\u2039'
4040
+ }
4041
+ });
4042
+
4043
+ // Page numbers
4044
+ for (var i = 1; i <= pages; i++) {
4045
+ (function(pageNum) {
4046
+ items.push({
4047
+ t: 'li',
4048
+ a: { class: `bw-page-item ${pageNum === currentPage ? 'bw-active' : ''}`.trim() },
4049
+ c: {
4050
+ t: 'a',
4051
+ a: { class: 'bw-page-link', href: '#', onclick: handleClick(pageNum) },
4052
+ c: '' + pageNum
4053
+ }
4054
+ });
4055
+ })(i);
4056
+ }
4057
+
4058
+ // Next arrow
4059
+ items.push({
4060
+ t: 'li',
4061
+ a: { class: `bw-page-item ${currentPage >= pages ? 'bw-disabled' : ''}`.trim() },
4062
+ c: {
4063
+ t: 'a',
4064
+ a: { class: 'bw-page-link', href: '#', onclick: handleClick(currentPage + 1), 'aria-label': 'Next' },
4065
+ c: '\u203A'
4066
+ }
4067
+ });
4068
+
4069
+ return {
4070
+ t: 'nav',
4071
+ a: { 'aria-label': 'Pagination' },
4072
+ c: {
4073
+ t: 'ul',
4074
+ a: {
4075
+ class: `bw-pagination ${size ? 'bw-pagination-' + size : ''} ${className}`.trim()
4076
+ },
4077
+ c: items
4078
+ }
4079
+ };
4080
+ }
4081
+
4082
+ /**
4083
+ * Create a radio button input with label
4084
+ *
4085
+ * @param {Object} [props] - Radio configuration
4086
+ * @param {string} [props.label] - Radio label text
4087
+ * @param {string} [props.name] - Radio group name
4088
+ * @param {string} [props.value] - Radio value attribute
4089
+ * @param {boolean} [props.checked=false] - Whether the radio is selected
4090
+ * @param {string} [props.id] - Element ID (links label to radio)
4091
+ * @param {boolean} [props.disabled=false] - Whether the radio is disabled
4092
+ * @param {string} [props.className] - Additional CSS classes
4093
+ * @returns {Object} TACO object representing a radio form group
4094
+ * @category Component Builders
4095
+ * @example
4096
+ * const radio = makeRadio({
4097
+ * label: "Option A",
4098
+ * name: "choice",
4099
+ * value: "a",
4100
+ * checked: true
4101
+ * });
4102
+ */
4103
+ function makeRadio(props = {}) {
4104
+ const {
4105
+ label,
4106
+ name,
4107
+ value,
4108
+ checked = false,
4109
+ id,
4110
+ disabled = false,
4111
+ className = '',
4112
+ ...eventHandlers
4113
+ } = props;
4114
+
4115
+ return {
4116
+ t: 'div',
4117
+ a: { class: `bw-form-check ${className}`.trim() },
4118
+ c: [
4119
+ {
4120
+ t: 'input',
4121
+ a: {
4122
+ type: 'radio',
4123
+ class: 'bw-form-check-input',
4124
+ name,
4125
+ value,
4126
+ checked,
4127
+ id,
4128
+ disabled,
4129
+ ...eventHandlers
4130
+ }
4131
+ },
4132
+ label && {
4133
+ t: 'label',
4134
+ a: { class: 'bw-form-check-label', for: id },
4135
+ c: label
4136
+ }
4137
+ ].filter(Boolean)
4138
+ };
4139
+ }
4140
+
4141
+ /**
4142
+ * Create a button group wrapper
4143
+ *
4144
+ * @param {Object} [props] - Button group configuration
4145
+ * @param {Array} [props.children] - Button TACO objects to group
4146
+ * @param {string} [props.size] - Size variant ("sm" or "lg")
4147
+ * @param {boolean} [props.vertical=false] - Stack buttons vertically
4148
+ * @param {string} [props.className] - Additional CSS classes
4149
+ * @returns {Object} TACO object representing a button group
4150
+ * @category Component Builders
4151
+ * @example
4152
+ * const group = makeButtonGroup({
4153
+ * children: [
4154
+ * makeButton({ text: "Left", variant: "primary" }),
4155
+ * makeButton({ text: "Middle", variant: "primary" }),
4156
+ * makeButton({ text: "Right", variant: "primary" })
4157
+ * ]
4158
+ * });
4159
+ */
4160
+ function makeButtonGroup(props = {}) {
4161
+ const {
4162
+ children,
4163
+ size,
4164
+ vertical = false,
4165
+ className = ''
4166
+ } = props;
4167
+
4168
+ return {
4169
+ t: 'div',
4170
+ a: {
4171
+ class: `${vertical ? 'bw-btn-group-vertical' : 'bw-btn-group'} ${size ? 'bw-btn-group-' + size : ''} ${className}`.trim(),
4172
+ role: 'group'
4173
+ },
4174
+ c: children
4175
+ };
4176
+ }
4177
+
4178
+ // =========================================================================
4179
+ // Phase 2: Core Interactive
4180
+ // =========================================================================
4181
+
4182
+ /**
4183
+ * Create an accordion component with collapsible items
4184
+ *
4185
+ * @param {Object} [props] - Accordion configuration
4186
+ * @param {Array<Object>} [props.items=[]] - Accordion items
4187
+ * @param {string} props.items[].title - Header text for the accordion item
4188
+ * @param {string|Object|Array} props.items[].content - Collapsible content
4189
+ * @param {boolean} [props.items[].open=false] - Whether the item is initially open
4190
+ * @param {boolean} [props.multiOpen=false] - Allow multiple items open simultaneously
4191
+ * @param {string} [props.className] - Additional CSS classes
4192
+ * @returns {Object} TACO object representing an accordion
4193
+ * @category Component Builders
4194
+ * @example
4195
+ * const accordion = makeAccordion({
4196
+ * items: [
4197
+ * { title: "Section 1", content: "Content 1", open: true },
4198
+ * { title: "Section 2", content: "Content 2" }
4199
+ * ]
4200
+ * });
4201
+ */
4202
+ function makeAccordion(props = {}) {
4203
+ const {
4204
+ items = [],
4205
+ multiOpen = false,
4206
+ className = ''
4207
+ } = props;
4208
+
4209
+ return {
4210
+ t: 'div',
4211
+ a: { class: `bw-accordion ${className}`.trim() },
4212
+ c: items.map(function(item, index) {
4213
+ return {
4214
+ t: 'div',
4215
+ a: { class: 'bw-accordion-item' },
4216
+ c: [
4217
+ {
4218
+ t: 'h2',
4219
+ a: { class: 'bw-accordion-header' },
4220
+ c: {
4221
+ t: 'button',
4222
+ a: {
4223
+ class: `bw-accordion-button ${item.open ? '' : 'bw-collapsed'}`.trim(),
4224
+ type: 'button',
4225
+ 'aria-expanded': item.open ? 'true' : 'false',
4226
+ 'data-accordion-index': index,
4227
+ onclick: function(e) {
4228
+ var btn = e.target.closest('.bw-accordion-button');
4229
+ var accordionEl = btn.closest('.bw-accordion');
4230
+ var accordionItem = btn.closest('.bw-accordion-item');
4231
+ var collapse = accordionItem.querySelector('.bw-accordion-collapse');
4232
+ var isOpen = collapse.classList.contains('bw-collapse-show');
4233
+
4234
+ if (!multiOpen) {
4235
+ // Close all siblings
4236
+ var allCollapses = accordionEl.querySelectorAll('.bw-accordion-collapse');
4237
+ var allButtons = accordionEl.querySelectorAll('.bw-accordion-button');
4238
+ for (var j = 0; j < allCollapses.length; j++) {
4239
+ allCollapses[j].classList.remove('bw-collapse-show');
4240
+ allCollapses[j].style.maxHeight = null;
4241
+ }
4242
+ for (var k = 0; k < allButtons.length; k++) {
4243
+ allButtons[k].classList.add('bw-collapsed');
4244
+ allButtons[k].setAttribute('aria-expanded', 'false');
4245
+ }
4246
+ }
4247
+
4248
+ if (isOpen) {
4249
+ collapse.classList.remove('bw-collapse-show');
4250
+ collapse.style.maxHeight = null;
4251
+ btn.classList.add('bw-collapsed');
4252
+ btn.setAttribute('aria-expanded', 'false');
4253
+ } else {
4254
+ collapse.classList.add('bw-collapse-show');
4255
+ collapse.style.maxHeight = collapse.scrollHeight + 'px';
4256
+ btn.classList.remove('bw-collapsed');
4257
+ btn.setAttribute('aria-expanded', 'true');
4258
+ }
4259
+ }
4260
+ },
4261
+ c: item.title
4262
+ }
4263
+ },
4264
+ {
4265
+ t: 'div',
4266
+ a: { class: `bw-accordion-collapse ${item.open ? 'bw-collapse-show' : ''}`.trim() },
4267
+ c: {
4268
+ t: 'div',
4269
+ a: { class: 'bw-accordion-body' },
4270
+ c: item.content
4271
+ },
4272
+ o: item.open ? {
4273
+ mounted: function(el) {
4274
+ el.style.maxHeight = el.scrollHeight + 'px';
4275
+ }
4276
+ } : undefined
4277
+ }
4278
+ ]
4279
+ };
4280
+ }),
4281
+ o: {
4282
+ type: 'accordion',
4283
+ state: { multiOpen: multiOpen }
4284
+ }
4285
+ };
4286
+ }
4287
+
4288
+ /**
4289
+ * Imperative handle for a rendered modal component
4290
+ *
4291
+ * Provides `.show()`, `.hide()`, `.toggle()`, and `.destroy()` methods
4292
+ * for controlling the modal programmatically.
4293
+ *
4294
+ * @category Component Handles
4295
+ */
4296
+ class ModalHandle {
4297
+ /**
4298
+ * @param {Element} element - The modal backdrop DOM element
4299
+ * @param {Object} taco - The original TACO object
4300
+ */
4301
+ constructor(element, taco) {
4302
+ this.element = element;
4303
+ this._taco = taco;
4304
+ this._escHandler = null;
4305
+ }
4306
+
4307
+ /** Show the modal */
4308
+ show() {
4309
+ this.element.classList.add('bw-modal-show');
4310
+ document.body.style.overflow = 'hidden';
4311
+ return this;
4312
+ }
4313
+
4314
+ /** Hide the modal */
4315
+ hide() {
4316
+ this.element.classList.remove('bw-modal-show');
4317
+ document.body.style.overflow = '';
4318
+ return this;
4319
+ }
4320
+
4321
+ /** Toggle modal visibility */
4322
+ toggle() {
4323
+ if (this.element.classList.contains('bw-modal-show')) {
4324
+ this.hide();
4325
+ } else {
4326
+ this.show();
4327
+ }
4328
+ return this;
4329
+ }
4330
+
4331
+ /** Remove the modal from DOM and clean up */
4332
+ destroy() {
4333
+ this.hide();
4334
+ if (this._escHandler) {
4335
+ document.removeEventListener('keydown', this._escHandler);
4336
+ }
4337
+ if (this.element.parentNode) {
4338
+ this.element.parentNode.removeChild(this.element);
4339
+ }
4340
+ }
4341
+ }
4342
+
4343
+ /**
4344
+ * Create a modal dialog overlay
4345
+ *
4346
+ * @param {Object} [props] - Modal configuration
4347
+ * @param {string} [props.title] - Modal title in header
4348
+ * @param {string|Object|Array} [props.content] - Modal body content
4349
+ * @param {string|Object|Array} [props.footer] - Modal footer content
4350
+ * @param {string} [props.size] - Modal size ("sm", "lg", "xl")
4351
+ * @param {boolean} [props.closeButton=true] - Show X close button in header
4352
+ * @param {Function} [props.onClose] - Callback when modal is closed
4353
+ * @param {string} [props.className] - Additional CSS classes
4354
+ * @returns {Object} TACO object representing a modal
4355
+ * @category Component Builders
4356
+ * @example
4357
+ * const modal = makeModal({
4358
+ * title: "Confirm",
4359
+ * content: "Are you sure?",
4360
+ * footer: makeButton({ text: "OK", variant: "primary" })
4361
+ * });
4362
+ */
4363
+ function makeModal(props = {}) {
4364
+ const {
4365
+ title,
4366
+ content,
4367
+ footer,
4368
+ size,
4369
+ closeButton = true,
4370
+ onClose,
4371
+ className = ''
4372
+ } = props;
4373
+
4374
+ function closeModal(el) {
4375
+ var backdrop = el.closest('.bw-modal');
4376
+ if (backdrop) {
4377
+ backdrop.classList.remove('bw-modal-show');
4378
+ document.body.style.overflow = '';
4379
+ }
4380
+ if (onClose) onClose();
4381
+ }
4382
+
4383
+ return {
4384
+ t: 'div',
4385
+ a: { class: `bw-modal ${className}`.trim() },
4386
+ c: {
4387
+ t: 'div',
4388
+ a: { class: `bw-modal-dialog ${size ? 'bw-modal-' + size : ''}`.trim() },
4389
+ c: {
4390
+ t: 'div',
4391
+ a: { class: 'bw-modal-content' },
4392
+ c: [
4393
+ (title || closeButton) && {
4394
+ t: 'div',
4395
+ a: { class: 'bw-modal-header' },
4396
+ c: [
4397
+ title && { t: 'h5', a: { class: 'bw-modal-title' }, c: title },
4398
+ closeButton && {
4399
+ t: 'button',
4400
+ a: {
4401
+ type: 'button',
4402
+ class: 'bw-close',
4403
+ 'aria-label': 'Close',
4404
+ onclick: function(e) { closeModal(e.target); }
4405
+ },
4406
+ c: '\u00D7'
4407
+ }
4408
+ ].filter(Boolean)
4409
+ },
4410
+ content && {
4411
+ t: 'div',
4412
+ a: { class: 'bw-modal-body' },
4413
+ c: content
4414
+ },
4415
+ footer && {
4416
+ t: 'div',
4417
+ a: { class: 'bw-modal-footer' },
4418
+ c: footer
4419
+ }
4420
+ ].filter(Boolean)
4421
+ }
4422
+ },
4423
+ o: {
4424
+ type: 'modal',
4425
+ mounted: function(el) {
4426
+ // Click backdrop to close
4427
+ el.addEventListener('click', function(e) {
4428
+ if (e.target === el) closeModal(el);
4429
+ });
4430
+ // Escape key to close
4431
+ var escHandler = function(e) {
4432
+ if (e.key === 'Escape' && el.classList.contains('bw-modal-show')) {
4433
+ closeModal(el);
4434
+ }
4435
+ };
4436
+ document.addEventListener('keydown', escHandler);
4437
+ el._bw_escHandler = escHandler;
4438
+ },
4439
+ unmount: function(el) {
4440
+ if (el._bw_escHandler) {
4441
+ document.removeEventListener('keydown', el._bw_escHandler);
4442
+ }
4443
+ document.body.style.overflow = '';
4444
+ }
4445
+ }
4446
+ };
4447
+ }
4448
+
4449
+ /**
4450
+ * Create a toast notification popup
4451
+ *
4452
+ * @param {Object} [props] - Toast configuration
4453
+ * @param {string} [props.title] - Toast title
4454
+ * @param {string|Object|Array} [props.content] - Toast body content
4455
+ * @param {string} [props.variant="info"] - Color variant ("primary", "success", "danger", "warning", "info")
4456
+ * @param {boolean} [props.autoDismiss=true] - Auto-dismiss after delay
4457
+ * @param {number} [props.delay=5000] - Auto-dismiss delay in ms
4458
+ * @param {string} [props.position="top-right"] - Container position
4459
+ * @param {string} [props.className] - Additional CSS classes
4460
+ * @returns {Object} TACO object representing a toast
4461
+ * @category Component Builders
4462
+ * @example
4463
+ * const toast = makeToast({
4464
+ * title: "Success",
4465
+ * content: "File saved!",
4466
+ * variant: "success"
4467
+ * });
4468
+ */
4469
+ function makeToast(props = {}) {
4470
+ const {
4471
+ title,
4472
+ content,
4473
+ variant = 'info',
4474
+ autoDismiss = true,
4475
+ delay = 5000,
4476
+ position = 'top-right',
4477
+ className = ''
4478
+ } = props;
4479
+
4480
+ return {
4481
+ t: 'div',
4482
+ a: {
4483
+ class: `bw-toast bw-toast-${variant} ${className}`.trim(),
4484
+ role: 'alert',
4485
+ 'data-position': position
4486
+ },
4487
+ c: [
4488
+ (title) && {
4489
+ t: 'div',
4490
+ a: { class: 'bw-toast-header' },
4491
+ c: [
4492
+ { t: 'strong', c: title },
4493
+ {
4494
+ t: 'button',
4495
+ a: {
4496
+ type: 'button',
4497
+ class: 'bw-close',
4498
+ 'aria-label': 'Close',
4499
+ onclick: function(e) {
4500
+ var toast = e.target.closest('.bw-toast');
4501
+ if (toast) {
4502
+ toast.classList.add('bw-toast-hiding');
4503
+ setTimeout(function() { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
4504
+ }
4505
+ }
4506
+ },
4507
+ c: '\u00D7'
4508
+ }
4509
+ ]
4510
+ },
4511
+ content && {
4512
+ t: 'div',
4513
+ a: { class: 'bw-toast-body' },
4514
+ c: content
4515
+ }
4516
+ ].filter(Boolean),
4517
+ o: {
4518
+ type: 'toast',
4519
+ mounted: function(el) {
4520
+ // Trigger show animation
4521
+ requestAnimationFrame(function() {
4522
+ el.classList.add('bw-toast-show');
4523
+ });
4524
+ // Auto-dismiss
4525
+ if (autoDismiss) {
4526
+ setTimeout(function() {
4527
+ el.classList.add('bw-toast-hiding');
4528
+ setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 300);
4529
+ }, delay);
4530
+ }
4531
+ }
4532
+ }
4533
+ };
4534
+ }
4535
+
4536
+ // =========================================================================
4537
+ // Phase 3: Essential Modern
4538
+ // =========================================================================
4539
+
4540
+ /**
4541
+ * Create a dropdown menu triggered by a button
4542
+ *
4543
+ * @param {Object} [props] - Dropdown configuration
4544
+ * @param {string|Object} [props.trigger] - Button text or TACO for the trigger
4545
+ * @param {Array<Object>} [props.items=[]] - Menu items
4546
+ * @param {string} [props.items[].text] - Item display text
4547
+ * @param {string} [props.items[].href] - Item link URL
4548
+ * @param {Function} [props.items[].onclick] - Item click handler
4549
+ * @param {boolean} [props.items[].divider] - Render as a divider line
4550
+ * @param {boolean} [props.items[].disabled] - Whether the item is disabled
4551
+ * @param {string} [props.align="start"] - Menu alignment ("start" or "end")
4552
+ * @param {string} [props.variant="primary"] - Trigger button variant
4553
+ * @param {string} [props.className] - Additional CSS classes
4554
+ * @returns {Object} TACO object representing a dropdown
4555
+ * @category Component Builders
4556
+ * @example
4557
+ * const dropdown = makeDropdown({
4558
+ * trigger: "Actions",
4559
+ * items: [
4560
+ * { text: "Edit", onclick: () => edit() },
4561
+ * { divider: true },
4562
+ * { text: "Delete", onclick: () => del() }
4563
+ * ]
4564
+ * });
4565
+ */
4566
+ function makeDropdown(props = {}) {
4567
+ const {
4568
+ trigger,
4569
+ items = [],
4570
+ align = 'start',
4571
+ variant = 'primary',
4572
+ className = ''
4573
+ } = props;
4574
+
4575
+ var triggerTaco;
4576
+ if (typeof trigger === 'string' || trigger === undefined) {
4577
+ triggerTaco = {
4578
+ t: 'button',
4579
+ a: {
4580
+ class: `bw-btn bw-btn-${variant} bw-dropdown-toggle`,
4581
+ type: 'button',
4582
+ onclick: function(e) {
4583
+ var dropdown = e.target.closest('.bw-dropdown');
4584
+ var menu = dropdown.querySelector('.bw-dropdown-menu');
4585
+ menu.classList.toggle('bw-dropdown-show');
4586
+ }
4587
+ },
4588
+ c: trigger || 'Dropdown'
4589
+ };
4590
+ } else {
4591
+ triggerTaco = trigger;
4592
+ }
4593
+
4594
+ return {
4595
+ t: 'div',
4596
+ a: { class: `bw-dropdown ${className}`.trim() },
4597
+ c: [
4598
+ triggerTaco,
4599
+ {
4600
+ t: 'div',
4601
+ a: { class: `bw-dropdown-menu ${align === 'end' ? 'bw-dropdown-menu-end' : ''}`.trim() },
4602
+ c: items.map(function(item) {
4603
+ if (item.divider) {
4604
+ return { t: 'hr', a: { class: 'bw-dropdown-divider' } };
4605
+ }
4606
+ return {
4607
+ t: 'a',
4608
+ a: {
4609
+ class: `bw-dropdown-item ${item.disabled ? 'disabled' : ''}`.trim(),
4610
+ href: item.href || '#',
4611
+ onclick: item.disabled ? undefined : function(e) {
4612
+ if (!item.href) e.preventDefault();
4613
+ var dropdown = e.target.closest('.bw-dropdown');
4614
+ var menu = dropdown.querySelector('.bw-dropdown-menu');
4615
+ menu.classList.remove('bw-dropdown-show');
4616
+ if (item.onclick) item.onclick(e);
4617
+ }
4618
+ },
4619
+ c: item.text
4620
+ };
4621
+ })
4622
+ }
4623
+ ],
4624
+ o: {
4625
+ type: 'dropdown',
4626
+ mounted: function(el) {
4627
+ // Click outside to close
4628
+ var outsideHandler = function(e) {
4629
+ if (!el.contains(e.target)) {
4630
+ var menu = el.querySelector('.bw-dropdown-menu');
4631
+ if (menu) menu.classList.remove('bw-dropdown-show');
4632
+ }
4633
+ };
4634
+ document.addEventListener('click', outsideHandler);
4635
+ el._bw_outsideHandler = outsideHandler;
4636
+ },
4637
+ unmount: function(el) {
4638
+ if (el._bw_outsideHandler) {
4639
+ document.removeEventListener('click', el._bw_outsideHandler);
4640
+ }
4641
+ }
4642
+ }
4643
+ };
4644
+ }
4645
+
4646
+ /**
4647
+ * Create a toggle switch (styled checkbox)
4648
+ *
4649
+ * @param {Object} [props] - Switch configuration
4650
+ * @param {string} [props.label] - Switch label text
4651
+ * @param {boolean} [props.checked=false] - Whether the switch is on
4652
+ * @param {string} [props.id] - Element ID (links label to switch)
4653
+ * @param {string} [props.name] - Input name attribute
4654
+ * @param {boolean} [props.disabled=false] - Whether the switch is disabled
4655
+ * @param {string} [props.className] - Additional CSS classes
4656
+ * @returns {Object} TACO object representing a toggle switch
4657
+ * @category Component Builders
4658
+ * @example
4659
+ * const toggle = makeSwitch({
4660
+ * label: "Dark mode",
4661
+ * checked: false,
4662
+ * onchange: (e) => toggleDark(e.target.checked)
4663
+ * });
4664
+ */
4665
+ function makeSwitch(props = {}) {
4666
+ const {
4667
+ label,
4668
+ checked = false,
4669
+ id,
4670
+ name,
4671
+ disabled = false,
4672
+ className = '',
4673
+ ...eventHandlers
4674
+ } = props;
4675
+
4676
+ return {
4677
+ t: 'div',
4678
+ a: { class: `bw-form-check bw-form-switch ${className}`.trim() },
4679
+ c: [
4680
+ {
4681
+ t: 'input',
4682
+ a: {
4683
+ type: 'checkbox',
4684
+ class: 'bw-form-check-input bw-switch-input',
4685
+ role: 'switch',
4686
+ checked,
4687
+ id,
4688
+ name,
4689
+ disabled,
4690
+ ...eventHandlers
4691
+ }
4692
+ },
4693
+ label && {
4694
+ t: 'label',
4695
+ a: { class: 'bw-form-check-label', for: id },
4696
+ c: label
4697
+ }
4698
+ ].filter(Boolean)
4699
+ };
4700
+ }
4701
+
4702
+ /**
4703
+ * Create a skeleton loading placeholder
4704
+ *
4705
+ * @param {Object} [props] - Skeleton configuration
4706
+ * @param {string} [props.variant="text"] - Shape variant ("text", "circle", "rect")
4707
+ * @param {string} [props.width] - Custom width (e.g. "200px", "100%")
4708
+ * @param {string} [props.height] - Custom height (e.g. "20px")
4709
+ * @param {number} [props.count=1] - Number of skeleton lines (for text variant)
4710
+ * @param {string} [props.className] - Additional CSS classes
4711
+ * @returns {Object} TACO object representing a skeleton placeholder
4712
+ * @category Component Builders
4713
+ * @example
4714
+ * const skeleton = makeSkeleton({ variant: "text", count: 3, width: "100%" });
4715
+ */
4716
+ function makeSkeleton(props = {}) {
4717
+ const {
4718
+ variant = 'text',
4719
+ width,
4720
+ height,
4721
+ count = 1,
4722
+ className = ''
4723
+ } = props;
4724
+
4725
+ if (variant === 'circle') {
4726
+ var circleSize = width || height || '3rem';
4727
+ return {
4728
+ t: 'div',
4729
+ a: {
4730
+ class: `bw-skeleton bw-skeleton-circle ${className}`.trim(),
4731
+ style: { width: circleSize, height: circleSize }
4732
+ }
4733
+ };
4734
+ }
4735
+
4736
+ if (variant === 'rect') {
4737
+ return {
4738
+ t: 'div',
4739
+ a: {
4740
+ class: `bw-skeleton bw-skeleton-rect ${className}`.trim(),
4741
+ style: {
4742
+ width: width || '100%',
4743
+ height: height || '120px'
4744
+ }
4745
+ }
4746
+ };
4747
+ }
4748
+
4749
+ // Text variant — multiple lines
4750
+ if (count === 1) {
4751
+ return {
4752
+ t: 'div',
4753
+ a: {
4754
+ class: `bw-skeleton bw-skeleton-text ${className}`.trim(),
4755
+ style: {
4756
+ width: width || '100%',
4757
+ height: height || '1em'
4758
+ }
4759
+ }
4760
+ };
4761
+ }
3462
4762
 
3463
- this.children.tabPanes.forEach((pane, i) => {
3464
- if (i === index) {
3465
- pane.classList.add('active');
3466
- } else {
3467
- pane.classList.remove('active');
4763
+ var lines = [];
4764
+ for (var i = 0; i < count; i++) {
4765
+ lines.push({
4766
+ t: 'div',
4767
+ a: {
4768
+ class: 'bw-skeleton bw-skeleton-text',
4769
+ style: {
4770
+ width: i === count - 1 ? '75%' : (width || '100%'),
4771
+ height: height || '1em'
4772
+ }
3468
4773
  }
3469
4774
  });
4775
+ }
3470
4776
 
3471
- this.state.activeIndex = index;
3472
- return this;
4777
+ return {
4778
+ t: 'div',
4779
+ a: { class: `bw-skeleton-group ${className}`.trim() },
4780
+ c: lines
4781
+ };
4782
+ }
4783
+
4784
+ /**
4785
+ * Create a user avatar with image or initials fallback
4786
+ *
4787
+ * @param {Object} [props] - Avatar configuration
4788
+ * @param {string} [props.src] - Image source URL
4789
+ * @param {string} [props.alt] - Image alt text
4790
+ * @param {string} [props.initials] - Fallback initials (e.g. "JD")
4791
+ * @param {string} [props.size="md"] - Size ("sm", "md", "lg", "xl")
4792
+ * @param {string} [props.variant="primary"] - Background color variant for initials
4793
+ * @param {string} [props.className] - Additional CSS classes
4794
+ * @returns {Object} TACO object representing an avatar
4795
+ * @category Component Builders
4796
+ * @example
4797
+ * const avatar = makeAvatar({ src: "/photo.jpg", alt: "Jane Doe", size: "lg" });
4798
+ * const avatarInitials = makeAvatar({ initials: "JD", variant: "success" });
4799
+ */
4800
+ function makeAvatar(props = {}) {
4801
+ const {
4802
+ src,
4803
+ alt = '',
4804
+ initials,
4805
+ size = 'md',
4806
+ variant = 'primary',
4807
+ className = ''
4808
+ } = props;
4809
+
4810
+ if (src) {
4811
+ return {
4812
+ t: 'img',
4813
+ a: {
4814
+ class: `bw-avatar bw-avatar-${size} ${className}`.trim(),
4815
+ src: src,
4816
+ alt: alt
4817
+ }
4818
+ };
3473
4819
  }
4820
+
4821
+ return {
4822
+ t: 'div',
4823
+ a: {
4824
+ class: `bw-avatar bw-avatar-${size} bw-avatar-${variant} ${className}`.trim()
4825
+ },
4826
+ c: initials || ''
4827
+ };
3474
4828
  }
3475
4829
 
3476
4830
  /**
3477
- * Create a code demo component for documentation pages
3478
- *
3479
- * Displays a live result alongside source code in a tabbed interface.
3480
- * Includes a copy-to-clipboard button on the code tab.
3481
- *
3482
- * @param {Object} [props] - Code demo configuration
3483
- * @param {string} [props.title] - Demo title heading
3484
- * @param {string} [props.description] - Demo description text
3485
- * @param {string} [props.code] - Source code to display (adds a "Code" tab when present)
3486
- * @param {string|Object|Array} [props.result] - Live result content for the "Result" tab
3487
- * @param {string} [props.language="javascript"] - Code language for syntax class
3488
- * @returns {Object} TACO object representing a code demo with tabbed Result/Code views
4831
+ * Create a carousel/slideshow component with slide transitions
4832
+ *
4833
+ * Supports image slides, TACO content slides, captions, prev/next controls,
4834
+ * dot indicators, and optional auto-play. Uses CSS translateX transitions.
4835
+ *
4836
+ * @param {Object} [props] - Carousel configuration
4837
+ * @param {Array<Object>} [props.items=[]] - Slide items
4838
+ * @param {string|Object} props.items[].content - Slide content (TACO, string, or img element)
4839
+ * @param {string} [props.items[].caption] - Caption text shown at bottom of slide
4840
+ * @param {boolean} [props.showControls=true] - Show prev/next arrow buttons
4841
+ * @param {boolean} [props.showIndicators=true] - Show dot navigation
4842
+ * @param {boolean} [props.autoPlay=false] - Auto-advance slides
4843
+ * @param {number} [props.interval=5000] - Auto-advance interval in ms
4844
+ * @param {string} [props.height='300px'] - Carousel height
4845
+ * @param {number} [props.startIndex=0] - Initial slide index
4846
+ * @param {string} [props.className] - Additional CSS classes
4847
+ * @returns {Object} TACO object representing a carousel
3489
4848
  * @category Component Builders
3490
4849
  * @example
3491
- * const demo = makeCodeDemo({
3492
- * title: "Button Example",
3493
- * description: "A simple primary button",
3494
- * code: 'makeButton({ text: "Click me" })',
3495
- * result: makeButton({ text: "Click me" })
4850
+ * const carousel = makeCarousel({
4851
+ * items: [
4852
+ * { content: { t: 'img', a: { src: 'photo.jpg' } }, caption: 'Photo 1' },
4853
+ * { content: { t: 'div', c: 'Text slide' } }
4854
+ * ],
4855
+ * autoPlay: true,
4856
+ * interval: 3000
3496
4857
  * });
3497
4858
  */
3498
- function makeCodeDemo(props = {}) {
4859
+ function makeCarousel(props = {}) {
3499
4860
  const {
3500
- title,
3501
- description,
3502
- code,
3503
- result,
3504
- language = 'javascript'
4861
+ items = [],
4862
+ showControls = true,
4863
+ showIndicators = true,
4864
+ autoPlay = false,
4865
+ interval = 5000,
4866
+ height = '300px',
4867
+ startIndex = 0,
4868
+ className = ''
3505
4869
  } = props;
3506
4870
 
3507
- // Generate unique ID for this demo
3508
- `demo-${Math.random().toString(36).substr(2, 9)}`;
4871
+ // Shared navigation logic
4872
+ function goToSlide(carouselEl, index) {
4873
+ var total = carouselEl.querySelectorAll('.bw-carousel-slide').length;
4874
+ if (index < 0) index = total - 1;
4875
+ if (index >= total) index = 0;
4876
+ carouselEl.setAttribute('data-carousel-index', index);
4877
+ var track = carouselEl.querySelector('.bw-carousel-track');
4878
+ track.style.transform = 'translateX(-' + (index * 100) + '%)';
4879
+ // Update indicators
4880
+ var indicators = carouselEl.querySelectorAll('.bw-carousel-indicator');
4881
+ for (var i = 0; i < indicators.length; i++) {
4882
+ if (i === index) {
4883
+ indicators[i].classList.add('active');
4884
+ } else {
4885
+ indicators[i].classList.remove('active');
4886
+ }
4887
+ }
4888
+ }
3509
4889
 
3510
- const tabs = [
4890
+ // Arrow SVGs (inline data URIs, same pattern as accordion chevrons)
4891
+ var prevArrow = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e";
4892
+ var nextArrow = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e";
4893
+
4894
+ var slides = items.map(function(item) {
4895
+ var slideContent = [
4896
+ item.content,
4897
+ item.caption && {
4898
+ t: 'div',
4899
+ a: { class: 'bw-carousel-caption' },
4900
+ c: item.caption
4901
+ }
4902
+ ].filter(Boolean);
4903
+
4904
+ return {
4905
+ t: 'div',
4906
+ a: { class: 'bw-carousel-slide' },
4907
+ c: slideContent.length === 1 ? slideContent[0] : slideContent
4908
+ };
4909
+ });
4910
+
4911
+ var children = [
4912
+ // Track
3511
4913
  {
3512
- label: 'Result',
3513
- active: true,
3514
- content: result
4914
+ t: 'div',
4915
+ a: {
4916
+ class: 'bw-carousel-track',
4917
+ style: 'transform: translateX(-' + (startIndex * 100) + '%)'
4918
+ },
4919
+ c: slides
3515
4920
  }
3516
4921
  ];
3517
4922
 
3518
- // Only add Code tab if code is provided
3519
- if (code) {
3520
- tabs.push({
3521
- label: 'Code',
3522
- content: {
3523
- t: 'div',
3524
- a: { style: 'position: relative;' },
3525
- c: [
3526
- {
3527
- t: 'button',
3528
- a: {
3529
- class: 'bw-copy-btn',
3530
- style: 'position: absolute; top: 0.5rem; right: 0.5rem; padding: 0.25rem 0.625rem; font-size: 0.6875rem; background: rgba(255,255,255,0.12); color: #aaa; border: 1px solid rgba(255,255,255,0.15); border-radius: 4px; cursor: pointer; font-family: inherit; transition: all 0.15s;',
3531
- onclick: (e) => {
3532
- navigator.clipboard.writeText(code).then(() => {
3533
- const btn = e.target;
3534
- const originalText = btn.textContent;
3535
- btn.textContent = 'Copied!';
3536
- btn.style.background = '#006666';
3537
- btn.style.color = '#fff';
3538
- setTimeout(() => {
3539
- btn.textContent = originalText;
3540
- btn.style.background = 'rgba(255,255,255,0.12)';
3541
- btn.style.color = '#aaa';
3542
- }, 2000);
3543
- });
3544
- }
3545
- },
3546
- c: 'Copy'
3547
- },
3548
- {
3549
- t: 'pre',
3550
- a: {
3551
- style: 'margin: 0; background: #1e293b; border: none; border-radius: 6px; overflow-x: auto;'
3552
- },
3553
- c: {
3554
- t: 'code',
3555
- a: {
3556
- class: `language-${language}`,
3557
- style: 'display: block; padding: 1.25rem; font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace; font-size: 0.8125rem; line-height: 1.6; color: #e2e8f0;'
3558
- },
3559
- c: code
4923
+ // Prev/Next controls
4924
+ if (showControls && items.length > 1) {
4925
+ children.push({
4926
+ t: 'button',
4927
+ a: {
4928
+ class: 'bw-carousel-control bw-carousel-control-prev',
4929
+ type: 'button',
4930
+ 'aria-label': 'Previous slide',
4931
+ onclick: function(e) {
4932
+ var carousel = e.target.closest('.bw-carousel');
4933
+ var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
4934
+ goToSlide(carousel, idx - 1);
4935
+ }
4936
+ },
4937
+ c: { t: 'img', a: { src: prevArrow, alt: '', role: 'presentation' } }
4938
+ });
4939
+ children.push({
4940
+ t: 'button',
4941
+ a: {
4942
+ class: 'bw-carousel-control bw-carousel-control-next',
4943
+ type: 'button',
4944
+ 'aria-label': 'Next slide',
4945
+ onclick: function(e) {
4946
+ var carousel = e.target.closest('.bw-carousel');
4947
+ var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
4948
+ goToSlide(carousel, idx + 1);
4949
+ }
4950
+ },
4951
+ c: { t: 'img', a: { src: nextArrow, alt: '', role: 'presentation' } }
4952
+ });
4953
+ }
4954
+
4955
+ // Indicators
4956
+ if (showIndicators && items.length > 1) {
4957
+ children.push({
4958
+ t: 'div',
4959
+ a: { class: 'bw-carousel-indicators' },
4960
+ c: items.map(function(_, i) {
4961
+ return {
4962
+ t: 'button',
4963
+ a: {
4964
+ class: 'bw-carousel-indicator' + (i === startIndex ? ' active' : ''),
4965
+ type: 'button',
4966
+ 'aria-label': 'Go to slide ' + (i + 1),
4967
+ 'data-slide-index': i,
4968
+ onclick: function(e) {
4969
+ var carousel = e.target.closest('.bw-carousel');
4970
+ var idx = parseInt(e.target.getAttribute('data-slide-index'));
4971
+ goToSlide(carousel, idx);
3560
4972
  }
3561
4973
  }
3562
- ]
3563
- }
4974
+ };
4975
+ })
3564
4976
  });
3565
4977
  }
3566
4978
 
3567
- const content = [
3568
- title && { t: 'h3', c: title },
3569
- description && {
3570
- t: 'p',
3571
- a: { style: 'color: #6c757d; margin-bottom: 1rem;' },
3572
- c: description
3573
- },
3574
- makeTabs({ tabs})
3575
- ].filter(Boolean);
3576
-
3577
4979
  return {
3578
4980
  t: 'div',
3579
- a: { class: 'bw-code-demo' },
3580
- c: content
4981
+ a: {
4982
+ class: ('bw-carousel ' + className).trim(),
4983
+ style: 'height: ' + height,
4984
+ 'data-carousel-index': startIndex
4985
+ },
4986
+ c: children,
4987
+ o: {
4988
+ type: 'carousel',
4989
+ state: { activeIndex: startIndex, autoPlay: autoPlay, interval: interval },
4990
+ mounted: autoPlay ? function(el) {
4991
+ var intervalId = setInterval(function() {
4992
+ var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
4993
+ goToSlide(el, idx + 1);
4994
+ }, interval);
4995
+ el._bw_carouselInterval = intervalId;
4996
+ } : undefined,
4997
+ unmount: autoPlay ? function(el) {
4998
+ if (el._bw_carouselInterval) {
4999
+ clearInterval(el._bw_carouselInterval);
5000
+ }
5001
+ } : undefined
5002
+ }
3581
5003
  };
3582
5004
  }
3583
5005
 
3584
- /**
3585
- * Registry mapping component type names to their handle classes
3586
- *
3587
- * Used by bw.createCard(), bw.createTable(), etc. to wrap rendered
3588
- * DOM elements in the appropriate imperative handle.
3589
- *
3590
- * @type {Object.<string, Function>}
3591
- */
3592
5006
  const componentHandles = {
3593
5007
  card: CardHandle,
3594
5008
  table: TableHandle,
3595
5009
  navbar: NavbarHandle,
3596
- tabs: TabsHandle
5010
+ tabs: TabsHandle,
5011
+ modal: ModalHandle
3597
5012
  };
3598
5013
 
3599
5014
  var components = /*#__PURE__*/Object.freeze({
3600
5015
  __proto__: null,
3601
5016
  CardHandle: CardHandle,
5017
+ ModalHandle: ModalHandle,
3602
5018
  NavbarHandle: NavbarHandle,
3603
5019
  TableHandle: TableHandle,
3604
5020
  TabsHandle: TabsHandle,
3605
5021
  componentHandles: componentHandles,
5022
+ makeAccordion: makeAccordion,
3606
5023
  makeAlert: makeAlert,
5024
+ makeAvatar: makeAvatar,
3607
5025
  makeBadge: makeBadge,
3608
5026
  makeBreadcrumb: makeBreadcrumb,
3609
5027
  makeButton: makeButton,
5028
+ makeButtonGroup: makeButtonGroup,
3610
5029
  makeCTA: makeCTA,
3611
5030
  makeCard: makeCard,
5031
+ makeCarousel: makeCarousel,
3612
5032
  makeCheckbox: makeCheckbox,
3613
5033
  makeCodeDemo: makeCodeDemo,
3614
5034
  makeCol: makeCol,
3615
5035
  makeContainer: makeContainer,
5036
+ makeDropdown: makeDropdown,
3616
5037
  makeFeatureGrid: makeFeatureGrid,
3617
5038
  makeForm: makeForm,
3618
5039
  makeFormGroup: makeFormGroup,
3619
5040
  makeHero: makeHero,
3620
5041
  makeInput: makeInput,
3621
5042
  makeListGroup: makeListGroup,
5043
+ makeModal: makeModal,
3622
5044
  makeNav: makeNav,
3623
5045
  makeNavbar: makeNavbar,
5046
+ makePagination: makePagination,
3624
5047
  makeProgress: makeProgress,
5048
+ makeRadio: makeRadio,
3625
5049
  makeRow: makeRow,
3626
5050
  makeSection: makeSection,
3627
5051
  makeSelect: makeSelect,
5052
+ makeSkeleton: makeSkeleton,
3628
5053
  makeSpinner: makeSpinner,
3629
5054
  makeStack: makeStack,
5055
+ makeSwitch: makeSwitch,
3630
5056
  makeTabs: makeTabs,
3631
- makeTextarea: makeTextarea
5057
+ makeTextarea: makeTextarea,
5058
+ makeToast: makeToast
3632
5059
  });
3633
5060
 
3634
5061
  /**
@@ -4051,6 +5478,26 @@
4051
5478
  return str.replace(/[&<>"'/]/g, (char) => escapeMap[char]);
4052
5479
  };
4053
5480
 
5481
+ /**
5482
+ * Mark a string as raw HTML so it will not be escaped by bw.html() or bw.createDOM().
5483
+ *
5484
+ * By default, bitwrench escapes all text content to prevent XSS. Use bw.raw()
5485
+ * when you need to embed pre-sanitized HTML, entities, or inline markup.
5486
+ *
5487
+ * @param {string} str - HTML string to mark as raw
5488
+ * @returns {Object} Marked object recognized by bw.html() and bw.createDOM()
5489
+ * @category DOM Generation
5490
+ * @see bw.escapeHTML
5491
+ * @see bw.html
5492
+ * @example
5493
+ * bw.raw('Hello &mdash; World')
5494
+ * // Used in TACO content:
5495
+ * { t: 'p', c: bw.raw('Price: <strong>$9.99</strong>') }
5496
+ */
5497
+ bw.raw = function(str) {
5498
+ return { __bw_raw: true, v: String(str) };
5499
+ };
5500
+
4054
5501
  /**
4055
5502
  * Normalize CSS class names by converting underscores to hyphens for bw-prefixed classes.
4056
5503
  *
@@ -4102,6 +5549,11 @@
4102
5549
  return taco.map(t => bw.html(t, options)).join('');
4103
5550
  }
4104
5551
 
5552
+ // Handle bw.raw() marked content
5553
+ if (taco && taco.__bw_raw) {
5554
+ return taco.v;
5555
+ }
5556
+
4105
5557
  // Handle primitives and non-TACO objects
4106
5558
  if (typeof taco !== 'object' || !taco.t) {
4107
5559
  return options.raw ? String(taco) : bw.escapeHTML(String(taco));
@@ -4202,12 +5654,21 @@
4202
5654
 
4203
5655
  // Handle null/undefined
4204
5656
  if (taco == null) return document.createTextNode('');
4205
-
5657
+
5658
+ // Handle bw.raw() marked content — inject as HTML
5659
+ if (taco && taco.__bw_raw) {
5660
+ var frag = document.createDocumentFragment();
5661
+ var tmp = document.createElement('span');
5662
+ tmp.innerHTML = taco.v;
5663
+ while (tmp.firstChild) frag.appendChild(tmp.firstChild);
5664
+ return frag;
5665
+ }
5666
+
4206
5667
  // Handle text nodes
4207
5668
  if (typeof taco !== 'object' || !taco.t) {
4208
5669
  return document.createTextNode(String(taco));
4209
5670
  }
4210
-
5671
+
4211
5672
  const { t: tag, a: attrs = {}, c: content, o: opts = {} } = taco;
4212
5673
 
4213
5674
  // Create element
@@ -4272,6 +5733,9 @@
4272
5733
  }
4273
5734
  }
4274
5735
  });
5736
+ } else if (typeof content === 'object' && content.__bw_raw) {
5737
+ // Raw HTML content — inject via innerHTML
5738
+ el.innerHTML = content.v;
4275
5739
  } else if (typeof content === 'object' && content.t) {
4276
5740
  var childEl = bw.createDOM(content, options);
4277
5741
  el.appendChild(childEl);
@@ -4770,6 +6234,16 @@
4770
6234
  if (attr) {
4771
6235
  // Patch an attribute
4772
6236
  el.setAttribute(attr, String(content));
6237
+ } else if (Array.isArray(content)) {
6238
+ // Patch with array of children (strings and/or TACOs)
6239
+ el.innerHTML = '';
6240
+ content.forEach(function(item) {
6241
+ if (typeof item === 'string' || typeof item === 'number') {
6242
+ el.appendChild(document.createTextNode(String(item)));
6243
+ } else if (item && item.t) {
6244
+ el.appendChild(bw.createDOM(item));
6245
+ }
6246
+ });
4773
6247
  } else if (typeof content === 'object' && content !== null && content.t) {
4774
6248
  // Patch with a TACO — replace children
4775
6249
  el.innerHTML = '';
@@ -5966,6 +7440,7 @@
5966
7440
  * @see bw.makeTable
5967
7441
  */
5968
7442
  bw.htmlTable = function(data, opts = {}) {
7443
+ console.warn('bw.htmlTable() is deprecated. Use bw.makeTableFromArray() for TACO output or bw.makeTable() for object-array data.');
5969
7444
  if (bw.typeOf(data) !== "array" || data.length < 1) return "";
5970
7445
 
5971
7446
  const dopts = {
@@ -6065,6 +7540,7 @@
6065
7540
  * @see bw.makeTabs
6066
7541
  */
6067
7542
  bw.htmlTabs = function(tabData, opts = {}) {
7543
+ console.warn('bw.htmlTabs() is deprecated. Use bw.makeTabs() instead.');
6068
7544
  if (bw.typeOf(tabData) !== "array" || tabData.length < 1) return "";
6069
7545
 
6070
7546
  const dopts = {
@@ -6111,6 +7587,7 @@
6111
7587
  * @category Legacy (v1)
6112
7588
  */
6113
7589
  bw.selectTabContent = function(tabElement) {
7590
+ console.warn('bw.selectTabContent() is deprecated. Use bw.makeTabs() instead.');
6114
7591
  if (!bw._isBrowser || !tabElement) return;
6115
7592
 
6116
7593
  const container = tabElement.closest(".bw-tab-container");
@@ -6639,12 +8116,21 @@
6639
8116
  const {
6640
8117
  data = [],
6641
8118
  columns,
6642
- className = "table",
8119
+ className = '',
8120
+ striped = false,
8121
+ hover = false,
6643
8122
  sortable = true,
6644
8123
  onSort,
6645
8124
  sortColumn,
6646
8125
  sortDirection = 'asc'
6647
8126
  } = config;
8127
+
8128
+ // Build class list: always include bw-table, add striped/hover, append user className
8129
+ let cls = 'bw-table';
8130
+ if (striped) cls += ' bw-table-striped';
8131
+ if (hover) cls += ' bw-table-hover';
8132
+ if (className) cls += ' ' + className;
8133
+ cls = cls.trim();
6648
8134
 
6649
8135
  // Auto-detect columns if not provided
6650
8136
  const cols = columns || (data.length > 0
@@ -6732,11 +8218,176 @@
6732
8218
 
6733
8219
  return {
6734
8220
  t: 'table',
6735
- a: { class: className },
8221
+ a: { class: cls },
6736
8222
  c: [thead, tbody]
6737
8223
  };
6738
8224
  };
6739
8225
 
8226
+ /**
8227
+ * Create a table from a 2D array.
8228
+ *
8229
+ * Converts a 2D array into the object-array format that `bw.makeTable()`
8230
+ * expects, then delegates. By default, the first row is used as column
8231
+ * headers. All standard `makeTable` props (striped, hover, sortable,
8232
+ * columns, onSort, etc.) are passed through.
8233
+ *
8234
+ * @param {Object} config - Configuration object
8235
+ * @param {Array<Array>} config.data - 2D array of values
8236
+ * @param {boolean} [config.headerRow=true] - Treat first row as column headers
8237
+ * @param {boolean} [config.striped=false] - Striped rows
8238
+ * @param {boolean} [config.hover=false] - Hover highlight
8239
+ * @param {boolean} [config.sortable=true] - Enable sort
8240
+ * @param {Array<Object>} [config.columns] - Override auto-generated column defs
8241
+ * @param {string} [config.className=''] - Additional CSS classes
8242
+ * @param {Function} [config.onSort] - Sort callback
8243
+ * @param {string} [config.sortColumn] - Currently sorted column key
8244
+ * @param {string} [config.sortDirection='asc'] - Sort direction
8245
+ * @returns {Object} TACO object for table
8246
+ * @category Component Builders
8247
+ * @see bw.makeTable
8248
+ * @example
8249
+ * bw.makeTableFromArray({
8250
+ * data: [
8251
+ * ['Name', 'Role', 'Status'],
8252
+ * ['Alice', 'Engineer', 'Active'],
8253
+ * ['Bob', 'Designer', 'Away']
8254
+ * ],
8255
+ * striped: true,
8256
+ * hover: true
8257
+ * });
8258
+ */
8259
+ bw.makeTableFromArray = function(config) {
8260
+ const { data = [], headerRow = true, columns, ...rest } = config;
8261
+
8262
+ if (!Array.isArray(data) || data.length === 0) {
8263
+ return bw.makeTable({ data: [], columns: columns || [], ...rest });
8264
+ }
8265
+
8266
+ // Determine headers
8267
+ let headers;
8268
+ let rows;
8269
+ if (headerRow && data.length > 0) {
8270
+ headers = data[0].map(function(h) { return String(h); });
8271
+ rows = data.slice(1);
8272
+ } else {
8273
+ // Generate col0, col1, ... headers
8274
+ const width = data[0].length;
8275
+ headers = [];
8276
+ for (let i = 0; i < width; i++) {
8277
+ headers.push('col' + i);
8278
+ }
8279
+ rows = data;
8280
+ }
8281
+
8282
+ // Convert rows to object arrays
8283
+ const objData = rows.map(function(row) {
8284
+ const obj = {};
8285
+ headers.forEach(function(key, i) {
8286
+ obj[key] = row[i] !== undefined ? row[i] : '';
8287
+ });
8288
+ return obj;
8289
+ });
8290
+
8291
+ // Auto-generate column defs if not provided
8292
+ const cols = columns || headers.map(function(key) {
8293
+ return { key: key, label: key };
8294
+ });
8295
+
8296
+ return bw.makeTable({ data: objData, columns: cols, ...rest });
8297
+ };
8298
+
8299
+ /**
8300
+ * Create a vertical bar chart from data.
8301
+ *
8302
+ * Renders a pure-CSS bar chart using flexbox and percentage heights.
8303
+ * No canvas, SVG, or external charting library required.
8304
+ *
8305
+ * @param {Object} config - Chart configuration
8306
+ * @param {Array<Object>} config.data - Array of data objects
8307
+ * @param {string} [config.labelKey='label'] - Key for bar labels
8308
+ * @param {string} [config.valueKey='value'] - Key for bar values
8309
+ * @param {string} [config.title] - Chart title
8310
+ * @param {string} [config.color='#006666'] - Bar color (hex or CSS color)
8311
+ * @param {string} [config.height='200px'] - Height of the chart area
8312
+ * @param {Function} [config.formatValue] - Value label formatter: (value) => string
8313
+ * @param {boolean} [config.showValues=true] - Show value labels above bars
8314
+ * @param {boolean} [config.showLabels=true] - Show labels below bars
8315
+ * @param {string} [config.className=''] - Additional CSS classes
8316
+ * @returns {Object} TACO object
8317
+ * @category Component Builders
8318
+ * @example
8319
+ * bw.makeBarChart({
8320
+ * data: [
8321
+ * { label: 'Jan', value: 12400 },
8322
+ * { label: 'Feb', value: 15800 },
8323
+ * { label: 'Mar', value: 9200 }
8324
+ * ],
8325
+ * title: 'Monthly Revenue',
8326
+ * color: '#0077b6',
8327
+ * formatValue: (v) => '$' + (v / 1000).toFixed(1) + 'k'
8328
+ * });
8329
+ */
8330
+ bw.makeBarChart = function(config) {
8331
+ const {
8332
+ data = [],
8333
+ labelKey = 'label',
8334
+ valueKey = 'value',
8335
+ title,
8336
+ color = '#006666',
8337
+ height = '200px',
8338
+ formatValue,
8339
+ showValues = true,
8340
+ showLabels = true,
8341
+ className = ''
8342
+ } = config;
8343
+
8344
+ if (!Array.isArray(data) || data.length === 0) {
8345
+ return { t: 'div', a: { class: ('bw-bar-chart-container ' + className).trim() }, c: '' };
8346
+ }
8347
+
8348
+ const values = data.map(function(d) { return Number(d[valueKey]) || 0; });
8349
+ const maxVal = Math.max.apply(null, values);
8350
+
8351
+ const bars = data.map(function(d, i) {
8352
+ const val = values[i];
8353
+ const pct = maxVal > 0 ? (val / maxVal * 100) : 0;
8354
+ const formatted = formatValue ? formatValue(val) : String(val);
8355
+
8356
+ const children = [];
8357
+ if (showValues) {
8358
+ children.push({ t: 'div', a: { class: 'bw-bar-value' }, c: formatted });
8359
+ }
8360
+ children.push({
8361
+ t: 'div',
8362
+ a: {
8363
+ class: 'bw-bar',
8364
+ style: 'height:' + pct + '%;background:' + color + ';'
8365
+ }
8366
+ });
8367
+ if (showLabels) {
8368
+ children.push({ t: 'div', a: { class: 'bw-bar-label' }, c: String(d[labelKey] || '') });
8369
+ }
8370
+
8371
+ return { t: 'div', a: { class: 'bw-bar-group' }, c: children };
8372
+ });
8373
+
8374
+ const chartChildren = [];
8375
+ if (title) {
8376
+ chartChildren.push({ t: 'h3', a: { class: 'bw-bar-chart-title' }, c: title });
8377
+ }
8378
+ chartChildren.push({
8379
+ t: 'div',
8380
+ a: { class: 'bw-bar-chart', style: 'height:' + height + ';' },
8381
+ c: bars
8382
+ });
8383
+
8384
+ return {
8385
+ t: 'div',
8386
+ a: { class: ('bw-bar-chart-container ' + className).trim() },
8387
+ c: chartChildren
8388
+ };
8389
+ };
8390
+
6740
8391
  /**
6741
8392
  * Create a responsive data table with title and optional wrapper
6742
8393
  *
@@ -6747,7 +8398,9 @@
6747
8398
  * @param {string} [config.title] - Table title heading
6748
8399
  * @param {Array<Object>} config.data - Array of row objects
6749
8400
  * @param {Array<Object>} [config.columns] - Column definitions
6750
- * @param {string} [config.className="table table-striped table-hover"] - Table CSS class
8401
+ * @param {string} [config.className=''] - Additional CSS classes for the table
8402
+ * @param {boolean} [config.striped=true] - Add striped row styling
8403
+ * @param {boolean} [config.hover=true] - Add hover row highlighting
6751
8404
  * @param {boolean} [config.responsive=true] - Wrap table in responsive overflow div
6752
8405
  * @returns {Object} TACO object for table with wrapper
6753
8406
  * @example
@@ -6762,7 +8415,9 @@
6762
8415
  title,
6763
8416
  data,
6764
8417
  columns,
6765
- className = "table table-striped table-hover",
8418
+ className = '',
8419
+ striped = true,
8420
+ hover = true,
6766
8421
  responsive = true,
6767
8422
  ...tableConfig
6768
8423
  } = config;
@@ -6771,6 +8426,8 @@
6771
8426
  data,
6772
8427
  columns,
6773
8428
  className,
8429
+ striped,
8430
+ hover,
6774
8431
  ...tableConfig
6775
8432
  });
6776
8433