bitwrench 2.0.10 → 2.0.11
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 +5777 -0
- package/dist/bitwrench-lean.cjs.min.js +44 -0
- package/dist/bitwrench-lean.es5.js +7383 -0
- package/dist/bitwrench-lean.es5.min.js +13 -0
- package/dist/bitwrench-lean.esm.js +5775 -0
- package/dist/bitwrench-lean.esm.min.js +44 -0
- package/dist/bitwrench-lean.umd.js +5783 -0
- package/dist/bitwrench-lean.umd.min.js +44 -0
- package/dist/bitwrench.cjs.js +1552 -175
- package/dist/bitwrench.cjs.min.js +6 -6
- package/dist/bitwrench.css +1218 -14
- package/dist/bitwrench.es5.js +2454 -662
- package/dist/bitwrench.es5.min.js +4 -4
- package/dist/bitwrench.esm.js +1552 -175
- package/dist/bitwrench.esm.min.js +6 -6
- package/dist/bitwrench.umd.js +1552 -175
- 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 +873 -25
- package/src/bitwrench-lean.js +14 -0
- package/src/bitwrench-styles.js +842 -13
- package/src/bitwrench.js +197 -4
- package/src/version.js +4 -4
package/dist/bitwrench.cjs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bitwrench v2.0.
|
|
1
|
+
/*! bitwrench v2.0.11 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const VERSION_INFO = {
|
|
10
|
-
version: '2.0.
|
|
10
|
+
version: '2.0.11',
|
|
11
11
|
name: 'bitwrench',
|
|
12
12
|
description: 'A library for javascript UI functions.',
|
|
13
13
|
license: 'BSD-2-Clause',
|
|
14
|
-
homepage: '
|
|
14
|
+
homepage: 'https://deftio.github.com/bitwrench/pages',
|
|
15
15
|
repository: 'git+https://github.com/deftio/bitwrench.git',
|
|
16
16
|
author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
|
|
17
|
-
buildDate: '2026-03-
|
|
17
|
+
buildDate: '2026-03-07T11:05:08.522Z'
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -686,7 +686,7 @@ function generateTables(scope, palette, layout) {
|
|
|
686
686
|
'background-color': palette.light.light
|
|
687
687
|
};
|
|
688
688
|
rules[scopeSelector(scope, '.bw-table-striped > tbody > tr:nth-of-type(odd) > *')] = {
|
|
689
|
-
'background-color': 'rgba(0, 0, 0, 0.
|
|
689
|
+
'background-color': 'rgba(0, 0, 0, 0.05)'
|
|
690
690
|
};
|
|
691
691
|
rules[scopeSelector(scope, '.bw-table-hover > tbody > tr:hover > *')] = {
|
|
692
692
|
'background-color': palette.primary.focus
|
|
@@ -880,6 +880,121 @@ function generateSectionsThemed(scope, palette) {
|
|
|
880
880
|
return rules;
|
|
881
881
|
}
|
|
882
882
|
|
|
883
|
+
function generateAccordionThemed(scope, palette) {
|
|
884
|
+
var rules = {};
|
|
885
|
+
rules[scopeSelector(scope, '.bw-accordion-item')] = {
|
|
886
|
+
'background-color': '#fff',
|
|
887
|
+
'border-color': palette.light.border
|
|
888
|
+
};
|
|
889
|
+
rules[scopeSelector(scope, '.bw-accordion-button')] = {
|
|
890
|
+
'color': palette.dark.base
|
|
891
|
+
};
|
|
892
|
+
rules[scopeSelector(scope, '.bw-accordion-button:hover')] = {
|
|
893
|
+
'background-color': palette.light.light
|
|
894
|
+
};
|
|
895
|
+
rules[scopeSelector(scope, '.bw-accordion-body')] = {
|
|
896
|
+
'border-top': '1px solid ' + palette.light.border
|
|
897
|
+
};
|
|
898
|
+
return rules;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function generateModalThemed(scope, palette) {
|
|
902
|
+
var rules = {};
|
|
903
|
+
rules[scopeSelector(scope, '.bw-modal-content')] = {
|
|
904
|
+
'background-color': '#fff',
|
|
905
|
+
'border-color': palette.light.border,
|
|
906
|
+
'box-shadow': '0 0.5rem 1rem rgba(0,0,0,0.15)'
|
|
907
|
+
};
|
|
908
|
+
rules[scopeSelector(scope, '.bw-modal-header')] = {
|
|
909
|
+
'border-bottom-color': palette.light.border
|
|
910
|
+
};
|
|
911
|
+
rules[scopeSelector(scope, '.bw-modal-footer')] = {
|
|
912
|
+
'border-top-color': palette.light.border
|
|
913
|
+
};
|
|
914
|
+
rules[scopeSelector(scope, '.bw-modal-title')] = {
|
|
915
|
+
'color': palette.dark.base
|
|
916
|
+
};
|
|
917
|
+
return rules;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
function generateToastThemed(scope, palette) {
|
|
921
|
+
var rules = {};
|
|
922
|
+
rules[scopeSelector(scope, '.bw-toast')] = {
|
|
923
|
+
'background-color': '#fff',
|
|
924
|
+
'border-color': 'rgba(0,0,0,0.1)',
|
|
925
|
+
'box-shadow': '0 0.5rem 1rem rgba(0,0,0,0.15)'
|
|
926
|
+
};
|
|
927
|
+
rules[scopeSelector(scope, '.bw-toast-header')] = {
|
|
928
|
+
'border-bottom-color': 'rgba(0,0,0,0.05)'
|
|
929
|
+
};
|
|
930
|
+
var variants = ['primary', 'secondary', 'success', 'danger', 'warning', 'info'];
|
|
931
|
+
variants.forEach(function(v) {
|
|
932
|
+
rules[scopeSelector(scope, '.bw-toast-' + v)] = {
|
|
933
|
+
'border-left': '4px solid ' + palette[v].base
|
|
934
|
+
};
|
|
935
|
+
});
|
|
936
|
+
return rules;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function generateDropdownThemed(scope, palette) {
|
|
940
|
+
var rules = {};
|
|
941
|
+
rules[scopeSelector(scope, '.bw-dropdown-menu')] = {
|
|
942
|
+
'background-color': '#fff',
|
|
943
|
+
'border-color': palette.light.border,
|
|
944
|
+
'box-shadow': '0 0.5rem 1rem rgba(0,0,0,0.15)'
|
|
945
|
+
};
|
|
946
|
+
rules[scopeSelector(scope, '.bw-dropdown-item')] = {
|
|
947
|
+
'color': palette.dark.base
|
|
948
|
+
};
|
|
949
|
+
rules[scopeSelector(scope, '.bw-dropdown-item:hover')] = {
|
|
950
|
+
'color': palette.dark.hover,
|
|
951
|
+
'background-color': palette.light.light
|
|
952
|
+
};
|
|
953
|
+
rules[scopeSelector(scope, '.bw-dropdown-item.disabled')] = {
|
|
954
|
+
'color': palette.secondary.base
|
|
955
|
+
};
|
|
956
|
+
rules[scopeSelector(scope, '.bw-dropdown-divider')] = {
|
|
957
|
+
'border-top-color': palette.light.border
|
|
958
|
+
};
|
|
959
|
+
return rules;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function generateSwitchThemed(scope, palette) {
|
|
963
|
+
var rules = {};
|
|
964
|
+
rules[scopeSelector(scope, '.bw-form-switch .bw-switch-input')] = {
|
|
965
|
+
'background-color': palette.secondary.base,
|
|
966
|
+
'border-color': palette.secondary.base
|
|
967
|
+
};
|
|
968
|
+
rules[scopeSelector(scope, '.bw-form-switch .bw-switch-input:checked')] = {
|
|
969
|
+
'background-color': palette.primary.base,
|
|
970
|
+
'border-color': palette.primary.base
|
|
971
|
+
};
|
|
972
|
+
rules[scopeSelector(scope, '.bw-form-switch .bw-switch-input:focus')] = {
|
|
973
|
+
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
974
|
+
};
|
|
975
|
+
return rules;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function generateSkeletonThemed(scope, palette) {
|
|
979
|
+
var rules = {};
|
|
980
|
+
rules[scopeSelector(scope, '.bw-skeleton')] = {
|
|
981
|
+
'background-color': palette.light.border
|
|
982
|
+
};
|
|
983
|
+
return rules;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function generateAvatarThemed(scope, palette) {
|
|
987
|
+
var rules = {};
|
|
988
|
+
var variants = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark'];
|
|
989
|
+
variants.forEach(function(v) {
|
|
990
|
+
rules[scopeSelector(scope, '.bw-avatar-' + v)] = {
|
|
991
|
+
'background-color': palette[v].base,
|
|
992
|
+
'color': palette[v].textOn
|
|
993
|
+
};
|
|
994
|
+
});
|
|
995
|
+
return rules;
|
|
996
|
+
}
|
|
997
|
+
|
|
883
998
|
/**
|
|
884
999
|
* Generate all themed CSS rules from a palette and layout.
|
|
885
1000
|
* Returns a flat CSS rules object (selector → declarations).
|
|
@@ -909,6 +1024,13 @@ function generateThemedCSS(scopeName, palette, layout) {
|
|
|
909
1024
|
generateSpinnerThemed(scopeName, palette),
|
|
910
1025
|
generateCloseButtonThemed(scopeName, palette),
|
|
911
1026
|
generateSectionsThemed(scopeName, palette),
|
|
1027
|
+
generateAccordionThemed(scopeName, palette),
|
|
1028
|
+
generateModalThemed(scopeName, palette),
|
|
1029
|
+
generateToastThemed(scopeName, palette),
|
|
1030
|
+
generateDropdownThemed(scopeName, palette),
|
|
1031
|
+
generateSwitchThemed(scopeName, palette),
|
|
1032
|
+
generateSkeletonThemed(scopeName, palette),
|
|
1033
|
+
generateAvatarThemed(scopeName, palette),
|
|
912
1034
|
generateUtilityColors(scopeName, palette)
|
|
913
1035
|
);
|
|
914
1036
|
}
|
|
@@ -1178,7 +1300,7 @@ function getStructuralStyles() {
|
|
|
1178
1300
|
'position': 'relative', 'display': 'flex', 'flex-wrap': 'wrap',
|
|
1179
1301
|
'align-items': 'center', 'justify-content': 'space-between', 'padding': '0.5rem 1.5rem'
|
|
1180
1302
|
};
|
|
1181
|
-
rules['.bw-navbar > .container'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'space-between' };
|
|
1303
|
+
rules['.bw-navbar > .bw-container, .bw-navbar > .container'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'space-between' };
|
|
1182
1304
|
rules['.bw-navbar-brand'] = {
|
|
1183
1305
|
'display': 'inline-flex', 'align-items': 'center', 'gap': '0.5rem',
|
|
1184
1306
|
'padding-top': '0.25rem', 'padding-bottom': '0.25rem', 'margin-right': '1.5rem',
|
|
@@ -1223,11 +1345,13 @@ function getStructuralStyles() {
|
|
|
1223
1345
|
|
|
1224
1346
|
// Badges (structural)
|
|
1225
1347
|
rules['.bw-badge'] = {
|
|
1226
|
-
'display': 'inline-block', 'padding': '.
|
|
1227
|
-
'font-weight': '
|
|
1348
|
+
'display': 'inline-block', 'padding': '.4em .75em', 'font-size': '.875em',
|
|
1349
|
+
'font-weight': '600', 'line-height': '1.3', 'text-align': 'center',
|
|
1228
1350
|
'white-space': 'nowrap', 'vertical-align': 'baseline', 'border-radius': '.375rem'
|
|
1229
1351
|
};
|
|
1230
1352
|
rules['.bw-badge:empty'] = { 'display': 'none' };
|
|
1353
|
+
rules['.bw-badge-sm'] = { 'font-size': '.75em', 'padding': '.25em .5em' };
|
|
1354
|
+
rules['.bw-badge-lg'] = { 'font-size': '1em', 'padding': '.5em .9em' };
|
|
1231
1355
|
rules['.bw-badge-pill'] = { 'border-radius': '50rem' };
|
|
1232
1356
|
|
|
1233
1357
|
// Progress (structural)
|
|
@@ -1294,7 +1418,8 @@ function getStructuralStyles() {
|
|
|
1294
1418
|
rules['.bw-hero'] = { 'position': 'relative', 'overflow': 'hidden' };
|
|
1295
1419
|
rules['.bw-hero-overlay'] = { 'position': 'absolute', 'top': '0', 'left': '0', 'right': '0', 'bottom': '0', 'z-index': '1' };
|
|
1296
1420
|
rules['.bw-hero-content'] = { 'position': 'relative', 'z-index': '2' };
|
|
1297
|
-
rules['.bw-hero-title'] = { 'font-weight': '300', 'letter-spacing': '-0.05rem' };
|
|
1421
|
+
rules['.bw-hero-title'] = { 'font-weight': '300', 'letter-spacing': '-0.05rem', 'color': 'inherit' };
|
|
1422
|
+
rules['.bw-hero-subtitle'] = { 'color': 'inherit' };
|
|
1298
1423
|
rules['.bw-hero-actions'] = { 'display': 'flex', 'gap': '1rem', 'justify-content': 'center', 'flex-wrap': 'wrap' };
|
|
1299
1424
|
rules['.bw-display-4'] = { 'font-size': 'calc(1.475rem + 2.7vw)', 'font-weight': '300', 'line-height': '1.2' };
|
|
1300
1425
|
rules['.bw-lead'] = { 'font-size': '1.25rem', 'font-weight': '300' };
|
|
@@ -1367,6 +1492,156 @@ function getStructuralStyles() {
|
|
|
1367
1492
|
|
|
1368
1493
|
// Code demo (structural)
|
|
1369
1494
|
rules['.bw-code-demo'] = { 'margin-bottom': '2rem' };
|
|
1495
|
+
rules['.bw-code-pre'] = { 'margin': '0', 'border': 'none', 'border-radius': '6px', 'overflow-x': 'auto' };
|
|
1496
|
+
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' };
|
|
1497
|
+
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' };
|
|
1498
|
+
|
|
1499
|
+
// Button group (structural)
|
|
1500
|
+
rules['.bw-btn-group, .bw-btn-group-vertical'] = { 'position': 'relative', 'display': 'inline-flex', 'vertical-align': 'middle' };
|
|
1501
|
+
rules['.bw-btn-group > .bw-btn, .bw-btn-group-vertical > .bw-btn'] = { 'position': 'relative', 'flex': '1 1 auto', 'border-radius': '0', 'margin-left': '-1px' };
|
|
1502
|
+
rules['.bw-btn-group > .bw-btn:first-child'] = { 'margin-left': '0', 'border-top-left-radius': '6px', 'border-bottom-left-radius': '6px' };
|
|
1503
|
+
rules['.bw-btn-group > .bw-btn:last-child'] = { 'border-top-right-radius': '6px', 'border-bottom-right-radius': '6px' };
|
|
1504
|
+
rules['.bw-btn-group-vertical'] = { 'flex-direction': 'column', 'align-items': 'flex-start', 'justify-content': 'center' };
|
|
1505
|
+
rules['.bw-btn-group-vertical > .bw-btn'] = { 'width': '100%', 'margin-left': '0', 'margin-top': '-1px' };
|
|
1506
|
+
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' };
|
|
1507
|
+
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' };
|
|
1508
|
+
|
|
1509
|
+
// Accordion (structural)
|
|
1510
|
+
rules['.bw-accordion'] = { 'border-radius': '8px', 'overflow': 'hidden' };
|
|
1511
|
+
rules['.bw-accordion-item'] = { 'border': '1px solid transparent' };
|
|
1512
|
+
rules['.bw-accordion-item + .bw-accordion-item'] = { 'border-top': '0' };
|
|
1513
|
+
rules['.bw-accordion-header'] = { 'margin': '0' };
|
|
1514
|
+
rules['.bw-accordion-button'] = {
|
|
1515
|
+
'position': 'relative', 'display': 'flex', 'align-items': 'center', 'width': '100%',
|
|
1516
|
+
'padding': '1rem 1.25rem', 'font-size': '1rem', 'font-weight': '500', 'text-align': 'left',
|
|
1517
|
+
'background-color': 'transparent', 'border': '0', 'overflow-anchor': 'none', 'cursor': 'pointer',
|
|
1518
|
+
'font-family': 'inherit', 'transition': 'color 0.15s ease-in-out, background-color 0.15s ease-in-out'
|
|
1519
|
+
};
|
|
1520
|
+
rules['.bw-accordion-button::after'] = {
|
|
1521
|
+
'flex-shrink': '0', 'width': '1.25rem', 'height': '1.25rem', 'margin-left': 'auto',
|
|
1522
|
+
'content': '""', 'background-repeat': 'no-repeat', 'background-size': '1.25rem',
|
|
1523
|
+
'transition': 'transform 0.2s ease-in-out'
|
|
1524
|
+
};
|
|
1525
|
+
rules['.bw-accordion-button:not(.bw-collapsed)::after'] = { 'transform': 'rotate(-180deg)' };
|
|
1526
|
+
rules['.bw-accordion-collapse'] = { 'max-height': '0', 'overflow': 'hidden', 'transition': 'max-height 0.3s ease' };
|
|
1527
|
+
rules['.bw-accordion-collapse.bw-collapse-show'] = { 'max-height': 'none' };
|
|
1528
|
+
rules['.bw-accordion-body'] = { 'padding': '1rem 1.25rem' };
|
|
1529
|
+
|
|
1530
|
+
// Modal (structural)
|
|
1531
|
+
rules['.bw-modal'] = {
|
|
1532
|
+
'display': 'none', 'position': 'fixed', 'top': '0', 'left': '0', 'width': '100%', 'height': '100%',
|
|
1533
|
+
'z-index': '1050', 'overflow-x': 'hidden', 'overflow-y': 'auto', 'opacity': '0', 'transition': 'opacity 0.15s linear'
|
|
1534
|
+
};
|
|
1535
|
+
rules['.bw-modal.bw-modal-show'] = { 'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'opacity': '1' };
|
|
1536
|
+
rules['.bw-modal-dialog'] = {
|
|
1537
|
+
'position': 'relative', 'width': '100%', 'max-width': '500px', 'margin': '1.75rem auto',
|
|
1538
|
+
'pointer-events': 'none', 'transform': 'translateY(-20px)', 'transition': 'transform 0.2s ease-out'
|
|
1539
|
+
};
|
|
1540
|
+
rules['.bw-modal.bw-modal-show .bw-modal-dialog'] = { 'transform': 'translateY(0)' };
|
|
1541
|
+
rules['.bw-modal-sm'] = { 'max-width': '300px' };
|
|
1542
|
+
rules['.bw-modal-lg'] = { 'max-width': '800px' };
|
|
1543
|
+
rules['.bw-modal-xl'] = { 'max-width': '1140px' };
|
|
1544
|
+
rules['.bw-modal-content'] = {
|
|
1545
|
+
'position': 'relative', 'display': 'flex', 'flex-direction': 'column', 'pointer-events': 'auto',
|
|
1546
|
+
'background-clip': 'padding-box', 'border': '1px solid transparent', 'border-radius': '8px', 'outline': '0'
|
|
1547
|
+
};
|
|
1548
|
+
rules['.bw-modal-header'] = { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '1rem 1.5rem' };
|
|
1549
|
+
rules['.bw-modal-title'] = { 'margin': '0', 'font-size': '1.25rem', 'font-weight': '600', 'line-height': '1.3' };
|
|
1550
|
+
rules['.bw-modal-body'] = { 'position': 'relative', 'flex': '1 1 auto', 'padding': '1.5rem' };
|
|
1551
|
+
rules['.bw-modal-footer'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'flex-end', 'padding': '0.75rem 1.5rem', 'gap': '0.5rem' };
|
|
1552
|
+
|
|
1553
|
+
// Toast (structural)
|
|
1554
|
+
rules['.bw-toast-container'] = {
|
|
1555
|
+
'position': 'fixed', 'z-index': '1080', 'pointer-events': 'none',
|
|
1556
|
+
'display': 'flex', 'flex-direction': 'column', 'gap': '0.5rem', 'padding': '1rem'
|
|
1557
|
+
};
|
|
1558
|
+
rules['.bw-toast'] = {
|
|
1559
|
+
'pointer-events': 'auto', 'width': '350px', 'max-width': '100%', 'background-clip': 'padding-box',
|
|
1560
|
+
'border-radius': '8px', 'opacity': '0', 'transform': 'translateY(-10px)',
|
|
1561
|
+
'transition': 'opacity 0.3s ease, transform 0.3s ease'
|
|
1562
|
+
};
|
|
1563
|
+
rules['.bw-toast.bw-toast-show'] = { 'opacity': '1', 'transform': 'translateY(0)' };
|
|
1564
|
+
rules['.bw-toast.bw-toast-hiding'] = { 'opacity': '0', 'transform': 'translateY(-10px)' };
|
|
1565
|
+
rules['.bw-toast-header'] = { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '0.5rem 0.75rem', 'font-size': '0.875rem' };
|
|
1566
|
+
rules['.bw-toast-body'] = { 'padding': '0.75rem', 'font-size': '0.9375rem' };
|
|
1567
|
+
|
|
1568
|
+
// Dropdown (structural)
|
|
1569
|
+
rules['.bw-dropdown'] = { 'position': 'relative', 'display': 'inline-block' };
|
|
1570
|
+
rules['.bw-dropdown-toggle::after'] = {
|
|
1571
|
+
'display': 'inline-block', 'margin-left': '0.255em', 'vertical-align': '0.255em',
|
|
1572
|
+
'content': '""', 'border-top': '0.3em solid', 'border-right': '0.3em solid transparent',
|
|
1573
|
+
'border-bottom': '0', 'border-left': '0.3em solid transparent'
|
|
1574
|
+
};
|
|
1575
|
+
rules['.bw-dropdown-menu'] = {
|
|
1576
|
+
'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': 'none',
|
|
1577
|
+
'min-width': '10rem', 'padding': '0.5rem 0', 'margin': '0.125rem 0 0',
|
|
1578
|
+
'background-clip': 'padding-box', 'border-radius': '6px'
|
|
1579
|
+
};
|
|
1580
|
+
rules['.bw-dropdown-menu.bw-dropdown-show'] = { 'display': 'block' };
|
|
1581
|
+
rules['.bw-dropdown-menu-end'] = { 'left': 'auto', 'right': '0' };
|
|
1582
|
+
rules['.bw-dropdown-item'] = {
|
|
1583
|
+
'display': 'block', 'width': '100%', 'padding': '0.375rem 1rem', 'clear': 'both',
|
|
1584
|
+
'font-weight': '400', 'text-align': 'inherit', 'text-decoration': 'none', 'white-space': 'nowrap',
|
|
1585
|
+
'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem',
|
|
1586
|
+
'transition': 'background-color 0.15s, color 0.15s'
|
|
1587
|
+
};
|
|
1588
|
+
rules['.bw-dropdown-divider'] = { 'height': '0', 'margin': '0.5rem 0', 'overflow': 'hidden', 'opacity': '1' };
|
|
1589
|
+
|
|
1590
|
+
// Switch (structural)
|
|
1591
|
+
rules['.bw-form-switch'] = { 'padding-left': '2.5em' };
|
|
1592
|
+
rules['.bw-form-switch .bw-switch-input'] = {
|
|
1593
|
+
'width': '2em', 'height': '1.125em', 'margin-left': '-2.5em', 'border-radius': '2em',
|
|
1594
|
+
'appearance': 'none', 'background-position': 'left center', 'background-repeat': 'no-repeat',
|
|
1595
|
+
'background-size': 'contain', 'transition': 'background-position 0.15s ease-in-out, background-color 0.15s ease-in-out',
|
|
1596
|
+
'cursor': 'pointer'
|
|
1597
|
+
};
|
|
1598
|
+
rules['.bw-form-switch .bw-switch-input:checked'] = { 'background-position': 'right center' };
|
|
1599
|
+
rules['.bw-form-switch .bw-switch-input:disabled'] = { 'opacity': '0.5', 'cursor': 'not-allowed' };
|
|
1600
|
+
|
|
1601
|
+
// Skeleton (structural)
|
|
1602
|
+
rules['.bw-skeleton'] = { 'border-radius': '4px', 'animation': 'bw-skeleton-pulse 1.5s ease-in-out infinite' };
|
|
1603
|
+
rules['.bw-skeleton-text'] = { 'height': '1em', 'margin-bottom': '0.5rem' };
|
|
1604
|
+
rules['.bw-skeleton-circle'] = { 'border-radius': '50%' };
|
|
1605
|
+
rules['.bw-skeleton-rect'] = { 'border-radius': '8px' };
|
|
1606
|
+
rules['.bw-skeleton-group'] = { 'display': 'flex', 'flex-direction': 'column' };
|
|
1607
|
+
rules['@keyframes bw-skeleton-pulse'] = { '0%': { 'opacity': '1' }, '50%': { 'opacity': '0.4' }, '100%': { 'opacity': '1' } };
|
|
1608
|
+
|
|
1609
|
+
// Avatar (structural)
|
|
1610
|
+
rules['.bw-avatar'] = {
|
|
1611
|
+
'display': 'inline-flex', 'align-items': 'center', 'justify-content': 'center',
|
|
1612
|
+
'border-radius': '50%', 'overflow': 'hidden', 'font-weight': '600',
|
|
1613
|
+
'text-transform': 'uppercase', 'vertical-align': 'middle', 'object-fit': 'cover'
|
|
1614
|
+
};
|
|
1615
|
+
rules['.bw-avatar-sm'] = { 'width': '2rem', 'height': '2rem', 'font-size': '0.75rem' };
|
|
1616
|
+
rules['.bw-avatar-md'] = { 'width': '3rem', 'height': '3rem', 'font-size': '1rem' };
|
|
1617
|
+
rules['.bw-avatar-lg'] = { 'width': '4rem', 'height': '4rem', 'font-size': '1.25rem' };
|
|
1618
|
+
rules['.bw-avatar-xl'] = { 'width': '5rem', 'height': '5rem', 'font-size': '1.5rem' };
|
|
1619
|
+
|
|
1620
|
+
// Bar chart (structural)
|
|
1621
|
+
rules['.bw-bar-chart-container'] = {
|
|
1622
|
+
'padding': '1rem', 'border': '1px solid transparent', 'border-radius': '8px'
|
|
1623
|
+
};
|
|
1624
|
+
rules['.bw-bar-chart'] = {
|
|
1625
|
+
'display': 'flex', 'align-items': 'flex-end', 'gap': '6px', 'padding': '0 0.5rem'
|
|
1626
|
+
};
|
|
1627
|
+
rules['.bw-bar-group'] = {
|
|
1628
|
+
'flex': '1', 'display': 'flex', 'flex-direction': 'column',
|
|
1629
|
+
'align-items': 'center', 'height': '100%', 'justify-content': 'flex-end'
|
|
1630
|
+
};
|
|
1631
|
+
rules['.bw-bar'] = {
|
|
1632
|
+
'width': '100%', 'border-radius': '3px 3px 0 0',
|
|
1633
|
+
'transition': 'height 0.5s ease', 'min-height': '4px'
|
|
1634
|
+
};
|
|
1635
|
+
rules['.bw-bar:hover'] = { 'opacity': '0.85' };
|
|
1636
|
+
rules['.bw-bar-value'] = {
|
|
1637
|
+
'font-size': '0.65rem', 'font-weight': '600', 'margin-bottom': '2px', 'text-align': 'center'
|
|
1638
|
+
};
|
|
1639
|
+
rules['.bw-bar-label'] = {
|
|
1640
|
+
'font-size': '0.7rem', 'margin-top': '4px', 'text-align': 'center'
|
|
1641
|
+
};
|
|
1642
|
+
rules['.bw-bar-chart-title'] = {
|
|
1643
|
+
'font-size': '1.1rem', 'font-weight': '600', 'margin': '0 0 0.75rem 0'
|
|
1644
|
+
};
|
|
1370
1645
|
|
|
1371
1646
|
// Spacing utilities (structural)
|
|
1372
1647
|
var spacingValues = { '0': '0', '1': '.25rem', '2': '.5rem', '3': '1rem', '4': '1.5rem', '5': '3rem' };
|
|
@@ -1684,6 +1959,56 @@ function generateDarkModeCSS(palette) {
|
|
|
1684
1959
|
'.bw-dark .bw-close': {
|
|
1685
1960
|
'color': textColor
|
|
1686
1961
|
},
|
|
1962
|
+
'.bw-dark .bw-accordion-item': {
|
|
1963
|
+
'background-color': surfaceBg,
|
|
1964
|
+
'border-color': borderColor
|
|
1965
|
+
},
|
|
1966
|
+
'.bw-dark .bw-accordion-button': {
|
|
1967
|
+
'color': textColor
|
|
1968
|
+
},
|
|
1969
|
+
'.bw-dark .bw-accordion-button:hover': {
|
|
1970
|
+
'background-color': bodyBg
|
|
1971
|
+
},
|
|
1972
|
+
'.bw-dark .bw-accordion-body': {
|
|
1973
|
+
'border-top-color': borderColor
|
|
1974
|
+
},
|
|
1975
|
+
'.bw-dark .bw-modal-content': {
|
|
1976
|
+
'background-color': surfaceBg,
|
|
1977
|
+
'border-color': borderColor
|
|
1978
|
+
},
|
|
1979
|
+
'.bw-dark .bw-modal-header': {
|
|
1980
|
+
'border-bottom-color': borderColor
|
|
1981
|
+
},
|
|
1982
|
+
'.bw-dark .bw-modal-footer': {
|
|
1983
|
+
'border-top-color': borderColor
|
|
1984
|
+
},
|
|
1985
|
+
'.bw-dark .bw-modal-title': {
|
|
1986
|
+
'color': textColor
|
|
1987
|
+
},
|
|
1988
|
+
'.bw-dark .bw-toast': {
|
|
1989
|
+
'background-color': surfaceBg,
|
|
1990
|
+
'border-color': borderColor
|
|
1991
|
+
},
|
|
1992
|
+
'.bw-dark .bw-toast-header': {
|
|
1993
|
+
'border-bottom-color': borderColor,
|
|
1994
|
+
'color': textColor
|
|
1995
|
+
},
|
|
1996
|
+
'.bw-dark .bw-dropdown-menu': {
|
|
1997
|
+
'background-color': surfaceBg,
|
|
1998
|
+
'border-color': borderColor
|
|
1999
|
+
},
|
|
2000
|
+
'.bw-dark .bw-dropdown-item': {
|
|
2001
|
+
'color': textColor
|
|
2002
|
+
},
|
|
2003
|
+
'.bw-dark .bw-dropdown-item:hover': {
|
|
2004
|
+
'background-color': bodyBg
|
|
2005
|
+
},
|
|
2006
|
+
'.bw-dark .bw-dropdown-divider': {
|
|
2007
|
+
'border-top-color': borderColor
|
|
2008
|
+
},
|
|
2009
|
+
'.bw-dark .bw-skeleton': {
|
|
2010
|
+
'background-color': borderColor
|
|
2011
|
+
},
|
|
1687
2012
|
'.bw-dark h1, .bw-dark h2, .bw-dark h3, .bw-dark h4, .bw-dark h5, .bw-dark h6': {
|
|
1688
2013
|
'color': textColor
|
|
1689
2014
|
},
|
|
@@ -2277,7 +2602,11 @@ function makeAlert(props = {}) {
|
|
|
2277
2602
|
a: {
|
|
2278
2603
|
type: 'button',
|
|
2279
2604
|
class: 'bw-close',
|
|
2280
|
-
'aria-label': 'Close'
|
|
2605
|
+
'aria-label': 'Close',
|
|
2606
|
+
onclick: function(e) {
|
|
2607
|
+
var alert = e.target.closest('.bw-alert');
|
|
2608
|
+
if (alert) { alert.remove(); }
|
|
2609
|
+
}
|
|
2281
2610
|
},
|
|
2282
2611
|
c: '×'
|
|
2283
2612
|
}
|
|
@@ -2291,25 +2620,30 @@ function makeAlert(props = {}) {
|
|
|
2291
2620
|
* @param {Object} [props] - Badge configuration
|
|
2292
2621
|
* @param {string} [props.text] - Badge display text
|
|
2293
2622
|
* @param {string} [props.variant="primary"] - Color variant
|
|
2623
|
+
* @param {string} [props.size] - Size variant: 'sm' or 'lg' (default is medium)
|
|
2294
2624
|
* @param {boolean} [props.pill=false] - Use pill (rounded) shape
|
|
2295
2625
|
* @param {string} [props.className] - Additional CSS classes
|
|
2296
2626
|
* @returns {Object} TACO object representing a badge span
|
|
2297
2627
|
* @category Component Builders
|
|
2298
2628
|
* @example
|
|
2299
2629
|
* const badge = makeBadge({ text: "New", variant: "danger", pill: true });
|
|
2630
|
+
* const small = makeBadge({ text: "3", variant: "info", size: "sm" });
|
|
2300
2631
|
*/
|
|
2301
2632
|
function makeBadge(props = {}) {
|
|
2302
2633
|
const {
|
|
2303
2634
|
text,
|
|
2304
2635
|
variant = 'primary',
|
|
2636
|
+
size,
|
|
2305
2637
|
pill = false,
|
|
2306
2638
|
className = ''
|
|
2307
2639
|
} = props;
|
|
2308
2640
|
|
|
2641
|
+
const sizeClass = size === 'sm' ? ' bw-badge-sm' : size === 'lg' ? ' bw-badge-lg' : '';
|
|
2642
|
+
|
|
2309
2643
|
return {
|
|
2310
2644
|
t: 'span',
|
|
2311
2645
|
a: {
|
|
2312
|
-
class: `bw-badge bw-badge-${variant} ${pill ? 'bw-badge-pill' : ''} ${className}`.trim()
|
|
2646
|
+
class: `bw-badge bw-badge-${variant}${sizeClass} ${pill ? 'bw-badge-pill' : ''} ${className}`.trim()
|
|
2313
2647
|
},
|
|
2314
2648
|
c: text
|
|
2315
2649
|
};
|
|
@@ -2768,12 +3102,14 @@ function makeCheckbox(props = {}) {
|
|
|
2768
3102
|
id,
|
|
2769
3103
|
name,
|
|
2770
3104
|
disabled = false,
|
|
2771
|
-
value
|
|
3105
|
+
value,
|
|
3106
|
+
className = '',
|
|
3107
|
+
...eventHandlers
|
|
2772
3108
|
} = props;
|
|
2773
3109
|
|
|
2774
3110
|
return {
|
|
2775
3111
|
t: 'div',
|
|
2776
|
-
a: { class:
|
|
3112
|
+
a: { class: `bw-form-check ${className}`.trim() },
|
|
2777
3113
|
c: [
|
|
2778
3114
|
{
|
|
2779
3115
|
t: 'input',
|
|
@@ -2784,7 +3120,8 @@ function makeCheckbox(props = {}) {
|
|
|
2784
3120
|
id,
|
|
2785
3121
|
name,
|
|
2786
3122
|
disabled,
|
|
2787
|
-
value
|
|
3123
|
+
value,
|
|
3124
|
+
...eventHandlers
|
|
2788
3125
|
}
|
|
2789
3126
|
},
|
|
2790
3127
|
label && {
|
|
@@ -3012,8 +3349,8 @@ function makeFeatureGrid(props = {}) {
|
|
|
3012
3349
|
feature.icon && {
|
|
3013
3350
|
t: 'div',
|
|
3014
3351
|
a: {
|
|
3015
|
-
class: 'bw-feature-icon bw-mb-3',
|
|
3016
|
-
style: `font-size: ${iconSize}
|
|
3352
|
+
class: 'bw-feature-icon bw-mb-3 bw-text-primary',
|
|
3353
|
+
style: `font-size: ${iconSize};`
|
|
3017
3354
|
},
|
|
3018
3355
|
c: feature.icon
|
|
3019
3356
|
},
|
|
@@ -3402,229 +3739,1076 @@ class NavbarHandle {
|
|
|
3402
3739
|
}
|
|
3403
3740
|
|
|
3404
3741
|
/**
|
|
3405
|
-
* Imperative handle for a rendered tabs component
|
|
3406
|
-
*
|
|
3407
|
-
* Provides programmatic tab switching. Sets up click handlers
|
|
3408
|
-
* on tab buttons and manages active states on both buttons and panes.
|
|
3409
|
-
* Created automatically when using bw.createTabs().
|
|
3742
|
+
* Imperative handle for a rendered tabs component
|
|
3743
|
+
*
|
|
3744
|
+
* Provides programmatic tab switching. Sets up click handlers
|
|
3745
|
+
* on tab buttons and manages active states on both buttons and panes.
|
|
3746
|
+
* Created automatically when using bw.createTabs().
|
|
3747
|
+
*
|
|
3748
|
+
* @category Component Handles
|
|
3749
|
+
*/
|
|
3750
|
+
class TabsHandle {
|
|
3751
|
+
/**
|
|
3752
|
+
* @param {Element} element - The tabs container DOM element
|
|
3753
|
+
* @param {Object} taco - The original TACO object used to create the tabs
|
|
3754
|
+
*/
|
|
3755
|
+
constructor(element, taco) {
|
|
3756
|
+
this.element = element;
|
|
3757
|
+
this._taco = taco;
|
|
3758
|
+
this.state = taco.o?.state || {};
|
|
3759
|
+
|
|
3760
|
+
this.children = {
|
|
3761
|
+
navItems: element.querySelectorAll('.bw-nav-link'),
|
|
3762
|
+
tabPanes: element.querySelectorAll('.bw-tab-pane')
|
|
3763
|
+
};
|
|
3764
|
+
|
|
3765
|
+
this._setupTabs();
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
/**
|
|
3769
|
+
* Attach click handlers to tab navigation buttons
|
|
3770
|
+
* @private
|
|
3771
|
+
*/
|
|
3772
|
+
_setupTabs() {
|
|
3773
|
+
this.children.navItems.forEach((navItem, index) => {
|
|
3774
|
+
navItem.onclick = (e) => {
|
|
3775
|
+
e.preventDefault();
|
|
3776
|
+
this.switchTo(index);
|
|
3777
|
+
};
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3780
|
+
|
|
3781
|
+
/**
|
|
3782
|
+
* Programmatically switch to a tab by index
|
|
3783
|
+
*
|
|
3784
|
+
* @param {number} index - Zero-based tab index to activate
|
|
3785
|
+
* @returns {TabsHandle} this (for chaining)
|
|
3786
|
+
*/
|
|
3787
|
+
switchTo(index) {
|
|
3788
|
+
this.children.navItems.forEach((item, i) => {
|
|
3789
|
+
if (i === index) {
|
|
3790
|
+
item.classList.add('active');
|
|
3791
|
+
} else {
|
|
3792
|
+
item.classList.remove('active');
|
|
3793
|
+
}
|
|
3794
|
+
});
|
|
3795
|
+
|
|
3796
|
+
this.children.tabPanes.forEach((pane, i) => {
|
|
3797
|
+
if (i === index) {
|
|
3798
|
+
pane.classList.add('active');
|
|
3799
|
+
} else {
|
|
3800
|
+
pane.classList.remove('active');
|
|
3801
|
+
}
|
|
3802
|
+
});
|
|
3803
|
+
|
|
3804
|
+
this.state.activeIndex = index;
|
|
3805
|
+
return this;
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
|
|
3809
|
+
/**
|
|
3810
|
+
* Create a code demo component for documentation pages
|
|
3811
|
+
*
|
|
3812
|
+
* Displays a live result alongside source code in a tabbed interface.
|
|
3813
|
+
* Includes a copy-to-clipboard button on the code tab.
|
|
3814
|
+
*
|
|
3815
|
+
* @param {Object} [props] - Code demo configuration
|
|
3816
|
+
* @param {string} [props.title] - Demo title heading
|
|
3817
|
+
* @param {string} [props.description] - Demo description text
|
|
3818
|
+
* @param {string} [props.code] - Source code to display (adds a "Code" tab when present)
|
|
3819
|
+
* @param {string|Object|Array} [props.result] - Live result content for the "Result" tab
|
|
3820
|
+
* @param {string} [props.language="javascript"] - Code language for syntax class
|
|
3821
|
+
* @returns {Object} TACO object representing a code demo with tabbed Result/Code views
|
|
3822
|
+
* @category Component Builders
|
|
3823
|
+
* @example
|
|
3824
|
+
* const demo = makeCodeDemo({
|
|
3825
|
+
* title: "Button Example",
|
|
3826
|
+
* description: "A simple primary button",
|
|
3827
|
+
* code: 'makeButton({ text: "Click me" })',
|
|
3828
|
+
* result: makeButton({ text: "Click me" })
|
|
3829
|
+
* });
|
|
3830
|
+
*/
|
|
3831
|
+
function makeCodeDemo(props = {}) {
|
|
3832
|
+
const {
|
|
3833
|
+
title,
|
|
3834
|
+
description,
|
|
3835
|
+
code,
|
|
3836
|
+
result,
|
|
3837
|
+
language = 'javascript'
|
|
3838
|
+
} = props;
|
|
3839
|
+
|
|
3840
|
+
// Generate unique ID for this demo
|
|
3841
|
+
`demo-${Math.random().toString(36).substr(2, 9)}`;
|
|
3842
|
+
|
|
3843
|
+
const tabs = [
|
|
3844
|
+
{
|
|
3845
|
+
label: 'Result',
|
|
3846
|
+
active: true,
|
|
3847
|
+
content: result
|
|
3848
|
+
}
|
|
3849
|
+
];
|
|
3850
|
+
|
|
3851
|
+
// Only add Code tab if code is provided
|
|
3852
|
+
if (code) {
|
|
3853
|
+
tabs.push({
|
|
3854
|
+
label: 'Code',
|
|
3855
|
+
content: {
|
|
3856
|
+
t: 'div',
|
|
3857
|
+
a: { style: 'position: relative;' },
|
|
3858
|
+
c: [
|
|
3859
|
+
{
|
|
3860
|
+
t: 'button',
|
|
3861
|
+
a: {
|
|
3862
|
+
class: 'bw-copy-btn bw-code-copy-btn',
|
|
3863
|
+
onclick: (e) => {
|
|
3864
|
+
navigator.clipboard.writeText(code).then(() => {
|
|
3865
|
+
const btn = e.target;
|
|
3866
|
+
const originalText = btn.textContent;
|
|
3867
|
+
btn.textContent = 'Copied!';
|
|
3868
|
+
btn.style.background = '#006666';
|
|
3869
|
+
btn.style.color = '#fff';
|
|
3870
|
+
setTimeout(() => {
|
|
3871
|
+
btn.textContent = originalText;
|
|
3872
|
+
btn.style.background = 'rgba(255,255,255,0.12)';
|
|
3873
|
+
btn.style.color = '#aaa';
|
|
3874
|
+
}, 2000);
|
|
3875
|
+
});
|
|
3876
|
+
}
|
|
3877
|
+
},
|
|
3878
|
+
c: 'Copy'
|
|
3879
|
+
},
|
|
3880
|
+
(typeof globalThis !== 'undefined' && typeof globalThis.bw !== 'undefined' && typeof globalThis.bw.codeEditor === 'function')
|
|
3881
|
+
? globalThis.bw.codeEditor({ code: code, lang: language === 'javascript' ? 'js' : language, readOnly: true, height: 'auto' })
|
|
3882
|
+
: {
|
|
3883
|
+
t: 'pre',
|
|
3884
|
+
a: { class: 'bw-code-pre' },
|
|
3885
|
+
c: {
|
|
3886
|
+
t: 'code',
|
|
3887
|
+
a: { class: `bw-code-block language-${language}` },
|
|
3888
|
+
c: code
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
]
|
|
3892
|
+
}
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
|
|
3896
|
+
const content = [
|
|
3897
|
+
title && { t: 'h3', c: title },
|
|
3898
|
+
description && {
|
|
3899
|
+
t: 'p',
|
|
3900
|
+
a: { class: 'bw-text-muted', style: 'margin-bottom: 1rem;' },
|
|
3901
|
+
c: description
|
|
3902
|
+
},
|
|
3903
|
+
makeTabs({ tabs})
|
|
3904
|
+
].filter(Boolean);
|
|
3905
|
+
|
|
3906
|
+
return {
|
|
3907
|
+
t: 'div',
|
|
3908
|
+
a: { class: 'bw-code-demo' },
|
|
3909
|
+
c: content
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
/**
|
|
3914
|
+
* Registry mapping component type names to their handle classes
|
|
3915
|
+
*
|
|
3916
|
+
* Used by bw.createCard(), bw.createTable(), etc. to wrap rendered
|
|
3917
|
+
* DOM elements in the appropriate imperative handle.
|
|
3918
|
+
*
|
|
3919
|
+
* @type {Object.<string, Function>}
|
|
3920
|
+
*/
|
|
3921
|
+
// =========================================================================
|
|
3922
|
+
// Phase 1: Quick Wins
|
|
3923
|
+
// =========================================================================
|
|
3924
|
+
|
|
3925
|
+
/**
|
|
3926
|
+
* Create a pagination navigation component
|
|
3927
|
+
*
|
|
3928
|
+
* @param {Object} [props] - Pagination configuration
|
|
3929
|
+
* @param {number} [props.pages=1] - Total number of pages
|
|
3930
|
+
* @param {number} [props.currentPage=1] - Currently active page (1-based)
|
|
3931
|
+
* @param {Function} [props.onPageChange] - Callback when page changes, receives page number
|
|
3932
|
+
* @param {string} [props.size] - Size variant ("sm" or "lg")
|
|
3933
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
3934
|
+
* @returns {Object} TACO object representing a pagination nav
|
|
3935
|
+
* @category Component Builders
|
|
3936
|
+
* @example
|
|
3937
|
+
* const pager = makePagination({
|
|
3938
|
+
* pages: 10,
|
|
3939
|
+
* currentPage: 3,
|
|
3940
|
+
* onPageChange: (page) => loadPage(page)
|
|
3941
|
+
* });
|
|
3942
|
+
*/
|
|
3943
|
+
function makePagination(props = {}) {
|
|
3944
|
+
const {
|
|
3945
|
+
pages = 1,
|
|
3946
|
+
currentPage = 1,
|
|
3947
|
+
onPageChange,
|
|
3948
|
+
size,
|
|
3949
|
+
className = ''
|
|
3950
|
+
} = props;
|
|
3951
|
+
|
|
3952
|
+
function handleClick(page) {
|
|
3953
|
+
return function(e) {
|
|
3954
|
+
e.preventDefault();
|
|
3955
|
+
if (page < 1 || page > pages || page === currentPage) return;
|
|
3956
|
+
if (onPageChange) onPageChange(page);
|
|
3957
|
+
};
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
const items = [];
|
|
3961
|
+
|
|
3962
|
+
// Previous arrow
|
|
3963
|
+
items.push({
|
|
3964
|
+
t: 'li',
|
|
3965
|
+
a: { class: `bw-page-item ${currentPage <= 1 ? 'bw-disabled' : ''}`.trim() },
|
|
3966
|
+
c: {
|
|
3967
|
+
t: 'a',
|
|
3968
|
+
a: { class: 'bw-page-link', href: '#', onclick: handleClick(currentPage - 1), 'aria-label': 'Previous' },
|
|
3969
|
+
c: '\u2039'
|
|
3970
|
+
}
|
|
3971
|
+
});
|
|
3972
|
+
|
|
3973
|
+
// Page numbers
|
|
3974
|
+
for (var i = 1; i <= pages; i++) {
|
|
3975
|
+
(function(pageNum) {
|
|
3976
|
+
items.push({
|
|
3977
|
+
t: 'li',
|
|
3978
|
+
a: { class: `bw-page-item ${pageNum === currentPage ? 'bw-active' : ''}`.trim() },
|
|
3979
|
+
c: {
|
|
3980
|
+
t: 'a',
|
|
3981
|
+
a: { class: 'bw-page-link', href: '#', onclick: handleClick(pageNum) },
|
|
3982
|
+
c: '' + pageNum
|
|
3983
|
+
}
|
|
3984
|
+
});
|
|
3985
|
+
})(i);
|
|
3986
|
+
}
|
|
3987
|
+
|
|
3988
|
+
// Next arrow
|
|
3989
|
+
items.push({
|
|
3990
|
+
t: 'li',
|
|
3991
|
+
a: { class: `bw-page-item ${currentPage >= pages ? 'bw-disabled' : ''}`.trim() },
|
|
3992
|
+
c: {
|
|
3993
|
+
t: 'a',
|
|
3994
|
+
a: { class: 'bw-page-link', href: '#', onclick: handleClick(currentPage + 1), 'aria-label': 'Next' },
|
|
3995
|
+
c: '\u203A'
|
|
3996
|
+
}
|
|
3997
|
+
});
|
|
3998
|
+
|
|
3999
|
+
return {
|
|
4000
|
+
t: 'nav',
|
|
4001
|
+
a: { 'aria-label': 'Pagination' },
|
|
4002
|
+
c: {
|
|
4003
|
+
t: 'ul',
|
|
4004
|
+
a: {
|
|
4005
|
+
class: `bw-pagination ${size ? 'bw-pagination-' + size : ''} ${className}`.trim()
|
|
4006
|
+
},
|
|
4007
|
+
c: items
|
|
4008
|
+
}
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
|
|
4012
|
+
/**
|
|
4013
|
+
* Create a radio button input with label
|
|
4014
|
+
*
|
|
4015
|
+
* @param {Object} [props] - Radio configuration
|
|
4016
|
+
* @param {string} [props.label] - Radio label text
|
|
4017
|
+
* @param {string} [props.name] - Radio group name
|
|
4018
|
+
* @param {string} [props.value] - Radio value attribute
|
|
4019
|
+
* @param {boolean} [props.checked=false] - Whether the radio is selected
|
|
4020
|
+
* @param {string} [props.id] - Element ID (links label to radio)
|
|
4021
|
+
* @param {boolean} [props.disabled=false] - Whether the radio is disabled
|
|
4022
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4023
|
+
* @returns {Object} TACO object representing a radio form group
|
|
4024
|
+
* @category Component Builders
|
|
4025
|
+
* @example
|
|
4026
|
+
* const radio = makeRadio({
|
|
4027
|
+
* label: "Option A",
|
|
4028
|
+
* name: "choice",
|
|
4029
|
+
* value: "a",
|
|
4030
|
+
* checked: true
|
|
4031
|
+
* });
|
|
4032
|
+
*/
|
|
4033
|
+
function makeRadio(props = {}) {
|
|
4034
|
+
const {
|
|
4035
|
+
label,
|
|
4036
|
+
name,
|
|
4037
|
+
value,
|
|
4038
|
+
checked = false,
|
|
4039
|
+
id,
|
|
4040
|
+
disabled = false,
|
|
4041
|
+
className = '',
|
|
4042
|
+
...eventHandlers
|
|
4043
|
+
} = props;
|
|
4044
|
+
|
|
4045
|
+
return {
|
|
4046
|
+
t: 'div',
|
|
4047
|
+
a: { class: `bw-form-check ${className}`.trim() },
|
|
4048
|
+
c: [
|
|
4049
|
+
{
|
|
4050
|
+
t: 'input',
|
|
4051
|
+
a: {
|
|
4052
|
+
type: 'radio',
|
|
4053
|
+
class: 'bw-form-check-input',
|
|
4054
|
+
name,
|
|
4055
|
+
value,
|
|
4056
|
+
checked,
|
|
4057
|
+
id,
|
|
4058
|
+
disabled,
|
|
4059
|
+
...eventHandlers
|
|
4060
|
+
}
|
|
4061
|
+
},
|
|
4062
|
+
label && {
|
|
4063
|
+
t: 'label',
|
|
4064
|
+
a: { class: 'bw-form-check-label', for: id },
|
|
4065
|
+
c: label
|
|
4066
|
+
}
|
|
4067
|
+
].filter(Boolean)
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
/**
|
|
4072
|
+
* Create a button group wrapper
|
|
4073
|
+
*
|
|
4074
|
+
* @param {Object} [props] - Button group configuration
|
|
4075
|
+
* @param {Array} [props.children] - Button TACO objects to group
|
|
4076
|
+
* @param {string} [props.size] - Size variant ("sm" or "lg")
|
|
4077
|
+
* @param {boolean} [props.vertical=false] - Stack buttons vertically
|
|
4078
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4079
|
+
* @returns {Object} TACO object representing a button group
|
|
4080
|
+
* @category Component Builders
|
|
4081
|
+
* @example
|
|
4082
|
+
* const group = makeButtonGroup({
|
|
4083
|
+
* children: [
|
|
4084
|
+
* makeButton({ text: "Left", variant: "primary" }),
|
|
4085
|
+
* makeButton({ text: "Middle", variant: "primary" }),
|
|
4086
|
+
* makeButton({ text: "Right", variant: "primary" })
|
|
4087
|
+
* ]
|
|
4088
|
+
* });
|
|
4089
|
+
*/
|
|
4090
|
+
function makeButtonGroup(props = {}) {
|
|
4091
|
+
const {
|
|
4092
|
+
children,
|
|
4093
|
+
size,
|
|
4094
|
+
vertical = false,
|
|
4095
|
+
className = ''
|
|
4096
|
+
} = props;
|
|
4097
|
+
|
|
4098
|
+
return {
|
|
4099
|
+
t: 'div',
|
|
4100
|
+
a: {
|
|
4101
|
+
class: `${vertical ? 'bw-btn-group-vertical' : 'bw-btn-group'} ${size ? 'bw-btn-group-' + size : ''} ${className}`.trim(),
|
|
4102
|
+
role: 'group'
|
|
4103
|
+
},
|
|
4104
|
+
c: children
|
|
4105
|
+
};
|
|
4106
|
+
}
|
|
4107
|
+
|
|
4108
|
+
// =========================================================================
|
|
4109
|
+
// Phase 2: Core Interactive
|
|
4110
|
+
// =========================================================================
|
|
4111
|
+
|
|
4112
|
+
/**
|
|
4113
|
+
* Create an accordion component with collapsible items
|
|
4114
|
+
*
|
|
4115
|
+
* @param {Object} [props] - Accordion configuration
|
|
4116
|
+
* @param {Array<Object>} [props.items=[]] - Accordion items
|
|
4117
|
+
* @param {string} props.items[].title - Header text for the accordion item
|
|
4118
|
+
* @param {string|Object|Array} props.items[].content - Collapsible content
|
|
4119
|
+
* @param {boolean} [props.items[].open=false] - Whether the item is initially open
|
|
4120
|
+
* @param {boolean} [props.multiOpen=false] - Allow multiple items open simultaneously
|
|
4121
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4122
|
+
* @returns {Object} TACO object representing an accordion
|
|
4123
|
+
* @category Component Builders
|
|
4124
|
+
* @example
|
|
4125
|
+
* const accordion = makeAccordion({
|
|
4126
|
+
* items: [
|
|
4127
|
+
* { title: "Section 1", content: "Content 1", open: true },
|
|
4128
|
+
* { title: "Section 2", content: "Content 2" }
|
|
4129
|
+
* ]
|
|
4130
|
+
* });
|
|
4131
|
+
*/
|
|
4132
|
+
function makeAccordion(props = {}) {
|
|
4133
|
+
const {
|
|
4134
|
+
items = [],
|
|
4135
|
+
multiOpen = false,
|
|
4136
|
+
className = ''
|
|
4137
|
+
} = props;
|
|
4138
|
+
|
|
4139
|
+
return {
|
|
4140
|
+
t: 'div',
|
|
4141
|
+
a: { class: `bw-accordion ${className}`.trim() },
|
|
4142
|
+
c: items.map(function(item, index) {
|
|
4143
|
+
return {
|
|
4144
|
+
t: 'div',
|
|
4145
|
+
a: { class: 'bw-accordion-item' },
|
|
4146
|
+
c: [
|
|
4147
|
+
{
|
|
4148
|
+
t: 'h2',
|
|
4149
|
+
a: { class: 'bw-accordion-header' },
|
|
4150
|
+
c: {
|
|
4151
|
+
t: 'button',
|
|
4152
|
+
a: {
|
|
4153
|
+
class: `bw-accordion-button ${item.open ? '' : 'bw-collapsed'}`.trim(),
|
|
4154
|
+
type: 'button',
|
|
4155
|
+
'aria-expanded': item.open ? 'true' : 'false',
|
|
4156
|
+
'data-accordion-index': index,
|
|
4157
|
+
onclick: function(e) {
|
|
4158
|
+
var btn = e.target.closest('.bw-accordion-button');
|
|
4159
|
+
var accordionEl = btn.closest('.bw-accordion');
|
|
4160
|
+
var accordionItem = btn.closest('.bw-accordion-item');
|
|
4161
|
+
var collapse = accordionItem.querySelector('.bw-accordion-collapse');
|
|
4162
|
+
var isOpen = collapse.classList.contains('bw-collapse-show');
|
|
4163
|
+
|
|
4164
|
+
if (!multiOpen) {
|
|
4165
|
+
// Close all siblings
|
|
4166
|
+
var allCollapses = accordionEl.querySelectorAll('.bw-accordion-collapse');
|
|
4167
|
+
var allButtons = accordionEl.querySelectorAll('.bw-accordion-button');
|
|
4168
|
+
for (var j = 0; j < allCollapses.length; j++) {
|
|
4169
|
+
allCollapses[j].classList.remove('bw-collapse-show');
|
|
4170
|
+
allCollapses[j].style.maxHeight = null;
|
|
4171
|
+
}
|
|
4172
|
+
for (var k = 0; k < allButtons.length; k++) {
|
|
4173
|
+
allButtons[k].classList.add('bw-collapsed');
|
|
4174
|
+
allButtons[k].setAttribute('aria-expanded', 'false');
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
if (isOpen) {
|
|
4179
|
+
collapse.classList.remove('bw-collapse-show');
|
|
4180
|
+
collapse.style.maxHeight = null;
|
|
4181
|
+
btn.classList.add('bw-collapsed');
|
|
4182
|
+
btn.setAttribute('aria-expanded', 'false');
|
|
4183
|
+
} else {
|
|
4184
|
+
collapse.classList.add('bw-collapse-show');
|
|
4185
|
+
collapse.style.maxHeight = collapse.scrollHeight + 'px';
|
|
4186
|
+
btn.classList.remove('bw-collapsed');
|
|
4187
|
+
btn.setAttribute('aria-expanded', 'true');
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
},
|
|
4191
|
+
c: item.title
|
|
4192
|
+
}
|
|
4193
|
+
},
|
|
4194
|
+
{
|
|
4195
|
+
t: 'div',
|
|
4196
|
+
a: { class: `bw-accordion-collapse ${item.open ? 'bw-collapse-show' : ''}`.trim() },
|
|
4197
|
+
c: {
|
|
4198
|
+
t: 'div',
|
|
4199
|
+
a: { class: 'bw-accordion-body' },
|
|
4200
|
+
c: item.content
|
|
4201
|
+
},
|
|
4202
|
+
o: item.open ? {
|
|
4203
|
+
mounted: function(el) {
|
|
4204
|
+
el.style.maxHeight = el.scrollHeight + 'px';
|
|
4205
|
+
}
|
|
4206
|
+
} : undefined
|
|
4207
|
+
}
|
|
4208
|
+
]
|
|
4209
|
+
};
|
|
4210
|
+
}),
|
|
4211
|
+
o: {
|
|
4212
|
+
type: 'accordion',
|
|
4213
|
+
state: { multiOpen: multiOpen }
|
|
4214
|
+
}
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
|
|
4218
|
+
/**
|
|
4219
|
+
* Imperative handle for a rendered modal component
|
|
4220
|
+
*
|
|
4221
|
+
* Provides `.show()`, `.hide()`, `.toggle()`, and `.destroy()` methods
|
|
4222
|
+
* for controlling the modal programmatically.
|
|
4223
|
+
*
|
|
4224
|
+
* @category Component Handles
|
|
4225
|
+
*/
|
|
4226
|
+
class ModalHandle {
|
|
4227
|
+
/**
|
|
4228
|
+
* @param {Element} element - The modal backdrop DOM element
|
|
4229
|
+
* @param {Object} taco - The original TACO object
|
|
4230
|
+
*/
|
|
4231
|
+
constructor(element, taco) {
|
|
4232
|
+
this.element = element;
|
|
4233
|
+
this._taco = taco;
|
|
4234
|
+
this._escHandler = null;
|
|
4235
|
+
}
|
|
4236
|
+
|
|
4237
|
+
/** Show the modal */
|
|
4238
|
+
show() {
|
|
4239
|
+
this.element.classList.add('bw-modal-show');
|
|
4240
|
+
document.body.style.overflow = 'hidden';
|
|
4241
|
+
return this;
|
|
4242
|
+
}
|
|
4243
|
+
|
|
4244
|
+
/** Hide the modal */
|
|
4245
|
+
hide() {
|
|
4246
|
+
this.element.classList.remove('bw-modal-show');
|
|
4247
|
+
document.body.style.overflow = '';
|
|
4248
|
+
return this;
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4251
|
+
/** Toggle modal visibility */
|
|
4252
|
+
toggle() {
|
|
4253
|
+
if (this.element.classList.contains('bw-modal-show')) {
|
|
4254
|
+
this.hide();
|
|
4255
|
+
} else {
|
|
4256
|
+
this.show();
|
|
4257
|
+
}
|
|
4258
|
+
return this;
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4261
|
+
/** Remove the modal from DOM and clean up */
|
|
4262
|
+
destroy() {
|
|
4263
|
+
this.hide();
|
|
4264
|
+
if (this._escHandler) {
|
|
4265
|
+
document.removeEventListener('keydown', this._escHandler);
|
|
4266
|
+
}
|
|
4267
|
+
if (this.element.parentNode) {
|
|
4268
|
+
this.element.parentNode.removeChild(this.element);
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
|
|
4273
|
+
/**
|
|
4274
|
+
* Create a modal dialog overlay
|
|
4275
|
+
*
|
|
4276
|
+
* @param {Object} [props] - Modal configuration
|
|
4277
|
+
* @param {string} [props.title] - Modal title in header
|
|
4278
|
+
* @param {string|Object|Array} [props.content] - Modal body content
|
|
4279
|
+
* @param {string|Object|Array} [props.footer] - Modal footer content
|
|
4280
|
+
* @param {string} [props.size] - Modal size ("sm", "lg", "xl")
|
|
4281
|
+
* @param {boolean} [props.closeButton=true] - Show X close button in header
|
|
4282
|
+
* @param {Function} [props.onClose] - Callback when modal is closed
|
|
4283
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4284
|
+
* @returns {Object} TACO object representing a modal
|
|
4285
|
+
* @category Component Builders
|
|
4286
|
+
* @example
|
|
4287
|
+
* const modal = makeModal({
|
|
4288
|
+
* title: "Confirm",
|
|
4289
|
+
* content: "Are you sure?",
|
|
4290
|
+
* footer: makeButton({ text: "OK", variant: "primary" })
|
|
4291
|
+
* });
|
|
4292
|
+
*/
|
|
4293
|
+
function makeModal(props = {}) {
|
|
4294
|
+
const {
|
|
4295
|
+
title,
|
|
4296
|
+
content,
|
|
4297
|
+
footer,
|
|
4298
|
+
size,
|
|
4299
|
+
closeButton = true,
|
|
4300
|
+
onClose,
|
|
4301
|
+
className = ''
|
|
4302
|
+
} = props;
|
|
4303
|
+
|
|
4304
|
+
function closeModal(el) {
|
|
4305
|
+
var backdrop = el.closest('.bw-modal');
|
|
4306
|
+
if (backdrop) {
|
|
4307
|
+
backdrop.classList.remove('bw-modal-show');
|
|
4308
|
+
document.body.style.overflow = '';
|
|
4309
|
+
}
|
|
4310
|
+
if (onClose) onClose();
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4313
|
+
return {
|
|
4314
|
+
t: 'div',
|
|
4315
|
+
a: { class: `bw-modal ${className}`.trim() },
|
|
4316
|
+
c: {
|
|
4317
|
+
t: 'div',
|
|
4318
|
+
a: { class: `bw-modal-dialog ${size ? 'bw-modal-' + size : ''}`.trim() },
|
|
4319
|
+
c: {
|
|
4320
|
+
t: 'div',
|
|
4321
|
+
a: { class: 'bw-modal-content' },
|
|
4322
|
+
c: [
|
|
4323
|
+
(title || closeButton) && {
|
|
4324
|
+
t: 'div',
|
|
4325
|
+
a: { class: 'bw-modal-header' },
|
|
4326
|
+
c: [
|
|
4327
|
+
title && { t: 'h5', a: { class: 'bw-modal-title' }, c: title },
|
|
4328
|
+
closeButton && {
|
|
4329
|
+
t: 'button',
|
|
4330
|
+
a: {
|
|
4331
|
+
type: 'button',
|
|
4332
|
+
class: 'bw-close',
|
|
4333
|
+
'aria-label': 'Close',
|
|
4334
|
+
onclick: function(e) { closeModal(e.target); }
|
|
4335
|
+
},
|
|
4336
|
+
c: '\u00D7'
|
|
4337
|
+
}
|
|
4338
|
+
].filter(Boolean)
|
|
4339
|
+
},
|
|
4340
|
+
content && {
|
|
4341
|
+
t: 'div',
|
|
4342
|
+
a: { class: 'bw-modal-body' },
|
|
4343
|
+
c: content
|
|
4344
|
+
},
|
|
4345
|
+
footer && {
|
|
4346
|
+
t: 'div',
|
|
4347
|
+
a: { class: 'bw-modal-footer' },
|
|
4348
|
+
c: footer
|
|
4349
|
+
}
|
|
4350
|
+
].filter(Boolean)
|
|
4351
|
+
}
|
|
4352
|
+
},
|
|
4353
|
+
o: {
|
|
4354
|
+
type: 'modal',
|
|
4355
|
+
mounted: function(el) {
|
|
4356
|
+
// Click backdrop to close
|
|
4357
|
+
el.addEventListener('click', function(e) {
|
|
4358
|
+
if (e.target === el) closeModal(el);
|
|
4359
|
+
});
|
|
4360
|
+
// Escape key to close
|
|
4361
|
+
var escHandler = function(e) {
|
|
4362
|
+
if (e.key === 'Escape' && el.classList.contains('bw-modal-show')) {
|
|
4363
|
+
closeModal(el);
|
|
4364
|
+
}
|
|
4365
|
+
};
|
|
4366
|
+
document.addEventListener('keydown', escHandler);
|
|
4367
|
+
el._bw_escHandler = escHandler;
|
|
4368
|
+
},
|
|
4369
|
+
unmount: function(el) {
|
|
4370
|
+
if (el._bw_escHandler) {
|
|
4371
|
+
document.removeEventListener('keydown', el._bw_escHandler);
|
|
4372
|
+
}
|
|
4373
|
+
document.body.style.overflow = '';
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
};
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
/**
|
|
4380
|
+
* Create a toast notification popup
|
|
4381
|
+
*
|
|
4382
|
+
* @param {Object} [props] - Toast configuration
|
|
4383
|
+
* @param {string} [props.title] - Toast title
|
|
4384
|
+
* @param {string|Object|Array} [props.content] - Toast body content
|
|
4385
|
+
* @param {string} [props.variant="info"] - Color variant ("primary", "success", "danger", "warning", "info")
|
|
4386
|
+
* @param {boolean} [props.autoDismiss=true] - Auto-dismiss after delay
|
|
4387
|
+
* @param {number} [props.delay=5000] - Auto-dismiss delay in ms
|
|
4388
|
+
* @param {string} [props.position="top-right"] - Container position
|
|
4389
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4390
|
+
* @returns {Object} TACO object representing a toast
|
|
4391
|
+
* @category Component Builders
|
|
4392
|
+
* @example
|
|
4393
|
+
* const toast = makeToast({
|
|
4394
|
+
* title: "Success",
|
|
4395
|
+
* content: "File saved!",
|
|
4396
|
+
* variant: "success"
|
|
4397
|
+
* });
|
|
4398
|
+
*/
|
|
4399
|
+
function makeToast(props = {}) {
|
|
4400
|
+
const {
|
|
4401
|
+
title,
|
|
4402
|
+
content,
|
|
4403
|
+
variant = 'info',
|
|
4404
|
+
autoDismiss = true,
|
|
4405
|
+
delay = 5000,
|
|
4406
|
+
position = 'top-right',
|
|
4407
|
+
className = ''
|
|
4408
|
+
} = props;
|
|
4409
|
+
|
|
4410
|
+
return {
|
|
4411
|
+
t: 'div',
|
|
4412
|
+
a: {
|
|
4413
|
+
class: `bw-toast bw-toast-${variant} ${className}`.trim(),
|
|
4414
|
+
role: 'alert',
|
|
4415
|
+
'data-position': position
|
|
4416
|
+
},
|
|
4417
|
+
c: [
|
|
4418
|
+
(title) && {
|
|
4419
|
+
t: 'div',
|
|
4420
|
+
a: { class: 'bw-toast-header' },
|
|
4421
|
+
c: [
|
|
4422
|
+
{ t: 'strong', c: title },
|
|
4423
|
+
{
|
|
4424
|
+
t: 'button',
|
|
4425
|
+
a: {
|
|
4426
|
+
type: 'button',
|
|
4427
|
+
class: 'bw-close',
|
|
4428
|
+
'aria-label': 'Close',
|
|
4429
|
+
onclick: function(e) {
|
|
4430
|
+
var toast = e.target.closest('.bw-toast');
|
|
4431
|
+
if (toast) {
|
|
4432
|
+
toast.classList.add('bw-toast-hiding');
|
|
4433
|
+
setTimeout(function() { if (toast.parentNode) toast.parentNode.removeChild(toast); }, 300);
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
},
|
|
4437
|
+
c: '\u00D7'
|
|
4438
|
+
}
|
|
4439
|
+
]
|
|
4440
|
+
},
|
|
4441
|
+
content && {
|
|
4442
|
+
t: 'div',
|
|
4443
|
+
a: { class: 'bw-toast-body' },
|
|
4444
|
+
c: content
|
|
4445
|
+
}
|
|
4446
|
+
].filter(Boolean),
|
|
4447
|
+
o: {
|
|
4448
|
+
type: 'toast',
|
|
4449
|
+
mounted: function(el) {
|
|
4450
|
+
// Trigger show animation
|
|
4451
|
+
requestAnimationFrame(function() {
|
|
4452
|
+
el.classList.add('bw-toast-show');
|
|
4453
|
+
});
|
|
4454
|
+
// Auto-dismiss
|
|
4455
|
+
if (autoDismiss) {
|
|
4456
|
+
setTimeout(function() {
|
|
4457
|
+
el.classList.add('bw-toast-hiding');
|
|
4458
|
+
setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 300);
|
|
4459
|
+
}, delay);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
};
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
// =========================================================================
|
|
4467
|
+
// Phase 3: Essential Modern
|
|
4468
|
+
// =========================================================================
|
|
4469
|
+
|
|
4470
|
+
/**
|
|
4471
|
+
* Create a dropdown menu triggered by a button
|
|
4472
|
+
*
|
|
4473
|
+
* @param {Object} [props] - Dropdown configuration
|
|
4474
|
+
* @param {string|Object} [props.trigger] - Button text or TACO for the trigger
|
|
4475
|
+
* @param {Array<Object>} [props.items=[]] - Menu items
|
|
4476
|
+
* @param {string} [props.items[].text] - Item display text
|
|
4477
|
+
* @param {string} [props.items[].href] - Item link URL
|
|
4478
|
+
* @param {Function} [props.items[].onclick] - Item click handler
|
|
4479
|
+
* @param {boolean} [props.items[].divider] - Render as a divider line
|
|
4480
|
+
* @param {boolean} [props.items[].disabled] - Whether the item is disabled
|
|
4481
|
+
* @param {string} [props.align="start"] - Menu alignment ("start" or "end")
|
|
4482
|
+
* @param {string} [props.variant="primary"] - Trigger button variant
|
|
4483
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4484
|
+
* @returns {Object} TACO object representing a dropdown
|
|
4485
|
+
* @category Component Builders
|
|
4486
|
+
* @example
|
|
4487
|
+
* const dropdown = makeDropdown({
|
|
4488
|
+
* trigger: "Actions",
|
|
4489
|
+
* items: [
|
|
4490
|
+
* { text: "Edit", onclick: () => edit() },
|
|
4491
|
+
* { divider: true },
|
|
4492
|
+
* { text: "Delete", onclick: () => del() }
|
|
4493
|
+
* ]
|
|
4494
|
+
* });
|
|
4495
|
+
*/
|
|
4496
|
+
function makeDropdown(props = {}) {
|
|
4497
|
+
const {
|
|
4498
|
+
trigger,
|
|
4499
|
+
items = [],
|
|
4500
|
+
align = 'start',
|
|
4501
|
+
variant = 'primary',
|
|
4502
|
+
className = ''
|
|
4503
|
+
} = props;
|
|
4504
|
+
|
|
4505
|
+
var triggerTaco;
|
|
4506
|
+
if (typeof trigger === 'string' || trigger === undefined) {
|
|
4507
|
+
triggerTaco = {
|
|
4508
|
+
t: 'button',
|
|
4509
|
+
a: {
|
|
4510
|
+
class: `bw-btn bw-btn-${variant} bw-dropdown-toggle`,
|
|
4511
|
+
type: 'button',
|
|
4512
|
+
onclick: function(e) {
|
|
4513
|
+
var dropdown = e.target.closest('.bw-dropdown');
|
|
4514
|
+
var menu = dropdown.querySelector('.bw-dropdown-menu');
|
|
4515
|
+
menu.classList.toggle('bw-dropdown-show');
|
|
4516
|
+
}
|
|
4517
|
+
},
|
|
4518
|
+
c: trigger || 'Dropdown'
|
|
4519
|
+
};
|
|
4520
|
+
} else {
|
|
4521
|
+
triggerTaco = trigger;
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
return {
|
|
4525
|
+
t: 'div',
|
|
4526
|
+
a: { class: `bw-dropdown ${className}`.trim() },
|
|
4527
|
+
c: [
|
|
4528
|
+
triggerTaco,
|
|
4529
|
+
{
|
|
4530
|
+
t: 'div',
|
|
4531
|
+
a: { class: `bw-dropdown-menu ${align === 'end' ? 'bw-dropdown-menu-end' : ''}`.trim() },
|
|
4532
|
+
c: items.map(function(item) {
|
|
4533
|
+
if (item.divider) {
|
|
4534
|
+
return { t: 'hr', a: { class: 'bw-dropdown-divider' } };
|
|
4535
|
+
}
|
|
4536
|
+
return {
|
|
4537
|
+
t: 'a',
|
|
4538
|
+
a: {
|
|
4539
|
+
class: `bw-dropdown-item ${item.disabled ? 'disabled' : ''}`.trim(),
|
|
4540
|
+
href: item.href || '#',
|
|
4541
|
+
onclick: item.disabled ? undefined : function(e) {
|
|
4542
|
+
if (!item.href) e.preventDefault();
|
|
4543
|
+
var dropdown = e.target.closest('.bw-dropdown');
|
|
4544
|
+
var menu = dropdown.querySelector('.bw-dropdown-menu');
|
|
4545
|
+
menu.classList.remove('bw-dropdown-show');
|
|
4546
|
+
if (item.onclick) item.onclick(e);
|
|
4547
|
+
}
|
|
4548
|
+
},
|
|
4549
|
+
c: item.text
|
|
4550
|
+
};
|
|
4551
|
+
})
|
|
4552
|
+
}
|
|
4553
|
+
],
|
|
4554
|
+
o: {
|
|
4555
|
+
type: 'dropdown',
|
|
4556
|
+
mounted: function(el) {
|
|
4557
|
+
// Click outside to close
|
|
4558
|
+
var outsideHandler = function(e) {
|
|
4559
|
+
if (!el.contains(e.target)) {
|
|
4560
|
+
var menu = el.querySelector('.bw-dropdown-menu');
|
|
4561
|
+
if (menu) menu.classList.remove('bw-dropdown-show');
|
|
4562
|
+
}
|
|
4563
|
+
};
|
|
4564
|
+
document.addEventListener('click', outsideHandler);
|
|
4565
|
+
el._bw_outsideHandler = outsideHandler;
|
|
4566
|
+
},
|
|
4567
|
+
unmount: function(el) {
|
|
4568
|
+
if (el._bw_outsideHandler) {
|
|
4569
|
+
document.removeEventListener('click', el._bw_outsideHandler);
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
};
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
/**
|
|
4577
|
+
* Create a toggle switch (styled checkbox)
|
|
4578
|
+
*
|
|
4579
|
+
* @param {Object} [props] - Switch configuration
|
|
4580
|
+
* @param {string} [props.label] - Switch label text
|
|
4581
|
+
* @param {boolean} [props.checked=false] - Whether the switch is on
|
|
4582
|
+
* @param {string} [props.id] - Element ID (links label to switch)
|
|
4583
|
+
* @param {string} [props.name] - Input name attribute
|
|
4584
|
+
* @param {boolean} [props.disabled=false] - Whether the switch is disabled
|
|
4585
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4586
|
+
* @returns {Object} TACO object representing a toggle switch
|
|
4587
|
+
* @category Component Builders
|
|
4588
|
+
* @example
|
|
4589
|
+
* const toggle = makeSwitch({
|
|
4590
|
+
* label: "Dark mode",
|
|
4591
|
+
* checked: false,
|
|
4592
|
+
* onchange: (e) => toggleDark(e.target.checked)
|
|
4593
|
+
* });
|
|
4594
|
+
*/
|
|
4595
|
+
function makeSwitch(props = {}) {
|
|
4596
|
+
const {
|
|
4597
|
+
label,
|
|
4598
|
+
checked = false,
|
|
4599
|
+
id,
|
|
4600
|
+
name,
|
|
4601
|
+
disabled = false,
|
|
4602
|
+
className = '',
|
|
4603
|
+
...eventHandlers
|
|
4604
|
+
} = props;
|
|
4605
|
+
|
|
4606
|
+
return {
|
|
4607
|
+
t: 'div',
|
|
4608
|
+
a: { class: `bw-form-check bw-form-switch ${className}`.trim() },
|
|
4609
|
+
c: [
|
|
4610
|
+
{
|
|
4611
|
+
t: 'input',
|
|
4612
|
+
a: {
|
|
4613
|
+
type: 'checkbox',
|
|
4614
|
+
class: 'bw-form-check-input bw-switch-input',
|
|
4615
|
+
role: 'switch',
|
|
4616
|
+
checked,
|
|
4617
|
+
id,
|
|
4618
|
+
name,
|
|
4619
|
+
disabled,
|
|
4620
|
+
...eventHandlers
|
|
4621
|
+
}
|
|
4622
|
+
},
|
|
4623
|
+
label && {
|
|
4624
|
+
t: 'label',
|
|
4625
|
+
a: { class: 'bw-form-check-label', for: id },
|
|
4626
|
+
c: label
|
|
4627
|
+
}
|
|
4628
|
+
].filter(Boolean)
|
|
4629
|
+
};
|
|
4630
|
+
}
|
|
4631
|
+
|
|
4632
|
+
/**
|
|
4633
|
+
* Create a skeleton loading placeholder
|
|
3410
4634
|
*
|
|
3411
|
-
* @
|
|
4635
|
+
* @param {Object} [props] - Skeleton configuration
|
|
4636
|
+
* @param {string} [props.variant="text"] - Shape variant ("text", "circle", "rect")
|
|
4637
|
+
* @param {string} [props.width] - Custom width (e.g. "200px", "100%")
|
|
4638
|
+
* @param {string} [props.height] - Custom height (e.g. "20px")
|
|
4639
|
+
* @param {number} [props.count=1] - Number of skeleton lines (for text variant)
|
|
4640
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4641
|
+
* @returns {Object} TACO object representing a skeleton placeholder
|
|
4642
|
+
* @category Component Builders
|
|
4643
|
+
* @example
|
|
4644
|
+
* const skeleton = makeSkeleton({ variant: "text", count: 3, width: "100%" });
|
|
3412
4645
|
*/
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
this.state = taco.o?.state || {};
|
|
4646
|
+
function makeSkeleton(props = {}) {
|
|
4647
|
+
const {
|
|
4648
|
+
variant = 'text',
|
|
4649
|
+
width,
|
|
4650
|
+
height,
|
|
4651
|
+
count = 1,
|
|
4652
|
+
className = ''
|
|
4653
|
+
} = props;
|
|
3422
4654
|
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
4655
|
+
if (variant === 'circle') {
|
|
4656
|
+
var circleSize = width || height || '3rem';
|
|
4657
|
+
return {
|
|
4658
|
+
t: 'div',
|
|
4659
|
+
a: {
|
|
4660
|
+
class: `bw-skeleton bw-skeleton-circle ${className}`.trim(),
|
|
4661
|
+
style: { width: circleSize, height: circleSize }
|
|
4662
|
+
}
|
|
3426
4663
|
};
|
|
3427
|
-
|
|
3428
|
-
this._setupTabs();
|
|
3429
4664
|
}
|
|
3430
4665
|
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
4666
|
+
if (variant === 'rect') {
|
|
4667
|
+
return {
|
|
4668
|
+
t: 'div',
|
|
4669
|
+
a: {
|
|
4670
|
+
class: `bw-skeleton bw-skeleton-rect ${className}`.trim(),
|
|
4671
|
+
style: {
|
|
4672
|
+
width: width || '100%',
|
|
4673
|
+
height: height || '120px'
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
};
|
|
3442
4677
|
}
|
|
3443
4678
|
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
} else {
|
|
3455
|
-
item.classList.remove('active');
|
|
4679
|
+
// Text variant — multiple lines
|
|
4680
|
+
if (count === 1) {
|
|
4681
|
+
return {
|
|
4682
|
+
t: 'div',
|
|
4683
|
+
a: {
|
|
4684
|
+
class: `bw-skeleton bw-skeleton-text ${className}`.trim(),
|
|
4685
|
+
style: {
|
|
4686
|
+
width: width || '100%',
|
|
4687
|
+
height: height || '1em'
|
|
4688
|
+
}
|
|
3456
4689
|
}
|
|
3457
|
-
}
|
|
4690
|
+
};
|
|
4691
|
+
}
|
|
3458
4692
|
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
4693
|
+
var lines = [];
|
|
4694
|
+
for (var i = 0; i < count; i++) {
|
|
4695
|
+
lines.push({
|
|
4696
|
+
t: 'div',
|
|
4697
|
+
a: {
|
|
4698
|
+
class: 'bw-skeleton bw-skeleton-text',
|
|
4699
|
+
style: {
|
|
4700
|
+
width: i === count - 1 ? '75%' : (width || '100%'),
|
|
4701
|
+
height: height || '1em'
|
|
4702
|
+
}
|
|
3464
4703
|
}
|
|
3465
4704
|
});
|
|
3466
|
-
|
|
3467
|
-
this.state.activeIndex = index;
|
|
3468
|
-
return this;
|
|
3469
4705
|
}
|
|
4706
|
+
|
|
4707
|
+
return {
|
|
4708
|
+
t: 'div',
|
|
4709
|
+
a: { class: `bw-skeleton-group ${className}`.trim() },
|
|
4710
|
+
c: lines
|
|
4711
|
+
};
|
|
3470
4712
|
}
|
|
3471
4713
|
|
|
3472
4714
|
/**
|
|
3473
|
-
* Create a
|
|
3474
|
-
*
|
|
3475
|
-
*
|
|
3476
|
-
*
|
|
3477
|
-
*
|
|
3478
|
-
* @param {
|
|
3479
|
-
* @param {string} [props.
|
|
3480
|
-
* @param {string} [props.
|
|
3481
|
-
* @param {string} [props.
|
|
3482
|
-
* @
|
|
3483
|
-
* @param {string} [props.language="javascript"] - Code language for syntax class
|
|
3484
|
-
* @returns {Object} TACO object representing a code demo with tabbed Result/Code views
|
|
4715
|
+
* Create a user avatar with image or initials fallback
|
|
4716
|
+
*
|
|
4717
|
+
* @param {Object} [props] - Avatar configuration
|
|
4718
|
+
* @param {string} [props.src] - Image source URL
|
|
4719
|
+
* @param {string} [props.alt] - Image alt text
|
|
4720
|
+
* @param {string} [props.initials] - Fallback initials (e.g. "JD")
|
|
4721
|
+
* @param {string} [props.size="md"] - Size ("sm", "md", "lg", "xl")
|
|
4722
|
+
* @param {string} [props.variant="primary"] - Background color variant for initials
|
|
4723
|
+
* @param {string} [props.className] - Additional CSS classes
|
|
4724
|
+
* @returns {Object} TACO object representing an avatar
|
|
3485
4725
|
* @category Component Builders
|
|
3486
4726
|
* @example
|
|
3487
|
-
* const
|
|
3488
|
-
*
|
|
3489
|
-
* description: "A simple primary button",
|
|
3490
|
-
* code: 'makeButton({ text: "Click me" })',
|
|
3491
|
-
* result: makeButton({ text: "Click me" })
|
|
3492
|
-
* });
|
|
4727
|
+
* const avatar = makeAvatar({ src: "/photo.jpg", alt: "Jane Doe", size: "lg" });
|
|
4728
|
+
* const avatarInitials = makeAvatar({ initials: "JD", variant: "success" });
|
|
3493
4729
|
*/
|
|
3494
|
-
function
|
|
4730
|
+
function makeAvatar(props = {}) {
|
|
3495
4731
|
const {
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
4732
|
+
src,
|
|
4733
|
+
alt = '',
|
|
4734
|
+
initials,
|
|
4735
|
+
size = 'md',
|
|
4736
|
+
variant = 'primary',
|
|
4737
|
+
className = ''
|
|
3501
4738
|
} = props;
|
|
3502
4739
|
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
content: result
|
|
3511
|
-
}
|
|
3512
|
-
];
|
|
3513
|
-
|
|
3514
|
-
// Only add Code tab if code is provided
|
|
3515
|
-
if (code) {
|
|
3516
|
-
tabs.push({
|
|
3517
|
-
label: 'Code',
|
|
3518
|
-
content: {
|
|
3519
|
-
t: 'div',
|
|
3520
|
-
a: { style: 'position: relative;' },
|
|
3521
|
-
c: [
|
|
3522
|
-
{
|
|
3523
|
-
t: 'button',
|
|
3524
|
-
a: {
|
|
3525
|
-
class: 'bw-copy-btn',
|
|
3526
|
-
style: 'position: absolute; top: 0.5rem; right: 0.5rem; padding: 0.25rem 0.625rem; font-size: 0.6875rem; background: rgba(255,255,255,0.12); color: #aaa; border: 1px solid rgba(255,255,255,0.15); border-radius: 4px; cursor: pointer; font-family: inherit; transition: all 0.15s;',
|
|
3527
|
-
onclick: (e) => {
|
|
3528
|
-
navigator.clipboard.writeText(code).then(() => {
|
|
3529
|
-
const btn = e.target;
|
|
3530
|
-
const originalText = btn.textContent;
|
|
3531
|
-
btn.textContent = 'Copied!';
|
|
3532
|
-
btn.style.background = '#006666';
|
|
3533
|
-
btn.style.color = '#fff';
|
|
3534
|
-
setTimeout(() => {
|
|
3535
|
-
btn.textContent = originalText;
|
|
3536
|
-
btn.style.background = 'rgba(255,255,255,0.12)';
|
|
3537
|
-
btn.style.color = '#aaa';
|
|
3538
|
-
}, 2000);
|
|
3539
|
-
});
|
|
3540
|
-
}
|
|
3541
|
-
},
|
|
3542
|
-
c: 'Copy'
|
|
3543
|
-
},
|
|
3544
|
-
{
|
|
3545
|
-
t: 'pre',
|
|
3546
|
-
a: {
|
|
3547
|
-
style: 'margin: 0; background: #1e293b; border: none; border-radius: 6px; overflow-x: auto;'
|
|
3548
|
-
},
|
|
3549
|
-
c: {
|
|
3550
|
-
t: 'code',
|
|
3551
|
-
a: {
|
|
3552
|
-
class: `language-${language}`,
|
|
3553
|
-
style: 'display: block; padding: 1.25rem; font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace; font-size: 0.8125rem; line-height: 1.6; color: #e2e8f0;'
|
|
3554
|
-
},
|
|
3555
|
-
c: code
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
]
|
|
4740
|
+
if (src) {
|
|
4741
|
+
return {
|
|
4742
|
+
t: 'img',
|
|
4743
|
+
a: {
|
|
4744
|
+
class: `bw-avatar bw-avatar-${size} ${className}`.trim(),
|
|
4745
|
+
src: src,
|
|
4746
|
+
alt: alt
|
|
3559
4747
|
}
|
|
3560
|
-
}
|
|
4748
|
+
};
|
|
3561
4749
|
}
|
|
3562
4750
|
|
|
3563
|
-
const content = [
|
|
3564
|
-
title && { t: 'h3', c: title },
|
|
3565
|
-
description && {
|
|
3566
|
-
t: 'p',
|
|
3567
|
-
a: { style: 'color: #6c757d; margin-bottom: 1rem;' },
|
|
3568
|
-
c: description
|
|
3569
|
-
},
|
|
3570
|
-
makeTabs({ tabs})
|
|
3571
|
-
].filter(Boolean);
|
|
3572
|
-
|
|
3573
4751
|
return {
|
|
3574
4752
|
t: 'div',
|
|
3575
|
-
a: {
|
|
3576
|
-
|
|
4753
|
+
a: {
|
|
4754
|
+
class: `bw-avatar bw-avatar-${size} bw-avatar-${variant} ${className}`.trim()
|
|
4755
|
+
},
|
|
4756
|
+
c: initials || ''
|
|
3577
4757
|
};
|
|
3578
4758
|
}
|
|
3579
4759
|
|
|
3580
|
-
/**
|
|
3581
|
-
* Registry mapping component type names to their handle classes
|
|
3582
|
-
*
|
|
3583
|
-
* Used by bw.createCard(), bw.createTable(), etc. to wrap rendered
|
|
3584
|
-
* DOM elements in the appropriate imperative handle.
|
|
3585
|
-
*
|
|
3586
|
-
* @type {Object.<string, Function>}
|
|
3587
|
-
*/
|
|
3588
4760
|
const componentHandles = {
|
|
3589
4761
|
card: CardHandle,
|
|
3590
4762
|
table: TableHandle,
|
|
3591
4763
|
navbar: NavbarHandle,
|
|
3592
|
-
tabs: TabsHandle
|
|
4764
|
+
tabs: TabsHandle,
|
|
4765
|
+
modal: ModalHandle
|
|
3593
4766
|
};
|
|
3594
4767
|
|
|
3595
4768
|
var components = /*#__PURE__*/Object.freeze({
|
|
3596
4769
|
__proto__: null,
|
|
3597
4770
|
CardHandle: CardHandle,
|
|
4771
|
+
ModalHandle: ModalHandle,
|
|
3598
4772
|
NavbarHandle: NavbarHandle,
|
|
3599
4773
|
TableHandle: TableHandle,
|
|
3600
4774
|
TabsHandle: TabsHandle,
|
|
3601
4775
|
componentHandles: componentHandles,
|
|
4776
|
+
makeAccordion: makeAccordion,
|
|
3602
4777
|
makeAlert: makeAlert,
|
|
4778
|
+
makeAvatar: makeAvatar,
|
|
3603
4779
|
makeBadge: makeBadge,
|
|
3604
4780
|
makeBreadcrumb: makeBreadcrumb,
|
|
3605
4781
|
makeButton: makeButton,
|
|
4782
|
+
makeButtonGroup: makeButtonGroup,
|
|
3606
4783
|
makeCTA: makeCTA,
|
|
3607
4784
|
makeCard: makeCard,
|
|
3608
4785
|
makeCheckbox: makeCheckbox,
|
|
3609
4786
|
makeCodeDemo: makeCodeDemo,
|
|
3610
4787
|
makeCol: makeCol,
|
|
3611
4788
|
makeContainer: makeContainer,
|
|
4789
|
+
makeDropdown: makeDropdown,
|
|
3612
4790
|
makeFeatureGrid: makeFeatureGrid,
|
|
3613
4791
|
makeForm: makeForm,
|
|
3614
4792
|
makeFormGroup: makeFormGroup,
|
|
3615
4793
|
makeHero: makeHero,
|
|
3616
4794
|
makeInput: makeInput,
|
|
3617
4795
|
makeListGroup: makeListGroup,
|
|
4796
|
+
makeModal: makeModal,
|
|
3618
4797
|
makeNav: makeNav,
|
|
3619
4798
|
makeNavbar: makeNavbar,
|
|
4799
|
+
makePagination: makePagination,
|
|
3620
4800
|
makeProgress: makeProgress,
|
|
4801
|
+
makeRadio: makeRadio,
|
|
3621
4802
|
makeRow: makeRow,
|
|
3622
4803
|
makeSection: makeSection,
|
|
3623
4804
|
makeSelect: makeSelect,
|
|
4805
|
+
makeSkeleton: makeSkeleton,
|
|
3624
4806
|
makeSpinner: makeSpinner,
|
|
3625
4807
|
makeStack: makeStack,
|
|
4808
|
+
makeSwitch: makeSwitch,
|
|
3626
4809
|
makeTabs: makeTabs,
|
|
3627
|
-
makeTextarea: makeTextarea
|
|
4810
|
+
makeTextarea: makeTextarea,
|
|
4811
|
+
makeToast: makeToast
|
|
3628
4812
|
});
|
|
3629
4813
|
|
|
3630
4814
|
/**
|
|
@@ -4766,6 +5950,16 @@ bw.patch = function(id, content, attr) {
|
|
|
4766
5950
|
if (attr) {
|
|
4767
5951
|
// Patch an attribute
|
|
4768
5952
|
el.setAttribute(attr, String(content));
|
|
5953
|
+
} else if (Array.isArray(content)) {
|
|
5954
|
+
// Patch with array of children (strings and/or TACOs)
|
|
5955
|
+
el.innerHTML = '';
|
|
5956
|
+
content.forEach(function(item) {
|
|
5957
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
5958
|
+
el.appendChild(document.createTextNode(String(item)));
|
|
5959
|
+
} else if (item && item.t) {
|
|
5960
|
+
el.appendChild(bw.createDOM(item));
|
|
5961
|
+
}
|
|
5962
|
+
});
|
|
4769
5963
|
} else if (typeof content === 'object' && content !== null && content.t) {
|
|
4770
5964
|
// Patch with a TACO — replace children
|
|
4771
5965
|
el.innerHTML = '';
|
|
@@ -5962,6 +7156,7 @@ bw.getURLParam = function(key, defaultValue) {
|
|
|
5962
7156
|
* @see bw.makeTable
|
|
5963
7157
|
*/
|
|
5964
7158
|
bw.htmlTable = function(data, opts = {}) {
|
|
7159
|
+
console.warn('bw.htmlTable() is deprecated. Use bw.makeTableFromArray() for TACO output or bw.makeTable() for object-array data.');
|
|
5965
7160
|
if (bw.typeOf(data) !== "array" || data.length < 1) return "";
|
|
5966
7161
|
|
|
5967
7162
|
const dopts = {
|
|
@@ -6061,6 +7256,7 @@ bw._attrsToStr = function(attrs) {
|
|
|
6061
7256
|
* @see bw.makeTabs
|
|
6062
7257
|
*/
|
|
6063
7258
|
bw.htmlTabs = function(tabData, opts = {}) {
|
|
7259
|
+
console.warn('bw.htmlTabs() is deprecated. Use bw.makeTabs() instead.');
|
|
6064
7260
|
if (bw.typeOf(tabData) !== "array" || tabData.length < 1) return "";
|
|
6065
7261
|
|
|
6066
7262
|
const dopts = {
|
|
@@ -6107,6 +7303,7 @@ bw.htmlTabs = function(tabData, opts = {}) {
|
|
|
6107
7303
|
* @category Legacy (v1)
|
|
6108
7304
|
*/
|
|
6109
7305
|
bw.selectTabContent = function(tabElement) {
|
|
7306
|
+
console.warn('bw.selectTabContent() is deprecated. Use bw.makeTabs() instead.');
|
|
6110
7307
|
if (!bw._isBrowser || !tabElement) return;
|
|
6111
7308
|
|
|
6112
7309
|
const container = tabElement.closest(".bw-tab-container");
|
|
@@ -6635,12 +7832,21 @@ bw.makeTable = function(config) {
|
|
|
6635
7832
|
const {
|
|
6636
7833
|
data = [],
|
|
6637
7834
|
columns,
|
|
6638
|
-
className =
|
|
7835
|
+
className = '',
|
|
7836
|
+
striped = false,
|
|
7837
|
+
hover = false,
|
|
6639
7838
|
sortable = true,
|
|
6640
7839
|
onSort,
|
|
6641
7840
|
sortColumn,
|
|
6642
7841
|
sortDirection = 'asc'
|
|
6643
7842
|
} = config;
|
|
7843
|
+
|
|
7844
|
+
// Build class list: always include bw-table, add striped/hover, append user className
|
|
7845
|
+
let cls = 'bw-table';
|
|
7846
|
+
if (striped) cls += ' bw-table-striped';
|
|
7847
|
+
if (hover) cls += ' bw-table-hover';
|
|
7848
|
+
if (className) cls += ' ' + className;
|
|
7849
|
+
cls = cls.trim();
|
|
6644
7850
|
|
|
6645
7851
|
// Auto-detect columns if not provided
|
|
6646
7852
|
const cols = columns || (data.length > 0
|
|
@@ -6728,11 +7934,176 @@ bw.makeTable = function(config) {
|
|
|
6728
7934
|
|
|
6729
7935
|
return {
|
|
6730
7936
|
t: 'table',
|
|
6731
|
-
a: { class:
|
|
7937
|
+
a: { class: cls },
|
|
6732
7938
|
c: [thead, tbody]
|
|
6733
7939
|
};
|
|
6734
7940
|
};
|
|
6735
7941
|
|
|
7942
|
+
/**
|
|
7943
|
+
* Create a table from a 2D array.
|
|
7944
|
+
*
|
|
7945
|
+
* Converts a 2D array into the object-array format that `bw.makeTable()`
|
|
7946
|
+
* expects, then delegates. By default, the first row is used as column
|
|
7947
|
+
* headers. All standard `makeTable` props (striped, hover, sortable,
|
|
7948
|
+
* columns, onSort, etc.) are passed through.
|
|
7949
|
+
*
|
|
7950
|
+
* @param {Object} config - Configuration object
|
|
7951
|
+
* @param {Array<Array>} config.data - 2D array of values
|
|
7952
|
+
* @param {boolean} [config.headerRow=true] - Treat first row as column headers
|
|
7953
|
+
* @param {boolean} [config.striped=false] - Striped rows
|
|
7954
|
+
* @param {boolean} [config.hover=false] - Hover highlight
|
|
7955
|
+
* @param {boolean} [config.sortable=true] - Enable sort
|
|
7956
|
+
* @param {Array<Object>} [config.columns] - Override auto-generated column defs
|
|
7957
|
+
* @param {string} [config.className=''] - Additional CSS classes
|
|
7958
|
+
* @param {Function} [config.onSort] - Sort callback
|
|
7959
|
+
* @param {string} [config.sortColumn] - Currently sorted column key
|
|
7960
|
+
* @param {string} [config.sortDirection='asc'] - Sort direction
|
|
7961
|
+
* @returns {Object} TACO object for table
|
|
7962
|
+
* @category Component Builders
|
|
7963
|
+
* @see bw.makeTable
|
|
7964
|
+
* @example
|
|
7965
|
+
* bw.makeTableFromArray({
|
|
7966
|
+
* data: [
|
|
7967
|
+
* ['Name', 'Role', 'Status'],
|
|
7968
|
+
* ['Alice', 'Engineer', 'Active'],
|
|
7969
|
+
* ['Bob', 'Designer', 'Away']
|
|
7970
|
+
* ],
|
|
7971
|
+
* striped: true,
|
|
7972
|
+
* hover: true
|
|
7973
|
+
* });
|
|
7974
|
+
*/
|
|
7975
|
+
bw.makeTableFromArray = function(config) {
|
|
7976
|
+
const { data = [], headerRow = true, columns, ...rest } = config;
|
|
7977
|
+
|
|
7978
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
7979
|
+
return bw.makeTable({ data: [], columns: columns || [], ...rest });
|
|
7980
|
+
}
|
|
7981
|
+
|
|
7982
|
+
// Determine headers
|
|
7983
|
+
let headers;
|
|
7984
|
+
let rows;
|
|
7985
|
+
if (headerRow && data.length > 0) {
|
|
7986
|
+
headers = data[0].map(function(h) { return String(h); });
|
|
7987
|
+
rows = data.slice(1);
|
|
7988
|
+
} else {
|
|
7989
|
+
// Generate col0, col1, ... headers
|
|
7990
|
+
const width = data[0].length;
|
|
7991
|
+
headers = [];
|
|
7992
|
+
for (let i = 0; i < width; i++) {
|
|
7993
|
+
headers.push('col' + i);
|
|
7994
|
+
}
|
|
7995
|
+
rows = data;
|
|
7996
|
+
}
|
|
7997
|
+
|
|
7998
|
+
// Convert rows to object arrays
|
|
7999
|
+
const objData = rows.map(function(row) {
|
|
8000
|
+
const obj = {};
|
|
8001
|
+
headers.forEach(function(key, i) {
|
|
8002
|
+
obj[key] = row[i] !== undefined ? row[i] : '';
|
|
8003
|
+
});
|
|
8004
|
+
return obj;
|
|
8005
|
+
});
|
|
8006
|
+
|
|
8007
|
+
// Auto-generate column defs if not provided
|
|
8008
|
+
const cols = columns || headers.map(function(key) {
|
|
8009
|
+
return { key: key, label: key };
|
|
8010
|
+
});
|
|
8011
|
+
|
|
8012
|
+
return bw.makeTable({ data: objData, columns: cols, ...rest });
|
|
8013
|
+
};
|
|
8014
|
+
|
|
8015
|
+
/**
|
|
8016
|
+
* Create a vertical bar chart from data.
|
|
8017
|
+
*
|
|
8018
|
+
* Renders a pure-CSS bar chart using flexbox and percentage heights.
|
|
8019
|
+
* No canvas, SVG, or external charting library required.
|
|
8020
|
+
*
|
|
8021
|
+
* @param {Object} config - Chart configuration
|
|
8022
|
+
* @param {Array<Object>} config.data - Array of data objects
|
|
8023
|
+
* @param {string} [config.labelKey='label'] - Key for bar labels
|
|
8024
|
+
* @param {string} [config.valueKey='value'] - Key for bar values
|
|
8025
|
+
* @param {string} [config.title] - Chart title
|
|
8026
|
+
* @param {string} [config.color='#006666'] - Bar color (hex or CSS color)
|
|
8027
|
+
* @param {string} [config.height='200px'] - Height of the chart area
|
|
8028
|
+
* @param {Function} [config.formatValue] - Value label formatter: (value) => string
|
|
8029
|
+
* @param {boolean} [config.showValues=true] - Show value labels above bars
|
|
8030
|
+
* @param {boolean} [config.showLabels=true] - Show labels below bars
|
|
8031
|
+
* @param {string} [config.className=''] - Additional CSS classes
|
|
8032
|
+
* @returns {Object} TACO object
|
|
8033
|
+
* @category Component Builders
|
|
8034
|
+
* @example
|
|
8035
|
+
* bw.makeBarChart({
|
|
8036
|
+
* data: [
|
|
8037
|
+
* { label: 'Jan', value: 12400 },
|
|
8038
|
+
* { label: 'Feb', value: 15800 },
|
|
8039
|
+
* { label: 'Mar', value: 9200 }
|
|
8040
|
+
* ],
|
|
8041
|
+
* title: 'Monthly Revenue',
|
|
8042
|
+
* color: '#0077b6',
|
|
8043
|
+
* formatValue: (v) => '$' + (v / 1000).toFixed(1) + 'k'
|
|
8044
|
+
* });
|
|
8045
|
+
*/
|
|
8046
|
+
bw.makeBarChart = function(config) {
|
|
8047
|
+
const {
|
|
8048
|
+
data = [],
|
|
8049
|
+
labelKey = 'label',
|
|
8050
|
+
valueKey = 'value',
|
|
8051
|
+
title,
|
|
8052
|
+
color = '#006666',
|
|
8053
|
+
height = '200px',
|
|
8054
|
+
formatValue,
|
|
8055
|
+
showValues = true,
|
|
8056
|
+
showLabels = true,
|
|
8057
|
+
className = ''
|
|
8058
|
+
} = config;
|
|
8059
|
+
|
|
8060
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
8061
|
+
return { t: 'div', a: { class: ('bw-bar-chart-container ' + className).trim() }, c: '' };
|
|
8062
|
+
}
|
|
8063
|
+
|
|
8064
|
+
const values = data.map(function(d) { return Number(d[valueKey]) || 0; });
|
|
8065
|
+
const maxVal = Math.max.apply(null, values);
|
|
8066
|
+
|
|
8067
|
+
const bars = data.map(function(d, i) {
|
|
8068
|
+
const val = values[i];
|
|
8069
|
+
const pct = maxVal > 0 ? (val / maxVal * 100) : 0;
|
|
8070
|
+
const formatted = formatValue ? formatValue(val) : String(val);
|
|
8071
|
+
|
|
8072
|
+
const children = [];
|
|
8073
|
+
if (showValues) {
|
|
8074
|
+
children.push({ t: 'div', a: { class: 'bw-bar-value' }, c: formatted });
|
|
8075
|
+
}
|
|
8076
|
+
children.push({
|
|
8077
|
+
t: 'div',
|
|
8078
|
+
a: {
|
|
8079
|
+
class: 'bw-bar',
|
|
8080
|
+
style: 'height:' + pct + '%;background:' + color + ';'
|
|
8081
|
+
}
|
|
8082
|
+
});
|
|
8083
|
+
if (showLabels) {
|
|
8084
|
+
children.push({ t: 'div', a: { class: 'bw-bar-label' }, c: String(d[labelKey] || '') });
|
|
8085
|
+
}
|
|
8086
|
+
|
|
8087
|
+
return { t: 'div', a: { class: 'bw-bar-group' }, c: children };
|
|
8088
|
+
});
|
|
8089
|
+
|
|
8090
|
+
const chartChildren = [];
|
|
8091
|
+
if (title) {
|
|
8092
|
+
chartChildren.push({ t: 'h3', a: { class: 'bw-bar-chart-title' }, c: title });
|
|
8093
|
+
}
|
|
8094
|
+
chartChildren.push({
|
|
8095
|
+
t: 'div',
|
|
8096
|
+
a: { class: 'bw-bar-chart', style: 'height:' + height + ';' },
|
|
8097
|
+
c: bars
|
|
8098
|
+
});
|
|
8099
|
+
|
|
8100
|
+
return {
|
|
8101
|
+
t: 'div',
|
|
8102
|
+
a: { class: ('bw-bar-chart-container ' + className).trim() },
|
|
8103
|
+
c: chartChildren
|
|
8104
|
+
};
|
|
8105
|
+
};
|
|
8106
|
+
|
|
6736
8107
|
/**
|
|
6737
8108
|
* Create a responsive data table with title and optional wrapper
|
|
6738
8109
|
*
|
|
@@ -6743,7 +8114,9 @@ bw.makeTable = function(config) {
|
|
|
6743
8114
|
* @param {string} [config.title] - Table title heading
|
|
6744
8115
|
* @param {Array<Object>} config.data - Array of row objects
|
|
6745
8116
|
* @param {Array<Object>} [config.columns] - Column definitions
|
|
6746
|
-
* @param {string} [config.className=
|
|
8117
|
+
* @param {string} [config.className=''] - Additional CSS classes for the table
|
|
8118
|
+
* @param {boolean} [config.striped=true] - Add striped row styling
|
|
8119
|
+
* @param {boolean} [config.hover=true] - Add hover row highlighting
|
|
6747
8120
|
* @param {boolean} [config.responsive=true] - Wrap table in responsive overflow div
|
|
6748
8121
|
* @returns {Object} TACO object for table with wrapper
|
|
6749
8122
|
* @example
|
|
@@ -6758,7 +8131,9 @@ bw.makeDataTable = function(config) {
|
|
|
6758
8131
|
title,
|
|
6759
8132
|
data,
|
|
6760
8133
|
columns,
|
|
6761
|
-
className =
|
|
8134
|
+
className = '',
|
|
8135
|
+
striped = true,
|
|
8136
|
+
hover = true,
|
|
6762
8137
|
responsive = true,
|
|
6763
8138
|
...tableConfig
|
|
6764
8139
|
} = config;
|
|
@@ -6767,6 +8142,8 @@ bw.makeDataTable = function(config) {
|
|
|
6767
8142
|
data,
|
|
6768
8143
|
columns,
|
|
6769
8144
|
className,
|
|
8145
|
+
striped,
|
|
8146
|
+
hover,
|
|
6770
8147
|
...tableConfig
|
|
6771
8148
|
});
|
|
6772
8149
|
|