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