bitwrench 2.0.20 → 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 +84 -84
  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,18 +1,18 @@
1
- /*! bitwrench-lean v2.0.20 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench-lean v2.0.21 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
2
2
  /**
3
3
  * Auto-generated version file from package.json
4
4
  * DO NOT EDIT DIRECTLY - Use npm run generate-version
5
5
  */
6
6
 
7
7
  const VERSION_INFO = {
8
- version: '2.0.20',
8
+ version: '2.0.21',
9
9
  name: 'bitwrench',
10
10
  description: 'A library for javascript UI functions.',
11
11
  license: 'BSD-2-Clause',
12
12
  homepage: 'https://deftio.github.com/bitwrench/pages',
13
13
  repository: 'git+https://github.com/deftio/bitwrench.git',
14
14
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
15
- buildDate: '2026-03-23T05:19:31.951Z'
15
+ buildDate: '2026-03-24T05:30:22.242Z'
16
16
  };
17
17
 
18
18
  /**
@@ -578,10 +578,10 @@ var ELEVATION_PRESETS = {
578
578
  xl: '0 4px 12px rgba(0,0,0,0.12)'
579
579
  },
580
580
  md: {
581
- sm: '0 1px 3px rgba(0,0,0,0.08)',
582
- md: '0 2px 6px rgba(0,0,0,0.12)',
583
- lg: '0 4px 12px rgba(0,0,0,0.16)',
584
- xl: '0 8px 24px rgba(0,0,0,0.20)'
581
+ sm: '0 1px 3px rgba(0,0,0,0.10), 0 1px 2px rgba(0,0,0,0.06)',
582
+ md: '0 4px 6px rgba(0,0,0,0.10), 0 2px 4px rgba(0,0,0,0.06)',
583
+ lg: '0 10px 15px rgba(0,0,0,0.12), 0 4px 6px rgba(0,0,0,0.08)',
584
+ xl: '0 20px 25px rgba(0,0,0,0.15), 0 8px 10px rgba(0,0,0,0.10)'
585
585
  },
586
586
  lg: {
587
587
  sm: '0 2px 4px rgba(0,0,0,0.10)',
@@ -775,6 +775,9 @@ function generateCards(scope, palette, layout) {
775
775
  rules[_sx(scope, '.bw_card:hover')] = {
776
776
  'box-shadow': elev.md
777
777
  };
778
+ rules[_sx(scope, '.bw_card_hoverable')] = {
779
+ 'transition': 'box-shadow ' + motion.slow + ' ' + motion.easing + ', transform ' + motion.slow + ' ' + motion.easing
780
+ };
778
781
  rules[_sx(scope, '.bw_card_hoverable:hover')] = {
779
782
  'box-shadow': elev.lg
780
783
  };
@@ -875,7 +878,8 @@ function generateNavigation(scope, palette, layout) {
875
878
  };
876
879
  rules[_sx(scope, '.bw_navbar_nav .bw_nav_link')] = {
877
880
  'color': palette.secondary.base,
878
- 'border-radius': layout.radius.btn
881
+ 'border-radius': layout.radius.btn,
882
+ 'transition': 'color ' + layout.motion.fast + ' ' + layout.motion.easing + ', background-color ' + layout.motion.fast + ' ' + layout.motion.easing
879
883
  };
880
884
  rules[_sx(scope, '.bw_navbar_nav .bw_nav_link:hover')] = {
881
885
  'color': palette.dark.base,
@@ -1038,15 +1042,18 @@ function generatePagination(scope, palette, layout) {
1038
1042
  return rules;
1039
1043
  }
1040
1044
 
1041
- function generateProgress(scope, palette) {
1045
+ function generateProgress(scope, palette, layout) {
1042
1046
  var rules = {};
1047
+ var rd = layout ? layout.radius : { badge: '.375rem' };
1043
1048
  rules[_sx(scope, '.bw_progress')] = {
1044
1049
  'background-color': palette.surfaceAlt,
1050
+ 'border-radius': rd.badge,
1045
1051
  'box-shadow': 'inset 0 1px 2px rgba(0,0,0,.1)'
1046
1052
  };
1047
1053
  rules[_sx(scope, '.bw_progress_bar')] = {
1048
1054
  'color': palette.primary.textOn,
1049
1055
  'background-color': palette.primary.base,
1056
+ 'border-radius': 'inherit',
1050
1057
  'box-shadow': 'inset 0 -1px 0 rgba(0,0,0,.15)'
1051
1058
  };
1052
1059
  // Variant progress bar colors handled by palette class
@@ -1170,7 +1177,8 @@ function generateCarouselThemed(scope, palette) {
1170
1177
  };
1171
1178
  rules[_sx(scope, '.bw_carousel_control')] = {
1172
1179
  'background-color': palette.dark.base,
1173
- 'color': palette.dark.textOn
1180
+ 'color': palette.dark.textOn,
1181
+ 'transition': 'background-color 0.15s ease-out'
1174
1182
  };
1175
1183
  rules[_sx(scope, '.bw_carousel_control:hover')] = {
1176
1184
  'background-color': palette.dark.hover
@@ -1184,9 +1192,11 @@ function generateCarouselThemed(scope, palette) {
1184
1192
 
1185
1193
  function generateModalThemed(scope, palette, layout) {
1186
1194
  var rules = {};
1195
+ var rd = layout ? layout.radius : { card: '8px' };
1187
1196
  rules[_sx(scope, '.bw_modal_content')] = {
1188
1197
  'background-color': palette.surface || '#fff',
1189
1198
  'border-color': palette.light.border,
1199
+ 'border-radius': rd.card,
1190
1200
  'box-shadow': layout.elevation.lg
1191
1201
  };
1192
1202
  rules[_sx(scope, '.bw_modal_header')] = {
@@ -1203,9 +1213,11 @@ function generateModalThemed(scope, palette, layout) {
1203
1213
 
1204
1214
  function generateToastThemed(scope, palette, layout) {
1205
1215
  var rules = {};
1216
+ var rd = layout ? layout.radius : { card: '8px' };
1206
1217
  rules[_sx(scope, '.bw_toast')] = {
1207
1218
  'background-color': palette.surface || '#fff',
1208
1219
  'border-color': palette.light.border,
1220
+ 'border-radius': rd.card,
1209
1221
  'box-shadow': layout.elevation.lg
1210
1222
  };
1211
1223
  rules[_sx(scope, '.bw_toast_header')] = {
@@ -1217,9 +1229,11 @@ function generateToastThemed(scope, palette, layout) {
1217
1229
 
1218
1230
  function generateDropdownThemed(scope, palette, layout) {
1219
1231
  var rules = {};
1232
+ var rd = layout ? layout.radius : { card: '8px' };
1220
1233
  rules[_sx(scope, '.bw_dropdown_menu')] = {
1221
1234
  'background-color': palette.surface || '#fff',
1222
1235
  'border-color': palette.light.border,
1236
+ 'border-radius': rd.card,
1223
1237
  'box-shadow': layout.elevation.md
1224
1238
  };
1225
1239
  rules[_sx(scope, '.bw_dropdown_item')] = {
@@ -1252,6 +1266,10 @@ function generateSwitchThemed(scope, palette) {
1252
1266
  rules[_sx(scope, '.bw_form_switch .bw_switch_input:focus')] = {
1253
1267
  'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
1254
1268
  };
1269
+ rules[_sx(scope, '.bw_form_switch .bw_switch_input:focus-visible')] = {
1270
+ 'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus,
1271
+ 'outline': 'none'
1272
+ };
1255
1273
  return rules;
1256
1274
  }
1257
1275
 
@@ -1315,12 +1333,14 @@ function generateStepperThemed(scope, palette) {
1315
1333
  return rules;
1316
1334
  }
1317
1335
 
1318
- function generateChipInputThemed(scope, palette) {
1336
+ function generateChipInputThemed(scope, palette, layout) {
1319
1337
  var rules = {};
1338
+ var rd = layout ? layout.radius : { input: '6px' };
1320
1339
  rules[_sx(scope, '.bw_chip_input')] = {
1321
1340
  'border-color': palette.light.border,
1322
1341
  'background-color': palette.surface || '#fff',
1323
- 'color': palette.dark.base
1342
+ 'color': palette.dark.base,
1343
+ 'border-radius': rd.input
1324
1344
  };
1325
1345
  rules[_sx(scope, '.bw_chip_input:focus-within')] = {
1326
1346
  'border-color': palette.primary.base,
@@ -1605,7 +1625,7 @@ function generateThemedCSS(scopeName, palette, layout) {
1605
1625
  generateTabs(scopeName, palette, layout),
1606
1626
  generateListGroups(scopeName, palette, layout),
1607
1627
  generatePagination(scopeName, palette, layout),
1608
- generateProgress(scopeName, palette),
1628
+ generateProgress(scopeName, palette, layout),
1609
1629
  generateBreadcrumbThemed(scopeName, palette, layout),
1610
1630
  generateCloseButtonThemed(scopeName, palette),
1611
1631
  generateSectionsThemed(scopeName, palette),
@@ -1619,7 +1639,7 @@ function generateThemedCSS(scopeName, palette, layout) {
1619
1639
  generateStatCardThemed(scopeName, palette, layout),
1620
1640
  generateTimelineThemed(scopeName, palette),
1621
1641
  generateStepperThemed(scopeName, palette),
1622
- generateChipInputThemed(scopeName, palette),
1642
+ generateChipInputThemed(scopeName, palette, layout),
1623
1643
  generateFileUploadThemed(scopeName, palette, layout),
1624
1644
  generateRangeThemed(scopeName, palette),
1625
1645
  generateSearchThemed(scopeName, palette, layout),
@@ -1767,7 +1787,7 @@ var structuralRules = {
1767
1787
  '.bw_card_text': { 'margin-bottom': '0', 'font-size': '0.9375rem', 'line-height': '1.6' },
1768
1788
  '.bw_card_header': { 'margin-bottom': '0', 'font-weight': '600', 'font-size': '0.875rem' },
1769
1789
  '.bw_card_footer': { 'font-size': '0.875rem' },
1770
- '.bw_card_hoverable': { 'transition': 'all 0.3s ease-out' },
1790
+ '.bw_card_hoverable': {},
1771
1791
  '.bw_card_hoverable:hover': { 'transform': 'translateY(-4px)' },
1772
1792
  '.bw_card_img_top': { 'width': '100%' },
1773
1793
  '.bw_card_img_bottom': { 'width': '100%' },
@@ -1782,7 +1802,8 @@ var structuralRules = {
1782
1802
  'display': 'block', 'width': '100%',
1783
1803
  'font-size': '0.9375rem', 'font-weight': '400', 'line-height': '1.5',
1784
1804
  'background-clip': 'padding-box', 'appearance': 'none',
1785
- 'border': '1px solid transparent', 'font-family': 'inherit'
1805
+ 'border': '1px solid transparent', 'font-family': 'inherit',
1806
+ 'transition': 'border-color 0.15s ease-out, box-shadow 0.15s ease-out'
1786
1807
  },
1787
1808
  '.bw_form_control:focus': { 'outline': '2px solid currentColor', 'outline-offset': '-1px' },
1788
1809
  '.bw_form_control::placeholder': { 'opacity': '1' },
@@ -1909,6 +1930,7 @@ var structuralRules = {
1909
1930
  'text-decoration': 'none', 'cursor': 'pointer',
1910
1931
  'border': 'none', 'background': 'transparent', 'font-family': 'inherit'
1911
1932
  },
1933
+ '.bw_nav_link:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '-2px' },
1912
1934
  '.bw_nav_tabs .bw_nav_link': { 'border': 'none', 'border-bottom': '2px solid transparent', 'border-radius': '0', 'background-color': 'transparent' },
1913
1935
  '.bw_nav_vertical': { 'flex-direction': 'column' },
1914
1936
  '.bw_tab_content': { 'padding': '1.25rem 0' },
@@ -2026,9 +2048,11 @@ var structuralRules = {
2026
2048
  'display': 'inline-flex', 'align-items': 'center', 'justify-content': 'center',
2027
2049
  'width': '1.5rem', 'height': '1.5rem', 'padding': '0',
2028
2050
  'font-size': '1.25rem', 'font-weight': '700', 'line-height': '1',
2029
- 'background': 'transparent', 'border': '0', 'border-radius': '0.25rem', 'cursor': 'pointer'
2051
+ 'background': 'transparent', 'border': '0', 'border-radius': '0.25rem', 'cursor': 'pointer',
2052
+ 'transition': 'opacity 0.15s ease-out'
2030
2053
  },
2031
- '.bw_close:hover': { 'opacity': '0.75' }
2054
+ '.bw_close:hover': { 'opacity': '0.75' },
2055
+ '.bw_close:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '2px' }
2032
2056
  },
2033
2057
 
2034
2058
  // ---- Stacks ----
@@ -2110,7 +2134,8 @@ var structuralRules = {
2110
2134
  'flex-shrink': '0', 'width': '1.25rem', 'height': '1.25rem', 'margin-left': 'auto',
2111
2135
  'content': '""',
2112
2136
  '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\")",
2113
- 'background-repeat': 'no-repeat', 'background-size': '1.25rem'
2137
+ 'background-repeat': 'no-repeat', 'background-size': '1.25rem',
2138
+ 'transition': 'transform 0.2s ease-out'
2114
2139
  },
2115
2140
  '.bw_accordion_button:not(.bw_collapsed)::after': { 'transform': 'rotate(-180deg)' },
2116
2141
  '.bw_accordion_body': { 'padding': '1rem 1.25rem' },
@@ -2135,6 +2160,7 @@ var structuralRules = {
2135
2160
  'z-index': '2', 'padding': '0'
2136
2161
  },
2137
2162
  '.bw_carousel_control img': { 'width': '20px', 'height': '20px', 'pointer-events': 'none' },
2163
+ '.bw_carousel_control:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '2px' },
2138
2164
  '.bw_carousel_control_prev': { 'left': '10px' },
2139
2165
  '.bw_carousel_control_next': { 'right': '10px' },
2140
2166
  '.bw_carousel_indicators': {
@@ -2154,12 +2180,14 @@ var structuralRules = {
2154
2180
  'display': 'flex', 'align-items': 'center', 'justify-content': 'center',
2155
2181
  'position': 'fixed', 'top': '0', 'left': '0', 'width': '100%', 'height': '100%',
2156
2182
  'z-index': '1050', 'overflow-x': 'hidden', 'overflow-y': 'auto',
2157
- 'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none'
2183
+ 'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none',
2184
+ 'transition': 'opacity 0.2s ease-out, visibility 0.2s ease-out'
2158
2185
  },
2159
2186
  '.bw_modal.bw_modal_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
2160
2187
  '.bw_modal_dialog': {
2161
2188
  'position': 'relative', 'width': 'calc(100% - 1rem)', 'max-width': '500px', 'margin': '1.75rem auto',
2162
- 'pointer-events': 'none'
2189
+ 'pointer-events': 'none', 'transform': 'translateY(-16px)',
2190
+ 'transition': 'transform 0.2s ease-out'
2163
2191
  },
2164
2192
  '.bw_modal.bw_modal_show .bw_modal_dialog': { 'transform': 'translateY(0)' },
2165
2193
  '.bw_modal_sm': { 'max-width': '300px' },
@@ -2189,10 +2217,11 @@ var structuralRules = {
2189
2217
  '.bw_toast_container.bw_toast_bottom_center': { 'bottom': '0', 'left': '50%', 'transform': 'translateX(-50%)' },
2190
2218
  '.bw_toast': {
2191
2219
  'pointer-events': 'auto', 'width': '350px', 'max-width': 'calc(100vw - 2rem)', 'background-clip': 'padding-box',
2192
- 'opacity': '0'
2220
+ 'opacity': '0', 'transform': 'translateY(-8px)',
2221
+ 'transition': 'opacity 0.2s ease-out, transform 0.2s ease-out'
2193
2222
  },
2194
2223
  '.bw_toast.bw_toast_show': { 'opacity': '1', 'transform': 'translateY(0)' },
2195
- '.bw_toast.bw_toast_hiding': { 'opacity': '0' },
2224
+ '.bw_toast.bw_toast_hiding': { 'opacity': '0', 'transform': 'translateY(-8px)' },
2196
2225
  '.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' },
2197
2226
  '.bw_toast_body': { 'padding': '0.5rem 0.75rem', 'font-size': '0.9375rem' }
2198
2227
  },
@@ -2209,9 +2238,11 @@ var structuralRules = {
2209
2238
  'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': 'block',
2210
2239
  'min-width': '10rem', 'padding': '0.5rem 0', 'margin': '0.125rem 0 0',
2211
2240
  'background-clip': 'padding-box', 'border': '1px solid transparent',
2212
- 'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none'
2241
+ 'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none',
2242
+ 'transform': 'translateY(-4px)',
2243
+ 'transition': 'opacity 0.15s ease-out, transform 0.15s ease-out, visibility 0.15s ease-out'
2213
2244
  },
2214
- '.bw_dropdown_menu.bw_dropdown_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' },
2245
+ '.bw_dropdown_menu.bw_dropdown_show': { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto', 'transform': 'translateY(0)' },
2215
2246
  '.bw_dropdown_menu_end': { 'left': 'auto', 'right': '0' },
2216
2247
  '.bw_dropdown_item': {
2217
2248
  'display': 'block', 'width': '100%', 'padding': '0.4rem 1rem', 'clear': 'both',
@@ -2230,7 +2261,8 @@ var structuralRules = {
2230
2261
  'appearance': 'none',
2231
2262
  '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\")",
2232
2263
  'background-position': 'left center', 'background-repeat': 'no-repeat',
2233
- 'background-size': 'contain', 'cursor': 'pointer'
2264
+ 'background-size': 'contain', 'cursor': 'pointer',
2265
+ 'transition': 'background-color 0.15s ease-out, background-position 0.15s ease-out, border-color 0.15s ease-out'
2234
2266
  },
2235
2267
  '.bw_form_switch .bw_switch_input:checked': { 'background-position': 'right center' },
2236
2268
  '.bw_form_switch .bw_switch_input:disabled': { 'opacity': '0.5', 'cursor': 'not-allowed' }
@@ -2264,9 +2296,7 @@ var structuralRules = {
2264
2296
  '.bw_stat_card': {
2265
2297
  'padding': '1.25rem',
2266
2298
  'border-left': '4px solid transparent',
2267
- 'border-radius': '0.375rem',
2268
- 'background-color': 'inherit',
2269
- 'transition': 'transform 0.15s ease'
2299
+ 'background-color': 'inherit'
2270
2300
  },
2271
2301
  '.bw_stat_card:hover': { 'transform': 'translateY(-1px)' },
2272
2302
  '.bw_stat_icon': { 'font-size': '1.5rem', 'margin-bottom': '0.5rem' },
@@ -2326,7 +2356,8 @@ var structuralRules = {
2326
2356
  'width': '1.5rem', 'height': '1.5rem',
2327
2357
  'border': 'none', 'background': 'none',
2328
2358
  'font-size': '1.25rem', 'cursor': 'pointer', 'padding': '0', 'border-radius': '50%'
2329
- }
2359
+ },
2360
+ '.bw_search_clear:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '2px' }
2330
2361
  },
2331
2362
 
2332
2363
  // ---- Range ----
@@ -2418,6 +2449,7 @@ var structuralRules = {
2418
2449
  'width': '1rem', 'height': '1rem', 'border': 'none', 'background': 'none',
2419
2450
  'font-size': '0.875rem', 'cursor': 'pointer', 'padding': '0', 'border-radius': '50%'
2420
2451
  },
2452
+ '.bw_chip_remove:focus-visible': { 'outline': '2px solid currentColor', 'outline-offset': '1px' },
2421
2453
  '.bw_chip_field': { 'flex': '1', 'min-width': '80px', 'border': 'none', 'outline': 'none', 'font-size': '0.875rem', 'padding': '0.125rem 0', 'background': 'transparent' }
2422
2454
  },
2423
2455
 
@@ -3457,6 +3489,287 @@ function repeatUntil(testFn, successFn, failFn, delay = 250, maxReps = 10, lastF
3457
3489
  return intervalID;
3458
3490
  }
3459
3491
 
3492
+ /**
3493
+ * Bitwrench Router -- client-side URL routing for SPAs
3494
+ *
3495
+ * Single export: initRouter(bw) attaches bw.router(), bw.navigate(), bw.link()
3496
+ *
3497
+ * @license BSD-2-Clause
3498
+ */
3499
+
3500
+ // -- internal helpers --
3501
+
3502
+ function normalizePath(p) {
3503
+ // strip query string (handled separately)
3504
+ var qi = p.indexOf('?');
3505
+ if (qi >= 0) p = p.substring(0, qi);
3506
+ // collapse double slashes, strip trailing slash
3507
+ p = p.replace(/\/\/+/g, '/');
3508
+ if (p.length > 1 && p.charAt(p.length - 1) === '/') p = p.substring(0, p.length - 1);
3509
+ return p || '/';
3510
+ }
3511
+
3512
+ function parseQuery(fullPath) {
3513
+ var qi = fullPath.indexOf('?');
3514
+ if (qi < 0) return {};
3515
+ var qs = fullPath.substring(qi + 1);
3516
+ var result = {};
3517
+ var pairs = qs.split('&');
3518
+ for (var i = 0; i < pairs.length; i++) {
3519
+ var kv = pairs[i].split('=');
3520
+ if (kv[0]) result[decodeURIComponent(kv[0])] = kv.length > 1 ? decodeURIComponent(kv[1]) : '';
3521
+ }
3522
+ return result;
3523
+ }
3524
+
3525
+ function matchRoute(routes, rawPath) {
3526
+ var query = parseQuery(rawPath);
3527
+ var path = normalizePath(rawPath);
3528
+ var segs = path === '/' ? [''] : path.split('/');
3529
+
3530
+ var globalWild = null;
3531
+
3532
+ for (var i = 0; i < routes.length; i++) {
3533
+ var r = routes[i];
3534
+ var pattern = r.pattern;
3535
+
3536
+ // global wildcard -- save for last
3537
+ if (pattern === '*') { globalWild = r; continue; }
3538
+
3539
+ // catch-all: ends with /*
3540
+ if (pattern.length > 1 && pattern.substring(pattern.length - 2) === '/*') {
3541
+ var prefix = pattern.substring(0, pattern.length - 2);
3542
+ var prefixSegs = prefix === '' ? [''] : prefix.split('/');
3543
+ if (segs.length < prefixSegs.length) continue;
3544
+ var params = {};
3545
+ var ok = true;
3546
+ for (var j = 0; j < prefixSegs.length; j++) {
3547
+ if (prefixSegs[j].charAt(0) === ':') {
3548
+ params[prefixSegs[j].substring(1)] = segs[j];
3549
+ } else if (prefixSegs[j] !== segs[j]) {
3550
+ ok = false; break;
3551
+ }
3552
+ }
3553
+ if (ok) {
3554
+ params._rest = segs.slice(prefixSegs.length).join('/');
3555
+ params._query = query;
3556
+ return { handler: r.handler, params: params };
3557
+ }
3558
+ continue;
3559
+ }
3560
+
3561
+ // exact / parameterized match
3562
+ var rSegs = pattern === '/' ? [''] : pattern.split('/');
3563
+ if (rSegs.length !== segs.length) continue;
3564
+ var params2 = {};
3565
+ var match = true;
3566
+ for (var k = 0; k < rSegs.length; k++) {
3567
+ if (rSegs[k].charAt(0) === ':') {
3568
+ params2[rSegs[k].substring(1)] = segs[k];
3569
+ } else if (rSegs[k] !== segs[k]) {
3570
+ match = false; break;
3571
+ }
3572
+ }
3573
+ if (match) {
3574
+ params2._query = query;
3575
+ return { handler: r.handler, params: params2 };
3576
+ }
3577
+ }
3578
+
3579
+ // global wildcard fallback
3580
+ if (globalWild) {
3581
+ return { handler: globalWild.handler, params: { _query: query } };
3582
+ }
3583
+ return null;
3584
+ }
3585
+
3586
+
3587
+ // -- public API factory --
3588
+
3589
+ function initRouter(bw) {
3590
+ var _activeRouter = null;
3591
+
3592
+ bw.router = function(config) {
3593
+ if (!config || !config.routes) throw new Error('bw.router: config.routes is required');
3594
+ if (!bw._isBrowser) throw new Error('bw.router: requires a browser environment');
3595
+
3596
+ var mode = config.mode || 'hash';
3597
+ var base = config.base || '/';
3598
+ if (base.length > 1 && base.charAt(base.length - 1) === '/') base = base.substring(0, base.length - 1);
3599
+ var target = config.target || null;
3600
+
3601
+ // compile routes (preserve registration order)
3602
+ var routes = [];
3603
+ var keys = Object.keys(config.routes);
3604
+ for (var i = 0; i < keys.length; i++) {
3605
+ routes.push({ pattern: keys[i], handler: config.routes[keys[i]] });
3606
+ }
3607
+
3608
+ var currentPath = '/';
3609
+ var destroyed = false;
3610
+
3611
+ function getPath() {
3612
+ if (mode === 'hash') {
3613
+ var h = window.location.hash.replace(/^#/, '');
3614
+ return h || '/';
3615
+ }
3616
+ var p = window.location.pathname;
3617
+ if (base !== '/' && p.indexOf(base) === 0) {
3618
+ p = p.substring(base.length) || '/';
3619
+ }
3620
+ var s = window.location.search || '';
3621
+ return p + s;
3622
+ }
3623
+
3624
+ function handleRoute(toRaw, opts) {
3625
+ if (destroyed) return;
3626
+ var fromPath = currentPath;
3627
+ var toPath = normalizePath(toRaw);
3628
+
3629
+ // before guard
3630
+ if (config.before) {
3631
+ var result = config.before(toPath, fromPath);
3632
+ if (result === false) return;
3633
+ if (typeof result === 'string') {
3634
+ toPath = normalizePath(result);
3635
+ toRaw = result;
3636
+ }
3637
+ }
3638
+
3639
+ currentPath = toPath;
3640
+
3641
+ // match route
3642
+ var m = matchRoute(routes, toRaw);
3643
+ if (m) {
3644
+ var rendered = m.handler(m.params);
3645
+ if (rendered != null && target) {
3646
+ bw.DOM(target, rendered);
3647
+ }
3648
+ }
3649
+
3650
+ // pub/sub
3651
+ var query = parseQuery(toRaw);
3652
+ bw.pub('bw:route', {
3653
+ path: toPath,
3654
+ params: m ? m.params : {},
3655
+ query: query,
3656
+ from: fromPath
3657
+ });
3658
+
3659
+ // after hook
3660
+ if (config.after) config.after(toPath, fromPath);
3661
+ }
3662
+
3663
+ function navigate(path, opts) {
3664
+ if (destroyed) return;
3665
+ opts = opts || {};
3666
+ if (mode === 'hash') {
3667
+ if (opts.replace) {
3668
+ var loc = window.location;
3669
+ loc.replace(loc.pathname + loc.search + '#' + path);
3670
+ } else {
3671
+ window.location.hash = path;
3672
+ }
3673
+ // hashchange listener will fire handleRoute; but if same hash, trigger manually
3674
+ var currentHash = window.location.hash.replace(/^#/, '') || '/';
3675
+ if (normalizePath(currentHash) === normalizePath(path)) {
3676
+ handleRoute(path);
3677
+ }
3678
+ } else {
3679
+ var url = (base === '/' ? '' : base) + path;
3680
+ if (opts.replace) {
3681
+ window.history.replaceState(null, '', url);
3682
+ } else {
3683
+ window.history.pushState(null, '', url);
3684
+ }
3685
+ handleRoute(path);
3686
+ }
3687
+ }
3688
+
3689
+ function onHashChange() {
3690
+ if (destroyed) return;
3691
+ handleRoute(getPath());
3692
+ }
3693
+
3694
+ function onPopState() {
3695
+ if (destroyed) return;
3696
+ handleRoute(getPath());
3697
+ }
3698
+
3699
+ // listen
3700
+ if (mode === 'hash') {
3701
+ window.addEventListener('hashchange', onHashChange);
3702
+ } else {
3703
+ window.addEventListener('popstate', onPopState);
3704
+ }
3705
+
3706
+ // initial render
3707
+ handleRoute(getPath());
3708
+
3709
+ var routerObj = {
3710
+ navigate: navigate,
3711
+ current: function() {
3712
+ var raw = getPath();
3713
+ var m = matchRoute(routes, raw);
3714
+ return {
3715
+ path: currentPath,
3716
+ params: m ? m.params : {},
3717
+ query: parseQuery(raw)
3718
+ };
3719
+ },
3720
+ destroy: function() {
3721
+ destroyed = true;
3722
+ if (mode === 'hash') {
3723
+ window.removeEventListener('hashchange', onHashChange);
3724
+ } else {
3725
+ window.removeEventListener('popstate', onPopState);
3726
+ }
3727
+ if (_activeRouter === routerObj) _activeRouter = null;
3728
+ }
3729
+ };
3730
+
3731
+ _activeRouter = routerObj;
3732
+ return routerObj;
3733
+ };
3734
+
3735
+ bw.navigate = function(path, opts) {
3736
+ if (_activeRouter) {
3737
+ _activeRouter.navigate(path, opts);
3738
+ } else {
3739
+ if (typeof console !== 'undefined' && console.warn) {
3740
+ console.warn('bw.navigate: no active router');
3741
+ }
3742
+ }
3743
+ };
3744
+
3745
+ bw.link = function(path, content, attrs) {
3746
+ var a = {};
3747
+ if (attrs) {
3748
+ var keys = Object.keys(attrs);
3749
+ for (var i = 0; i < keys.length; i++) a[keys[i]] = attrs[keys[i]];
3750
+ }
3751
+ if (_activeRouter) {
3752
+ a.href = '#' + path;
3753
+ } else {
3754
+ a.href = path;
3755
+ }
3756
+ a.onclick = function(e) {
3757
+ e.preventDefault();
3758
+ bw.navigate(path);
3759
+ };
3760
+ return { t: 'a', a: a, c: content };
3761
+ };
3762
+
3763
+ // expose for testing: internal helpers
3764
+ bw._router = {
3765
+ matchRoute: matchRoute,
3766
+ normalizePath: normalizePath,
3767
+ parseQuery: parseQuery,
3768
+ getActiveRouter: function() { return _activeRouter; },
3769
+ resetActiveRouter: function() { _activeRouter = null; }
3770
+ };
3771
+ }
3772
+
3460
3773
  /**
3461
3774
  * Empty stub for bitwrench-bccl.js.
3462
3775
  * Used by the lean build to exclude all BCCL component code.
@@ -7023,6 +7336,7 @@ bw.getComponent = function(id) {
7023
7336
  bw.getAllComponents = function() {
7024
7337
  return new Map(bw._componentRegistry);
7025
7338
  };
7339
+ initRouter(bw);
7026
7340
 
7027
7341
  // Register all make functions
7028
7342
  Object.entries(components).forEach(([name, fn]) => {