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