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