bitwrench 2.0.19 → 2.0.21

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.
Files changed (53) hide show
  1. package/README.md +0 -2
  2. package/dist/bitwrench-bccl.cjs.js +1 -1
  3. package/dist/bitwrench-bccl.cjs.min.js +1 -1
  4. package/dist/bitwrench-bccl.esm.js +1 -1
  5. package/dist/bitwrench-bccl.esm.min.js +1 -1
  6. package/dist/bitwrench-bccl.umd.js +1 -1
  7. package/dist/bitwrench-bccl.umd.min.js +1 -1
  8. package/dist/bitwrench-code-edit.cjs.js +1 -1
  9. package/dist/bitwrench-code-edit.cjs.min.js +1 -1
  10. package/dist/bitwrench-code-edit.es5.js +1 -1
  11. package/dist/bitwrench-code-edit.es5.min.js +1 -1
  12. package/dist/bitwrench-code-edit.esm.js +1 -1
  13. package/dist/bitwrench-code-edit.esm.min.js +1 -1
  14. package/dist/bitwrench-code-edit.umd.js +1 -1
  15. package/dist/bitwrench-code-edit.umd.min.js +1 -1
  16. package/dist/bitwrench-debug.js +1 -1
  17. package/dist/bitwrench-debug.min.js +1 -1
  18. package/dist/bitwrench-lean.cjs.js +344 -30
  19. package/dist/bitwrench-lean.cjs.min.js +14 -6
  20. package/dist/bitwrench-lean.es5.js +379 -29
  21. package/dist/bitwrench-lean.es5.min.js +12 -4
  22. package/dist/bitwrench-lean.esm.js +344 -30
  23. package/dist/bitwrench-lean.esm.min.js +14 -6
  24. package/dist/bitwrench-lean.umd.js +344 -30
  25. package/dist/bitwrench-lean.umd.min.js +14 -6
  26. package/dist/bitwrench-util-css.cjs.js +1 -1
  27. package/dist/bitwrench-util-css.cjs.min.js +1 -1
  28. package/dist/bitwrench-util-css.es5.js +1 -1
  29. package/dist/bitwrench-util-css.es5.min.js +1 -1
  30. package/dist/bitwrench-util-css.esm.js +1 -1
  31. package/dist/bitwrench-util-css.esm.min.js +1 -1
  32. package/dist/bitwrench-util-css.umd.js +1 -1
  33. package/dist/bitwrench-util-css.umd.min.js +1 -1
  34. package/dist/bitwrench.cjs.js +344 -30
  35. package/dist/bitwrench.cjs.min.js +14 -6
  36. package/dist/bitwrench.css +65 -14
  37. package/dist/bitwrench.es5.js +379 -29
  38. package/dist/bitwrench.es5.min.js +13 -5
  39. package/dist/bitwrench.esm.js +344 -30
  40. package/dist/bitwrench.esm.min.js +15 -7
  41. package/dist/bitwrench.min.css +1 -1
  42. package/dist/bitwrench.umd.js +344 -30
  43. package/dist/bitwrench.umd.min.js +14 -6
  44. package/dist/builds.json +94 -94
  45. package/dist/bwserve.cjs.js +2 -2
  46. package/dist/bwserve.esm.js +2 -2
  47. package/dist/sri.json +46 -46
  48. package/package.json +5 -6
  49. package/readme.html +2 -2
  50. package/src/bitwrench-router.js +282 -0
  51. package/src/bitwrench-styles.js +59 -27
  52. package/src/bitwrench.js +6 -0
  53. package/src/version.js +3 -3
@@ -1,4 +1,4 @@
1
- /*! bitwrench-util-css v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-util-css v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  'use strict';
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -1,4 +1,4 @@
1
- /*! bitwrench-util-css v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-util-css v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  "use strict";
3
3
  /**
4
4
  * bitwrench-util-css.js - Rule-based CSS utility parser plugin
@@ -1,4 +1,4 @@
1
- /*! bitwrench-util-css v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-util-css v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
4
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -1,4 +1,4 @@
1
- /*! bitwrench-util-css v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-util-css v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).bwUtilCSS={})}(this,function(t){"use strict";function n(t){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n(t)}
3
3
  /**
4
4
  * bitwrench-util-css.js - Rule-based CSS utility parser plugin
@@ -1,4 +1,4 @@
1
- /*! bitwrench-util-css v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-util-css v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  /**
3
3
  * bitwrench-util-css.js - Rule-based CSS utility parser plugin
4
4
  *
@@ -1,4 +1,4 @@
1
- /*! bitwrench-util-css v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-util-css v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  /**
3
3
  * bitwrench-util-css.js - Rule-based CSS utility parser plugin
4
4
  *
@@ -1,4 +1,4 @@
1
- /*! bitwrench-util-css v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-util-css v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
4
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -1,4 +1,4 @@
1
- /*! bitwrench-util-css v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-util-css v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).bwUtilCSS={})}(this,function(t){"use strict";
3
3
  /**
4
4
  * bitwrench-util-css.js - Rule-based CSS utility parser plugin
@@ -1,4 +1,4 @@
1
- /*! bitwrench v2.0.19 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  'use strict';
3
3
 
4
4
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
@@ -8,14 +8,14 @@ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentS
8
8
  */
