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