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