9
9
 
10
10
  const VERSION_INFO = {
11
- version: '2.0.19',
11
+ version: '2.0.21',
12
12
  name: 'bitwrench',
13
13
  description: 'A library for javascript UI functions.',
14
14
  license: 'BSD-2-Clause',
15
15
  homepage: 'https://deftio.github.com/bitwrench/pages',
16
16
  repository: 'git+https://github.com/deftio/bitwrench.git',
17
17
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
18
- buildDate: '2026-03-22T19:09:32.608Z'
18
+ buildDate: '2026-03-24T05:30:22.242Z'
19
19
  };
20
20
 
21
21
  /**
@@ -581,10 +581,10 @@ var ELEVATION_PRESETS = {
581
581
  xl: '0 4px 12px rgba(0,0,0,0.12)'
582
582
  },
583
583
  md: {
584
- sm: '0 1px 3px rgba(0,0,0,0.08)',
585
- md: '0 2px 6px rgba(0,0,0,0.12)',
586
- lg: '0 4px 12px rgba(0,0,0,0.16)',
587
- xl: '0 8px 24px rgba(0,0,0,0.20)'
584
+ sm: '0 1px 3px rgba(0,0,0,0.10), 0 1px 2px rgba(0,0,0,0.06)',
585
+ md: '0 4px 6px rgba(0,0,0,0.10), 0 2px 4px rgba(0,0,0,0.06)',
586
+ lg: '0 10px 15px rgba(0,0,0,0.12), 0 4px 6px rgba(0,0,0,0.08)',
587
+ xl: '0 20px 25px rgba(0,0,0,0.15), 0 8px 10px rgba(0,0,0,0.10)'
588
588
  },
589
589
  lg: {
590
590
  sm: '0 2px 4px rgba(0,0,0,0.10)',
@@ -778,6 +778,9 @@ function generateCards(scope, palette, layout) {
778
778
  rules[_sx(scope, '.bw_card:hover')] = {
779
779
  'box-shadow': elev.md
780
780
  };
781
+ rules[_sx(scope, '.bw_card_hoverable')] = {
782
+ 'transition': 'box-shadow ' + motion.slow + ' ' + motion.easing + ', transform ' + motion.slow + ' ' + motion.easing
783
+ };
781
784
  rules[_sx(scope, '.bw_card_hoverable:hover')] = {
782
785
  'box-shadow': elev.lg
783
786
  };
@@ -878,7 +881,8 @@ function generateNavigation(scope, palette, layout) {
878
881
  };
879
882
  rules[_sx(scope, '.bw_navbar_nav .bw_nav_link')] = {
880
883
  'color': palette.secondary.base,
881
- 'border-radius': layout.radius.btn
884
+ 'border-radius': layout.radius.btn,
885
+ 'transition': 'color ' + layout.motion.fast + ' ' + layout.motion.easing + ', background-color ' + layout.motion.fast + ' ' + layout.motion.easing
882
886
  };
883
887
  rules[_sx(scope, '.bw_navbar_nav .bw_nav_link:hover')] = {
884
888
  'color': palette.dark.base,
@@ -1041,15 +1045,18 @@ function generatePagination(scope, palette, layout) {
1041
1045
  return rules;
1042
1046
  }
1043
1047
 
1044
- function generateProgress(scope, palette) {
1048
+ function generateProgress(scope, palette, layout) {
1045
1049
  var rules = {};
1050
+ var rd = layout ? layout.radius : { badge: '.375rem' };
1046
1051
  rules[_sx(scope, '.bw_progress')] = {
1047
1052
  'background-color': palette.surfaceAlt,
1053
+ 'border-radius': rd.badge,
1048
1054
  'box-shadow': 'inset 0 1px 2px rgba(0,0,0,.1)'
1049
1055
  };
1050
1056
  rules[_sx(scope, '.bw_progress_bar')] = {
1051
1057
  'color': palette.primary.textOn,
1052
1058
  'background-color': palette.primary.base,
1059
+ 'border-radius': 'inherit',
1053
1060
  'box-shadow': 'inset 0 -1px 0 rgba(0,0,0,.15)'
1054
1061
  };
1055
1062
  // Variant progress bar colors handled by palette class
@@ -1173,7 +1180,8 @@ function generateCarouselThemed(scope, palette) {
1173
1180
  };
1174
1181
  rules[_sx(scope, '.bw_carousel_control')] = {
1175
1182
  'background-color': palette.dark.base,
1176
- 'color': palette.dark.textOn
1183
+ 'color': palette.dark.textOn,
1184
+ 'transition': 'background-color 0.15s ease-out'
1177
1185
  };
1178
1186
  rules[_sx(scope, '.bw_carousel_control:hover')] = {
1179
1187
  'background-color': palette.dark.hover
@@ -1187,9 +1195,11 @@ function generateCarouselThemed(scope, palette) {
1187
1195
 
1188
1196
  function generateModalThemed(scope, palette, layout) {
1189
1197
  var rules = {};
1198
+ var rd = layout ? layout.radius : { card: '8px' };
1190
1199
  rules[_sx(scope, '.bw_modal_content')] = {
1191
1200
  'background-color': palette.surface || '#fff',
1192
1201
  'border-color': palette.light.border,
1202
+ 'border-radius': rd.card,
1193
1203
  'box-shadow': layout.elevation.lg
1194
1204
  };
1195
1205
  rules[_sx(scope, '.bw_modal_header')] = {
@@ -1206,9 +1216,11 @@ function generateModalThemed(scope, palette, layout) {
1206
1216
 
1207
1217
  function generateToastThemed(scope, palette, layout) {
1208
1218
  var rules = {};
1219
+ var rd = layout ? layout.radius : { card: '8px' };
1209
1220
  rules[_sx(scope, '.bw_toast')] = {
1210
1221
  'background-color': palette.surface || '#fff',
1211
1222
  'border-color': palette.light.border,
1223
+ 'border-radius': rd.card,
1212
1224
  'box-shadow': layout.elevation.lg
1213
1225
  };
1214
1226
  rules[_sx(scope, '.bw_toast_header')] = {
@@ -1220,9 +1232,11 @@ function generateToastThemed(scope, palette, layout) {
1220
1232
 
1221
1233
  function generateDropdownThemed(scope, palette, layout) {
1222
1234
  var rules = {};
1235
+ var rd = layout ? layout.radius : { card: '8px' };
1223
1236
  rules[_sx(scope, '.bw_dropdown_menu')] = {
1224
1237
  'background-color': palette.surface || '#fff',
1225
1238
  'border-color': palette.light.border,
1239
+ 'border-radius': rd.card,
1226
1240
  'box-shadow': layout.elevation.md
1227
1241
  };
1228
1242
  rules[_sx(scope, '.bw_dropdown_item')] = {
@@ -1255,6 +1269,10 @@ function generateSwitchThemed(scope, palette) {
1255
1269
  rules[_sx(scope, '.bw_form_switch .bw_switch_input:focus')] = {
1256
1270
  'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
1257
1271
  };
1272
+ rules[_sx(scope, '.bw_form_switch .bw_switch_input:focus-visible')] = {
1273
+ 'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus,
1274
+ 'outline': 'none'
1275
+ };
1258
1276
  return rules;
1259
1277
  }
1260
1278
 
@@ -1318,12 +1336,14 @@ function generateStepperThemed(scope, palette) {
1318
1336
  return rules;
1319
1337
  }
1320
1338
 
1321
- function generateChipInputThemed(scope, palette) {
1339
+ function generateChipInputThemed(scope, palette, layout) {
1322
1340
  var rules = {};
1341
+ var rd = layout ? layout.radius : { input: '6px' };
1323
1342
  rules[_sx(scope, '.bw_chip_input')] = {
1324
1343
  'border-color': palette.light.border,
1325
1344
  'background-color': palette.surface || '#fff',
1326
- 'color': palette.dark.base
1345
+ 'color': palette.dark.base,
1346
+ 'border-radius': rd.input
1327
1347
  };
1328
1348
  rules[_sx(scope, '.bw_chip_input:focus-within')] = {
1329
1349
  'border-color': palette.primary.base,
@@ -1608,7 +1628,7 @@ function generateThemedCSS(scopeName, palette, layout) {
1608
1628
  generateTabs(scopeName, palette, layout),
1609
1629
  generateListGroups(scopeName, palette, layout),
1610
1630
  generatePagination(scopeName, palette, layout),
1611
- generateProgress(scopeName, palette),
1631
+ generateProgress(scopeName, palette, layout),
1612
1632
  generateBreadcrumbThemed(scopeName, palette, layout),
1613
1633
  generateCloseButtonThemed(scopeName, palette),
1614
1634
  generateSectionsThemed(scopeName, palette),
@@ -1622,7 +1642,7 @@ function generateThemedCSS(scopeName, palette, layout) {
1622
1642
  generateStatCardThemed(scopeName, palette, layout),
1623
1643
  generateTimelineThemed(scopeName, palette),
1624
1644
  generateStepperThemed(scopeName, palette),
1625
- generateChipInputThemed(scopeName, palette),
1645
+ generateChipInputThemed(scopeName, palette, layout),
1626
1646
  generateFileUploadThemed(scopeName, palette, layout),
1627
1647
  generateRangeThemed(scopeName, palette),
1628
1648
  generateSearchThemed(scopeName, palette, layout),
@@ -1770,7 +1790,7 @@ var structuralRules = {
1770
1790
  '.bw_card_text': { 'margin-bottom': '0', 'font-size': '0.9375rem', 'line-height': '1.6' },
1771
1791
  '.bw_card_header': { 'margin-bottom': '0', 'font-weight': '600', 'font-size': '0.875rem' },
1772
1792
  '.bw_card_footer': { 'font-size': '0.875rem' },
1773
- '.bw_card_hoverable': { 'transition': 'all 0.3s ease-out' },
1793
+ '.bw_card_hoverable': {},
1774
1794
  '.bw_card_hoverable:hover': { 'transform': 'translateY(-4px)' },
1775
1795
  '.bw_card_img_top': { 'width': '100%' },
1776
1796
  '.bw_card_img_bottom': { 'width': '100%' },
@@ -1785,7 +1805,8 @@ var structuralRules = {
1785
1805
  'display': 'block', 'width': '100%',
1786
1806
  'font-size': '0.9375rem', 'font-weight': '400', 'line-height': '1.5',
1787
1807
  'background-clip': 'padding-box', 'appearance': 'none',
1788
- 'border': '1px solid transparent', 'font-family': 'inherit'
1808
+ 'border': '1px solid transparent', 'font-family': 'inherit',
1809
+ 'transition': 'border-color 0.15s ease-out, box-shadow 0.15s ease-out'
1789
1810
  },
1790
1811
  '.bw_form_control:focus': { 'outline': '2px solid currentColor', 'outline-offset': '-1px' },
1791
1812
  '.bw_form_control::placeholder': { 'opacity': '1' },
@@ -1912,6 +1933,7 @@ var structuralRules = {
1912
1933
  'text-decoration': 'none', 'cursor': 'pointer',
1913
1934
  'border': 'none', 'background': 'transparent', 'font-family': 'inherit'
1914
1935
  },
1936
+ '.bw_nav_link:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '-2px' },
1915
1937
  '.bw_nav_tabs .bw_nav_link': { 'border': 'none', 'border-bottom': '2px solid transparent', 'border-radius': '0', 'background-color': 'transparent' },
1916
1938
  '.bw_nav_vertical': { 'flex-direction': 'column' },
1917
1939
  '.bw_tab_content': { 'padding': '1.25rem 0' },
@@ -2029,9 +2051,11 @@ var structuralRules = {
2029
2051
  'display': 'inline-flex', 'align-items': 'center', 'justify-content': 'center',
2030
2052
  'width': '1.5rem', 'height': '1.5rem', 'padding': '0',
2031
2053
  'font-size': '1.25rem', 'font-weight': '700', 'line-height': '1',
2032
- 'background': 'transparent', 'border': '0', 'border-radius': '0.25rem', 'cursor': 'pointer'
2054
+ 'background': 'transparent', 'border': '0', 'border-radius': '0.25rem', 'cursor': 'pointer',
2055
+ 'transition': 'opacity 0.15s ease-out'
2033
2056
  },
2034
- '.bw_close:hover': { 'opacity': '0.75' }
2057
+ '.bw_close:hover': { 'opacity': '0.75' },
2058
+ '.bw_close:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '2px' }
2035
2059
  },
2036
2060
 
2037
2061
  // ---- Stacks ----
@@ -2113,7 +2137,8 @@ var structuralRules = {
2113
2137
  'flex-shrink': '0', 'width': '1.25rem', 'height': '1.25rem', 'margin-left': 'auto',
2114
2138
  'content': '""',
2115
2139
  'background-image': "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e\")",
2116
- 'background-repeat': 'no-repeat', 'background-size': '1.25rem'
2140
+ 'background-repeat': 'no-repeat', 'background-size': '1.25rem',
2141
+ 'transition': 'transform 0.2s ease-out'
2117
2142
  },
2118
2143
  '.bw_accordion_button:not(.bw_collapsed)::after': { 'transform': 'rotate(-180deg)' },
2119
2144
  '.bw_accordion_body': { 'padding': '1rem 1.25rem' },
@@ -2138,6 +2163,7 @@ var structuralRules = {
2138
2163
  'z-index': '2', 'padding': '0'
2139
2164
  },
2140
2165
  '.bw_carousel_control img': { 'width': '20px', 'height': '20px', 'pointer-events': 'none' },
2166
+ '.bw_carousel_control:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '2px' },
2141
2167
  '.bw_carousel_control_prev': { 'left': '10px' },
2142
2168
  '.bw_carousel_control_next': { 'right': '10px' },
2143
2169
  '.bw_carousel_indicators': {
@@ -2157,12 +2183,14 @@ var structuralRules = {
2157
2183
  'display': 'flex', 'align-items': 'center', 'justify-content': 'center',
2158
2184
  'position': 'fixed', 'top': '0', 'left': '0', 'width': '100%', 'height': '100%',
2159
2185
  'z-index': '1050', 'overflow-x': 'hidden', 'overflow-y': 'auto',
2160
- 'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none'
2186
+ 'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none',
2187
+ 'transition': 'opacity 0.2s ease-out, visibility 0.2s ease-out'
2161
2188
  },
2162
2189
  '.bw_modal.bw_modal_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
2163
2190
  '.bw_modal_dialog': {
2164
2191
  'position': 'relative', 'width': 'calc(100% - 1rem)', 'max-width': '500px', 'margin': '1.75rem auto',
2165
- 'pointer-events': 'none'
2192
+ 'pointer-events': 'none', 'transform': 'translateY(-16px)',
2193
+ 'transition': 'transform 0.2s ease-out'
2166
2194
  },
2167
2195
  '.bw_modal.bw_modal_show .bw_modal_dialog': { 'transform': 'translateY(0)' },
2168
2196
  '.bw_modal_sm': { 'max-width': '300px' },
@@ -2192,10 +2220,11 @@ var structuralRules = {
2192
2220
  '.bw_toast_container.bw_toast_bottom_center': { 'bottom': '0', 'left': '50%', 'transform': 'translateX(-50%)' },
2193
2221
  '.bw_toast': {
2194
2222
  'pointer-events': 'auto', 'width': '350px', 'max-width': 'calc(100vw - 2rem)', 'background-clip': 'padding-box',
2195
- 'opacity': '0'
2223
+ 'opacity': '0', 'transform': 'translateY(-8px)',
2224
+ 'transition': 'opacity 0.2s ease-out, transform 0.2s ease-out'
2196
2225
  },
2197
2226
  '.bw_toast.bw_toast_show': { 'opacity': '1', 'transform': 'translateY(0)' },
2198
- '.bw_toast.bw_toast_hiding': { 'opacity': '0' },
2227
+ '.bw_toast.bw_toast_hiding': { 'opacity': '0', 'transform': 'translateY(-8px)' },
2199
2228
  '.bw_toast_header': { 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '0.5rem 0.75rem', 'font-size': '0.875rem', 'border-bottom': '1px solid transparent' },
2200
2229
  '.bw_toast_body': { 'padding': '0.5rem 0.75rem', 'font-size': '0.9375rem' }
2201
2230
  },
@@ -2212,9 +2241,11 @@ var structuralRules = {
2212
2241
  'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': 'block',
2213
2242
  'min-width': '10rem', 'padding': '0.5rem 0', 'margin': '0.125rem 0 0',
2214
2243
  'background-clip': 'padding-box', 'border': '1px solid transparent',
2215
- 'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none'
2244
+ 'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none',
2245
+ 'transform': 'translateY(-4px)',
2246
+ 'transition': 'opacity 0.15s ease-out, transform 0.15s ease-out, visibility 0.15s ease-out'
2216
2247
  },
2217
- '.bw_dropdown_menu.bw_dropdown_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
2248
+ '.bw_dropdown_menu.bw_dropdown_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto', 'transform': 'translateY(0)' },
2218
2249
  '.bw_dropdown_menu_end': { 'left': 'auto', 'right': '0' },
2219
2250
  '.bw_dropdown_item': {
2220
2251
  'display': 'block', 'width': '100%', 'padding': '0.4rem 1rem', 'clear': 'both',
@@ -2233,7 +2264,8 @@ var structuralRules = {
2233
2264
  'appearance': 'none',
2234
2265
  'background-image': "url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba(255,255,255,1)'/%3e%3c/svg%3e\")",
2235
2266
  'background-position': 'left center', 'background-repeat': 'no-repeat',
2236
- 'background-size': 'contain', 'cursor': 'pointer'
2267
+ 'background-size': 'contain', 'cursor': 'pointer',
2268
+ 'transition': 'background-color 0.15s ease-out, background-position 0.15s ease-out, border-color 0.15s ease-out'
2237
2269
  },
2238
2270
  '.bw_form_switch .bw_switch_input:checked': { 'background-position': 'right center' },
2239
2271
  '.bw_form_switch .bw_switch_input:disabled': { 'opacity': '0.5', 'cursor': 'not-allowed' }
@@ -2267,9 +2299,7 @@ var structuralRules = {
2267
2299
  '.bw_stat_card': {
2268
2300
  'padding': '1.25rem',
2269
2301
  'border-left': '4px solid transparent',
2270
- 'border-radius': '0.375rem',
2271
- 'background-color': 'inherit',
2272
- 'transition': 'transform 0.15s ease'
2302
+ 'background-color': 'inherit'
2273
2303
  },
2274
2304
  '.bw_stat_card:hover': { 'transform': 'translateY(-1px)' },
2275
2305
  '.bw_stat_icon': { 'font-size': '1.5rem', 'margin-bottom': '0.5rem' },
@@ -2329,7 +2359,8 @@ var structuralRules = {
2329
2359
  'width': '1.5rem', 'height': '1.5rem',
2330
2360
  'border': 'none', 'background': 'none',
2331
2361
  'font-size': '1.25rem', 'cursor': 'pointer', 'padding': '0', 'border-radius': '50%'
2332
- }
2362
+ },
2363
+ '.bw_search_clear:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '2px' }
2333
2364
  },
2334
2365
 
2335
2366
  // ---- Range ----
@@ -2421,6 +2452,7 @@ var structuralRules = {
2421
2452
  'width': '1rem', 'height': '1rem', 'border': 'none', 'background': 'none',
2422
2453
  'font-size': '0.875rem', 'cursor': 'pointer', 'padding': '0', 'border-radius': '50%'
2423
2454
  },
2455
+ '.bw_chip_remove:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '1px' },
2424
2456
  '.bw_chip_field': { 'flex': '1', 'min-width': '80px', 'border': 'none', 'outline': 'none', 'font-size': '0.875rem', 'padding': '0.125rem 0', 'background': 'transparent' }
2425
2457
  },
2426
2458
 
@@ -3460,6 +3492,287 @@ function repeatUntil(testFn, successFn, failFn, delay = 250, maxReps = 10, lastF
3460
3492
  return intervalID;
3461
3493
  }
3462
3494
 
3495
+ /**
3496
+ * Bitwrench Router -- client-side URL routing for SPAs
3497
+ *
3498
+ * Single export: initRouter(bw) attaches bw.router(), bw.navigate(), bw.link()
3499
+ *
3500
+ * @license BSD-2-Clause
3501
+ */
3502
+
3503
+ // -- internal helpers --
3504
+
3505
+ function normalizePath(p) {
3506
+ // strip query string (handled separately)
3507
+ var qi = p.indexOf('?');
3508
+ if (qi >= 0) p = p.substring(0, qi);
3509
+ // collapse double slashes, strip trailing slash
3510
+ p = p.replace(/\/\/+/g, '/');
3511
+ if (p.length > 1 && p.charAt(p.length - 1) === '/') p = p.substring(0, p.length - 1);
3512
+ return p || '/';
3513
+ }
3514
+
3515
+ function parseQuery(fullPath) {
3516
+ var qi = fullPath.indexOf('?');
3517
+ if (qi < 0) return {};
3518
+ var qs = fullPath.substring(qi + 1);
3519
+ var result = {};
3520
+ var pairs = qs.split('&');
3521
+ for (var i = 0; i < pairs.length; i++) {
3522
+ var kv = pairs[i].split('=');
3523
+ if (kv[0]) result[decodeURIComponent(kv[0])] = kv.length > 1 ? decodeURIComponent(kv[1]) : '';
3524
+ }
3525
+ return result;
3526
+ }
3527
+
3528
+ function matchRoute(routes, rawPath) {
3529
+ var query = parseQuery(rawPath);
3530
+ var path = normalizePath(rawPath);
3531
+ var segs = path === '/' ? [''] : path.split('/');
3532
+
3533
+ var globalWild = null;
3534
+
3535
+ for (var i = 0; i < routes.length; i++) {
3536
+ var r = routes[i];
3537
+ var pattern = r.pattern;
3538
+
3539
+ // global wildcard -- save for last
3540
+ if (pattern === '*') { globalWild = r; continue; }
3541
+
3542
+ // catch-all: ends with /*
3543
+ if (pattern.length > 1 && pattern.substring(pattern.length - 2) === '/*') {
3544
+ var prefix = pattern.substring(0, pattern.length - 2);
3545
+ var prefixSegs = prefix === '' ? [''] : prefix.split('/');
3546
+ if (segs.length < prefixSegs.length) continue;
3547
+ var params = {};
3548
+ var ok = true;
3549
+ for (var j = 0; j < prefixSegs.length; j++) {
3550
+ if (prefixSegs[j].charAt(0) === ':') {
3551
+ params[prefixSegs[j].substring(1)] = segs[j];
3552
+ } else if (prefixSegs[j] !== segs[j]) {
3553
+ ok = false; break;
3554
+ }
3555
+ }
3556
+ if (ok) {
3557
+ params._rest = segs.slice(prefixSegs.length).join('/');
3558
+ params._query = query;
3559
+ return { handler: r.handler, params: params };
3560
+ }
3561
+ continue;
3562
+ }
3563
+
3564
+ // exact / parameterized match
3565
+ var rSegs = pattern === '/' ? [''] : pattern.split('/');
3566
+ if (rSegs.length !== segs.length) continue;
3567
+ var params2 = {};
3568
+ var match = true;
3569
+ for (var k = 0; k < rSegs.length; k++) {
3570
+ if (rSegs[k].charAt(0) === ':') {
3571
+ params2[rSegs[k].substring(1)] = segs[k];
3572
+ } else if (rSegs[k] !== segs[k]) {
3573
+ match = false; break;
3574
+ }
3575
+ }
3576
+ if (match) {
3577
+ params2._query = query;
3578
+ return { handler: r.handler, params: params2 };
3579
+ }
3580
+ }
3581
+
3582
+ // global wildcard fallback
3583
+ if (globalWild) {
3584
+ return { handler: globalWild.handler, params: { _query: query } };
3585
+ }
3586
+ return null;
3587
+ }
3588
+
3589
+
3590
+ // -- public API factory --
3591
+
3592
+ function initRouter(bw) {
3593
+ var _activeRouter = null;
3594
+
3595
+ bw.router = function(config) {
3596
+ if (!config || !config.routes) throw new Error('bw.router: config.routes is required');
3597
+ if (!bw._isBrowser) throw new Error('bw.router: requires a browser environment');
3598
+
3599
+ var mode = config.mode || 'hash';
3600
+ var base = config.base || '/';
3601
+ if (base.length > 1 && base.charAt(base.length - 1) === '/') base = base.substring(0, base.length - 1);
3602
+ var target = config.target || null;
3603
+
3604
+ // compile routes (preserve registration order)
3605
+ var routes = [];
3606
+ var keys = Object.keys(config.routes);
3607
+ for (var i = 0; i < keys.length; i++) {
3608
+ routes.push({ pattern: keys[i], handler: config.routes[keys[i]] });
3609
+ }
3610
+
3611
+ var currentPath = '/';
3612
+ var destroyed = false;
3613
+
3614
+ function getPath() {
3615
+ if (mode === 'hash') {
3616
+ var h = window.location.hash.replace(/^#/, '');
3617
+ return h || '/';
3618
+ }
3619
+ var p = window.location.pathname;
3620
+ if (base !== '/' && p.indexOf(base) === 0) {
3621
+ p = p.substring(base.length) || '/';
3622
+ }
3623
+ var s = window.location.search || '';
3624
+ return p + s;
3625
+ }
3626
+
3627
+ function handleRoute(toRaw, opts) {
3628
+ if (destroyed) return;
3629
+ var fromPath = currentPath;
3630
+ var toPath = normalizePath(toRaw);
3631
+
3632
+ // before guard
3633
+ if (config.before) {
3634
+ var result = config.before(toPath, fromPath);
3635
+ if (result === false) return;
3636
+ if (typeof result === 'string') {
3637
+ toPath = normalizePath(result);
3638
+ toRaw = result;
3639
+ }
3640
+ }
3641
+
3642
+ currentPath = toPath;
3643
+
3644
+ // match route
3645
+ var m = matchRoute(routes, toRaw);
3646
+ if (m) {
3647
+ var rendered = m.handler(m.params);
3648
+ if (rendered != null && target) {
3649
+ bw.DOM(target, rendered);
3650
+ }
3651
+ }
3652
+
3653
+ // pub/sub
3654
+ var query = parseQuery(toRaw);
3655
+ bw.pub('bw:route', {
3656
+ path: toPath,
3657
+ params: m ? m.params : {},
3658
+ query: query,
3659
+ from: fromPath
3660
+ });
3661
+
3662
+ // after hook
3663
+ if (config.after) config.after(toPath, fromPath);
3664
+ }
3665
+
3666
+ function navigate(path, opts) {
3667
+ if (destroyed) return;
3668
+ opts = opts || {};
3669
+ if (mode === 'hash') {
3670
+ if (opts.replace) {
3671
+ var loc = window.location;
3672
+ loc.replace(loc.pathname + loc.search + '#' + path);
3673
+ } else {
3674
+ window.location.hash = path;
3675
+ }
3676
+ // hashchange listener will fire handleRoute; but if same hash, trigger manually
3677
+ var currentHash = window.location.hash.replace(/^#/, '') || '/';
3678
+ if (normalizePath(currentHash) === normalizePath(path)) {
3679
+ handleRoute(path);
3680
+ }
3681
+ } else {
3682
+ var url = (base === '/' ? '' : base) + path;
3683
+ if (opts.replace) {
3684
+ window.history.replaceState(null, '', url);
3685
+ } else {
3686
+ window.history.pushState(null, '', url);
3687
+ }
3688
+ handleRoute(path);
3689
+ }
3690
+ }
3691
+
3692
+ function onHashChange() {
3693
+ if (destroyed) return;
3694
+ handleRoute(getPath());
3695
+ }
3696
+
3697
+ function onPopState() {
3698
+ if (destroyed) return;
3699
+ handleRoute(getPath());
3700
+ }
3701
+
3702
+ // listen
3703
+ if (mode === 'hash') {
3704
+ window.addEventListener('hashchange', onHashChange);
3705
+ } else {
3706
+ window.addEventListener('popstate', onPopState);
3707
+ }
3708
+
3709
+ // initial render
3710
+ handleRoute(getPath());
3711
+
3712
+ var routerObj = {
3713
+ navigate: navigate,
3714
+ current: function() {
3715
+ var raw = getPath();
3716
+ var m = matchRoute(routes, raw);
3717
+ return {
3718
+ path: currentPath,
3719
+ params: m ? m.params : {},
3720
+ query: parseQuery(raw)
3721
+ };
3722
+ },
3723
+ destroy: function() {
3724
+ destroyed = true;
3725
+ if (mode === 'hash') {
3726
+ window.removeEventListener('hashchange', onHashChange);
3727
+ } else {
3728
+ window.removeEventListener('popstate', onPopState);
3729
+ }
3730
+ if (_activeRouter === routerObj) _activeRouter = null;
3731
+ }
3732
+ };
3733
+
3734
+ _activeRouter = routerObj;
3735
+ return routerObj;
3736
+ };
3737
+
3738
+ bw.navigate = function(path, opts) {
3739
+ if (_activeRouter) {
3740
+ _activeRouter.navigate(path, opts);
3741
+ } else {
3742
+ if (typeof console !== 'undefined' && console.warn) {
3743
+ console.warn('bw.navigate: no active router');
3744
+ }
3745
+ }
3746
+ };
3747
+
3748
+ bw.link = function(path, content, attrs) {
3749
+ var a = {};
3750
+ if (attrs) {
3751
+ var keys = Object.keys(attrs);
3752
+ for (var i = 0; i < keys.length; i++) a[keys[i]] = attrs[keys[i]];
3753
+ }
3754
+ if (_activeRouter) {
3755
+ a.href = '#' + path;
3756
+ } else {
3757
+ a.href = path;
3758
+ }
3759
+ a.onclick = function(e) {
3760
+ e.preventDefault();
3761
+ bw.navigate(path);
3762
+ };
3763
+ return { t: 'a', a: a, c: content };
3764
+ };
3765
+
3766
+ // expose for testing: internal helpers
3767
+ bw._router = {
3768
+ matchRoute: matchRoute,
3769
+ normalizePath: normalizePath,
3770
+ parseQuery: parseQuery,
3771
+ getActiveRouter: function() { return _activeRouter; },
3772
+ resetActiveRouter: function() { _activeRouter = null; }
3773
+ };
3774
+ }
3775
+
3463
3776
  /**
3464
3777
  * Bitwrench v2 Components
3465
3778
  *
@@ -10856,6 +11169,7 @@ bw.getComponent = function(id) {
10856
11169
  bw.getAllComponents = function() {
10857
11170
  return new Map(bw._componentRegistry);
10858
11171
  };
11172
+ initRouter(bw);
10859
11173
 
10860
11174
  // Register all make functions
10861
11175
  Object.entries(components).forEach(([name, fn]) => {