bitwrench 2.0.17 → 2.0.19

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 (72) hide show
  1. package/README.md +169 -75
  2. package/dist/bitwrench-bccl.cjs.js +228 -55
  3. package/dist/bitwrench-bccl.cjs.min.js +3 -3
  4. package/dist/bitwrench-bccl.esm.js +228 -55
  5. package/dist/bitwrench-bccl.esm.min.js +3 -3
  6. package/dist/bitwrench-bccl.umd.js +228 -55
  7. package/dist/bitwrench-bccl.umd.min.js +3 -3
  8. package/dist/bitwrench-code-edit.cjs.js +7 -9
  9. package/dist/bitwrench-code-edit.cjs.min.js +5 -7
  10. package/dist/bitwrench-code-edit.es5.js +6 -8
  11. package/dist/bitwrench-code-edit.es5.min.js +5 -7
  12. package/dist/bitwrench-code-edit.esm.js +7 -9
  13. package/dist/bitwrench-code-edit.esm.min.js +5 -7
  14. package/dist/bitwrench-code-edit.umd.js +7 -9
  15. package/dist/bitwrench-code-edit.umd.min.js +5 -7
  16. package/dist/bitwrench-debug.js +268 -0
  17. package/dist/bitwrench-debug.min.js +3 -0
  18. package/dist/bitwrench-lean.cjs.js +1190 -2348
  19. package/dist/bitwrench-lean.cjs.min.js +20 -20
  20. package/dist/bitwrench-lean.es5.js +1285 -2551
  21. package/dist/bitwrench-lean.es5.min.js +18 -18
  22. package/dist/bitwrench-lean.esm.js +1190 -2348
  23. package/dist/bitwrench-lean.esm.min.js +20 -20
  24. package/dist/bitwrench-lean.umd.js +1190 -2348
  25. package/dist/bitwrench-lean.umd.min.js +20 -20
  26. package/dist/bitwrench-util-css.cjs.js +236 -0
  27. package/dist/bitwrench-util-css.cjs.min.js +22 -0
  28. package/dist/bitwrench-util-css.es5.js +414 -0
  29. package/dist/bitwrench-util-css.es5.min.js +21 -0
  30. package/dist/bitwrench-util-css.esm.js +230 -0
  31. package/dist/bitwrench-util-css.esm.min.js +21 -0
  32. package/dist/bitwrench-util-css.umd.js +242 -0
  33. package/dist/bitwrench-util-css.umd.min.js +21 -0
  34. package/dist/bitwrench.cjs.js +1404 -2388
  35. package/dist/bitwrench.cjs.min.js +21 -21
  36. package/dist/bitwrench.css +503 -132
  37. package/dist/bitwrench.es5.js +1588 -2659
  38. package/dist/bitwrench.es5.min.js +19 -19
  39. package/dist/bitwrench.esm.js +1405 -2389
  40. package/dist/bitwrench.esm.min.js +21 -21
  41. package/dist/bitwrench.min.css +1 -1
  42. package/dist/bitwrench.umd.js +1404 -2388
  43. package/dist/bitwrench.umd.min.js +21 -21
  44. package/dist/builds.json +214 -104
  45. package/dist/bwserve.cjs.js +514 -68
  46. package/dist/bwserve.esm.js +513 -69
  47. package/dist/sri.json +46 -36
  48. package/package.json +6 -3
  49. package/readme.html +183 -85
  50. package/src/bitwrench-bccl-entry.js +3 -4
  51. package/src/bitwrench-bccl.js +224 -50
  52. package/src/bitwrench-code-edit.js +6 -8
  53. package/src/bitwrench-color-utils.js +31 -9
  54. package/src/bitwrench-debug.js +245 -0
  55. package/src/bitwrench-esm-entry.js +11 -0
  56. package/src/bitwrench-styles.js +474 -240
  57. package/src/bitwrench-util-css.js +229 -0
  58. package/src/bitwrench.js +689 -2042
  59. package/src/bwserve/attach.js +57 -0
  60. package/src/bwserve/bwclient.js +141 -0
  61. package/src/bwserve/bwshell.js +102 -0
  62. package/src/bwserve/client.js +151 -1
  63. package/src/bwserve/index.js +127 -28
  64. package/src/cli/attach.js +587 -0
  65. package/src/cli/convert.js +2 -5
  66. package/src/cli/index.js +7 -0
  67. package/src/cli/inject.js +1 -1
  68. package/src/cli/serve.js +185 -5
  69. package/src/generate-css.js +11 -4
  70. package/src/vendor/html2canvas.min.js +20 -0
  71. package/src/version.js +3 -3
  72. package/src/bwserve/shell.js +0 -106
@@ -172,7 +172,12 @@ export function makeCard(props = {}) {
172
172
  },
173
173
  o: {
174
174
  type: 'card',
175
- state: props.state || {}
175
+ state: props.state || {},
176
+ slots: {
177
+ title: '.bw_card_title',
178
+ content: '.bw_card_body',
179
+ footer: '.bw_card_footer'
180
+ }
176
181
  }
177
182
  };
178
183
  }
@@ -183,7 +188,12 @@ export function makeCard(props = {}) {
183
188
  c: cardContent,
184
189
  o: {
185
190
  type: 'card',
186
- state: props.state || {}
191
+ state: props.state || {},
192
+ slots: {
193
+ title: '.bw_card_title',
194
+ content: '.bw_card_body',
195
+ footer: '.bw_card_footer'
196
+ }
187
197
  }
188
198
  };
189
199
  }
@@ -327,7 +337,7 @@ export function makeCol(props = {}) {
327
337
  if (breakpoint === 'xs') {
328
338
  classes.push(`bw_col_${value}`);
329
339
  } else {
330
- classes.push(`bw_col_${breakpoint}-${value}`);
340
+ classes.push(`bw_col_${breakpoint}_${value}`);
331
341
  }
332
342
  });
333
343
  } else if (size) {
@@ -501,6 +511,24 @@ export function makeTabs(props = {}) {
501
511
  }
502
512
  });
503
513
 
514
+ // Shared tab switching logic
515
+ function switchTab(el, index) {
516
+ var allTabs = el.querySelectorAll('.bw_nav_link');
517
+ var allPanes = el.querySelectorAll('.bw_tab_pane');
518
+ if (index < 0 || index >= allTabs.length) return;
519
+ allTabs.forEach(function(t) {
520
+ t.classList.remove('active');
521
+ t.setAttribute('aria-selected', 'false');
522
+ t.setAttribute('tabindex', '-1');
523
+ });
524
+ allPanes.forEach(function(p) { p.classList.remove('active'); });
525
+ allTabs[index].classList.add('active');
526
+ allTabs[index].setAttribute('aria-selected', 'true');
527
+ allTabs[index].setAttribute('tabindex', '0');
528
+ allPanes[index].classList.add('active');
529
+ if (el._bw_state) el._bw_state.activeIndex = index;
530
+ }
531
+
504
532
  return {
505
533
  t: 'div',
506
534
  a: { class: 'bw_tabs' },
@@ -519,24 +547,8 @@ export function makeTabs(props = {}) {
519
547
  role: 'tab',
520
548
  tabindex: index === actualActiveIndex ? '0' : '-1',
521
549
  'aria-selected': index === actualActiveIndex ? 'true' : 'false',
522
- 'data-tab-index': index,
523
550
  onclick: (e) => {
524
- const tabsContainer = e.target.closest('.bw_tabs');
525
- const allTabs = tabsContainer.querySelectorAll('.bw_nav_link');
526
- const allPanes = tabsContainer.querySelectorAll('.bw_tab_pane');
527
-
528
- allTabs.forEach(t => {
529
- t.classList.remove('active');
530
- t.setAttribute('aria-selected', 'false');
531
- t.setAttribute('tabindex', '-1');
532
- });
533
- allPanes.forEach(p => p.classList.remove('active'));
534
-
535
- e.target.classList.add('active');
536
- e.target.setAttribute('aria-selected', 'true');
537
- e.target.setAttribute('tabindex', '0');
538
- const targetIndex = parseInt(e.target.getAttribute('data-tab-index'));
539
- allPanes[targetIndex].classList.add('active');
551
+ switchTab(e.target.closest('.bw_tabs'), index);
540
552
  }
541
553
  },
542
554
  c: tab.label
@@ -559,6 +571,10 @@ export function makeTabs(props = {}) {
559
571
  o: {
560
572
  type: 'tabs',
561
573
  state: { activeIndex: actualActiveIndex },
574
+ handle: {
575
+ setActiveTab: switchTab,
576
+ getActiveTab: function(el) { return (el._bw_state && el._bw_state.activeIndex) || 0; }
577
+ },
562
578
  mounted: function(el) {
563
579
  var tablist = el.querySelector('[role="tablist"]');
564
580
  if (!tablist) return;
@@ -644,7 +660,13 @@ export function makeAlert(props = {}) {
644
660
  },
645
661
  c: '×'
646
662
  }
647
- ].filter(Boolean)
663
+ ].filter(Boolean),
664
+ o: {
665
+ type: 'alert',
666
+ handle: {
667
+ dismiss: function(el) { if (el && el.parentNode) el.parentNode.removeChild(el); }
668
+ }
669
+ }
648
670
  };
649
671
  }
650
672
 
@@ -742,6 +764,24 @@ export function makeProgress(props = {}) {
742
764
  'aria-valuemax': max
743
765
  },
744
766
  c: label || `${percentage}%`
767
+ },
768
+ o: {
769
+ type: 'progress',
770
+ handle: {
771
+ setValue: function(el, n) {
772
+ var bar = el.querySelector('.bw_progress_bar');
773
+ if (!bar) return;
774
+ var maxVal = parseInt(bar.getAttribute('aria-valuemax')) || 100;
775
+ var pct = Math.round((n / maxVal) * 100);
776
+ bar.style.width = pct + '%';
777
+ bar.setAttribute('aria-valuenow', n);
778
+ bar.textContent = pct + '%';
779
+ },
780
+ getValue: function(el) {
781
+ var bar = el.querySelector('.bw_progress_bar');
782
+ return bar ? parseInt(bar.getAttribute('aria-valuenow')) || 0 : 0;
783
+ }
784
+ }
745
785
  }
746
786
  };
747
787
  }
@@ -1710,8 +1750,8 @@ export function makePagination(props = {}) {
1710
1750
  t: 'li',
1711
1751
  a: { class: `bw_page_item ${currentPage <= 1 ? 'bw_disabled' : ''}`.trim() },
1712
1752
  c: {
1713
- t: 'a',
1714
- a: { class: 'bw_page_link', href: '#', onclick: handleClick(currentPage - 1), 'aria-label': 'Previous' },
1753
+ t: 'button',
1754
+ a: { class: 'bw_page_link', type: 'button', onclick: handleClick(currentPage - 1), 'aria-label': 'Previous', disabled: currentPage <= 1 ? true : undefined },
1715
1755
  c: '\u2039'
1716
1756
  }
1717
1757
  });
@@ -1723,8 +1763,8 @@ export function makePagination(props = {}) {
1723
1763
  t: 'li',
1724
1764
  a: { class: `bw_page_item ${pageNum === currentPage ? 'bw_active' : ''}`.trim() },
1725
1765
  c: {
1726
- t: 'a',
1727
- a: { class: 'bw_page_link', href: '#', onclick: handleClick(pageNum) },
1766
+ t: 'button',
1767
+ a: { class: 'bw_page_link', type: 'button', onclick: handleClick(pageNum), 'aria-current': pageNum === currentPage ? 'page' : undefined },
1728
1768
  c: '' + pageNum
1729
1769
  }
1730
1770
  });
@@ -1736,8 +1776,8 @@ export function makePagination(props = {}) {
1736
1776
  t: 'li',
1737
1777
  a: { class: `bw_page_item ${currentPage >= pages ? 'bw_disabled' : ''}`.trim() },
1738
1778
  c: {
1739
- t: 'a',
1740
- a: { class: 'bw_page_link', href: '#', onclick: handleClick(currentPage + 1), 'aria-label': 'Next' },
1779
+ t: 'button',
1780
+ a: { class: 'bw_page_link', type: 'button', onclick: handleClick(currentPage + 1), 'aria-label': 'Next', disabled: currentPage >= pages ? true : undefined },
1741
1781
  c: '\u203A'
1742
1782
  }
1743
1783
  });
@@ -1751,6 +1791,26 @@ export function makePagination(props = {}) {
1751
1791
  class: `bw_pagination ${size ? 'bw_pagination_' + size : ''} ${className}`.trim()
1752
1792
  },
1753
1793
  c: items
1794
+ },
1795
+ o: {
1796
+ type: 'pagination',
1797
+ state: { currentPage: currentPage, pages: pages },
1798
+ handle: {
1799
+ setPage: function(el, n) {
1800
+ if (n < 1 || n > pages) return;
1801
+ var allItems = el.querySelectorAll('.bw_page_item');
1802
+ for (var i = 0; i < allItems.length; i++) {
1803
+ allItems[i].classList.remove('bw_active');
1804
+ }
1805
+ // +1 offset: first item is prev arrow
1806
+ if (allItems[n]) allItems[n].classList.add('bw_active');
1807
+ if (el._bw_state) el._bw_state.currentPage = n;
1808
+ if (onPageChange) onPageChange(n);
1809
+ },
1810
+ getPage: function(el) {
1811
+ return (el._bw_state && el._bw_state.currentPage) || 1;
1812
+ }
1813
+ }
1754
1814
  }
1755
1815
  };
1756
1816
  }
@@ -1899,7 +1959,6 @@ export function makeAccordion(props = {}) {
1899
1959
  class: `bw_accordion_button ${item.open ? '' : 'bw_collapsed'}`.trim(),
1900
1960
  type: 'button',
1901
1961
  'aria-expanded': item.open ? 'true' : 'false',
1902
- 'data-accordion-index': index,
1903
1962
  onclick: function(e) {
1904
1963
  var btn = e.target.closest('.bw_accordion_button');
1905
1964
  var accordionEl = btn.closest('.bw_accordion');
@@ -1974,7 +2033,43 @@ export function makeAccordion(props = {}) {
1974
2033
  }),
1975
2034
  o: {
1976
2035
  type: 'accordion',
1977
- state: { multiOpen: multiOpen }
2036
+ state: { multiOpen: multiOpen },
2037
+ handle: {
2038
+ toggle: function(el, index) {
2039
+ var items = el.querySelectorAll('.bw_accordion_item');
2040
+ if (index < 0 || index >= items.length) return;
2041
+ var btn = items[index].querySelector('.bw_accordion_button');
2042
+ if (btn) btn.click();
2043
+ },
2044
+ openAll: function(el) {
2045
+ var items = el.querySelectorAll('.bw_accordion_item');
2046
+ for (var i = 0; i < items.length; i++) {
2047
+ var collapse = items[i].querySelector('.bw_accordion_collapse');
2048
+ var btn = items[i].querySelector('.bw_accordion_button');
2049
+ if (!collapse.classList.contains('bw_collapse_show')) {
2050
+ collapse.classList.add('bw_collapse_show');
2051
+ collapse.style.maxHeight = 'none';
2052
+ btn.classList.remove('bw_collapsed');
2053
+ btn.setAttribute('aria-expanded', 'true');
2054
+ }
2055
+ }
2056
+ },
2057
+ closeAll: function(el) {
2058
+ var items = el.querySelectorAll('.bw_accordion_item');
2059
+ for (var i = 0; i < items.length; i++) {
2060
+ var collapse = items[i].querySelector('.bw_accordion_collapse');
2061
+ var btn = items[i].querySelector('.bw_accordion_button');
2062
+ if (collapse.classList.contains('bw_collapse_show')) {
2063
+ collapse.style.maxHeight = collapse.scrollHeight + 'px';
2064
+ collapse.offsetHeight;
2065
+ collapse.style.maxHeight = '0px';
2066
+ collapse.classList.remove('bw_collapse_show');
2067
+ btn.classList.add('bw_collapsed');
2068
+ btn.setAttribute('aria-expanded', 'false');
2069
+ }
2070
+ }
2071
+ }
2072
+ }
1978
2073
  }
1979
2074
  };
1980
2075
  }
@@ -2064,6 +2159,14 @@ export function makeModal(props = {}) {
2064
2159
  },
2065
2160
  o: {
2066
2161
  type: 'modal',
2162
+ handle: {
2163
+ open: function(el) {
2164
+ el.classList.add('bw_modal_show');
2165
+ el.style.display = 'flex';
2166
+ document.body.style.overflow = 'hidden';
2167
+ },
2168
+ close: function(el) { closeModal(el); }
2169
+ },
2067
2170
  mounted: function(el) {
2068
2171
  // Click backdrop to close
2069
2172
  el.addEventListener('click', function(e) {
@@ -2122,9 +2225,8 @@ export function makeToast(props = {}) {
2122
2225
  return {
2123
2226
  t: 'div',
2124
2227
  a: {
2125
- class: `bw_toast ${variantClass(variant)} ${className}`.trim(),
2126
- role: 'alert',
2127
- 'data-position': position
2228
+ class: `bw_toast ${variantClass(variant)} bw_toast_${position.replace(/-/g, '_')} ${className}`.trim(),
2229
+ role: 'alert'
2128
2230
  },
2129
2231
  c: [
2130
2232
  (title) && {
@@ -2158,6 +2260,12 @@ export function makeToast(props = {}) {
2158
2260
  ].filter(Boolean),
2159
2261
  o: {
2160
2262
  type: 'toast',
2263
+ handle: {
2264
+ dismiss: function(el) {
2265
+ el.classList.add('bw_toast_hiding');
2266
+ setTimeout(function() { if (el.parentNode) el.parentNode.removeChild(el); }, 300);
2267
+ }
2268
+ },
2161
2269
  mounted: function(el) {
2162
2270
  // Trigger show animation
2163
2271
  requestAnimationFrame(function() {
@@ -2515,7 +2623,7 @@ export function makeCarousel(props = {}) {
2515
2623
  var total = carouselEl.querySelectorAll('.bw_carousel_slide').length;
2516
2624
  if (index < 0) index = total - 1;
2517
2625
  if (index >= total) index = 0;
2518
- carouselEl.setAttribute('data-carousel-index', index);
2626
+ carouselEl._bw_carouselIndex = index;
2519
2627
  var track = carouselEl.querySelector('.bw_carousel_track');
2520
2628
  track.style.transform = 'translateX(-' + (index * 100) + '%)';
2521
2629
  // Update indicators
@@ -2572,7 +2680,7 @@ export function makeCarousel(props = {}) {
2572
2680
  'aria-label': 'Previous slide',
2573
2681
  onclick: function(e) {
2574
2682
  var carousel = e.target.closest('.bw_carousel');
2575
- var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
2683
+ var idx = carousel._bw_carouselIndex || 0;
2576
2684
  goToSlide(carousel, idx - 1);
2577
2685
  }
2578
2686
  },
@@ -2586,7 +2694,7 @@ export function makeCarousel(props = {}) {
2586
2694
  'aria-label': 'Next slide',
2587
2695
  onclick: function(e) {
2588
2696
  var carousel = e.target.closest('.bw_carousel');
2589
- var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
2697
+ var idx = carousel._bw_carouselIndex || 0;
2590
2698
  goToSlide(carousel, idx + 1);
2591
2699
  }
2592
2700
  },
@@ -2606,11 +2714,9 @@ export function makeCarousel(props = {}) {
2606
2714
  class: 'bw_carousel_indicator' + (i === startIndex ? ' active' : ''),
2607
2715
  type: 'button',
2608
2716
  'aria-label': 'Go to slide ' + (i + 1),
2609
- 'data-slide-index': i,
2610
2717
  onclick: function(e) {
2611
2718
  var carousel = e.target.closest('.bw_carousel');
2612
- var idx = parseInt(e.target.getAttribute('data-slide-index'));
2613
- goToSlide(carousel, idx);
2719
+ goToSlide(carousel, i);
2614
2720
  }
2615
2721
  }
2616
2722
  };
@@ -2624,17 +2730,37 @@ export function makeCarousel(props = {}) {
2624
2730
  class: ('bw_carousel ' + className).trim(),
2625
2731
  style: 'height: ' + height,
2626
2732
  tabindex: '0',
2627
- 'aria-roledescription': 'carousel',
2628
- 'data-carousel-index': startIndex
2733
+ 'aria-roledescription': 'carousel'
2629
2734
  },
2630
2735
  c: children,
2631
2736
  o: {
2632
2737
  type: 'carousel',
2633
2738
  state: { activeIndex: startIndex, autoPlay: autoPlay, interval: interval },
2739
+ handle: {
2740
+ goToSlide: function(el, index) { goToSlide(el, index); },
2741
+ next: function(el) { goToSlide(el, (el._bw_carouselIndex || 0) + 1); },
2742
+ prev: function(el) { goToSlide(el, (el._bw_carouselIndex || 0) - 1); },
2743
+ getActiveIndex: function(el) { return el._bw_carouselIndex || 0; },
2744
+ pause: function(el) {
2745
+ if (el._bw_carouselInterval) {
2746
+ clearInterval(el._bw_carouselInterval);
2747
+ el._bw_carouselInterval = null;
2748
+ }
2749
+ },
2750
+ play: function(el) {
2751
+ if (!el._bw_carouselInterval && el._bw_state) {
2752
+ var ms = el._bw_state.interval || 5000;
2753
+ el._bw_carouselInterval = setInterval(function() {
2754
+ goToSlide(el, (el._bw_carouselIndex || 0) + 1);
2755
+ }, ms);
2756
+ }
2757
+ }
2758
+ },
2634
2759
  mounted: function(el) {
2760
+ el._bw_carouselIndex = startIndex;
2635
2761
  // Keyboard navigation
2636
2762
  el.addEventListener('keydown', function(e) {
2637
- var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
2763
+ var idx = el._bw_carouselIndex || 0;
2638
2764
  if (e.key === 'ArrowLeft') {
2639
2765
  e.preventDefault();
2640
2766
  goToSlide(el, idx - 1);
@@ -2646,7 +2772,7 @@ export function makeCarousel(props = {}) {
2646
2772
  // Auto-play
2647
2773
  if (autoPlay) {
2648
2774
  var intervalId = setInterval(function() {
2649
- var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
2775
+ var idx = el._bw_carouselIndex || 0;
2650
2776
  goToSlide(el, idx + 1);
2651
2777
  }, interval);
2652
2778
  el._bw_carouselInterval = intervalId;
@@ -2656,7 +2782,7 @@ export function makeCarousel(props = {}) {
2656
2782
  });
2657
2783
  el.addEventListener('mouseleave', function() {
2658
2784
  el._bw_carouselInterval = setInterval(function() {
2659
- var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
2785
+ var idx = el._bw_carouselIndex || 0;
2660
2786
  goToSlide(el, idx + 1);
2661
2787
  }, interval);
2662
2788
  });
@@ -2772,7 +2898,13 @@ export function makeStatCard(props = {}) {
2772
2898
  t: 'div',
2773
2899
  a: { class: classes, style: style },
2774
2900
  c: children,
2775
- o: { type: 'stat-card' }
2901
+ o: {
2902
+ type: 'stat-card',
2903
+ slots: {
2904
+ value: '.bw_stat_value',
2905
+ label: '.bw_stat_label'
2906
+ }
2907
+ }
2776
2908
  };
2777
2909
  }
2778
2910
 
@@ -3451,7 +3583,7 @@ export function makeChipInput(props = {}) {
3451
3583
  function makeChipEl(text) {
3452
3584
  return {
3453
3585
  t: 'span',
3454
- a: { class: 'bw_chip', 'data-chip-value': text },
3586
+ a: { class: 'bw_chip' },
3455
3587
  c: [
3456
3588
  text,
3457
3589
  {
@@ -3462,9 +3594,8 @@ export function makeChipInput(props = {}) {
3462
3594
  'aria-label': 'Remove ' + text,
3463
3595
  onclick: function(e) {
3464
3596
  var chip = e.target.closest('.bw_chip');
3465
- var val = chip.getAttribute('data-chip-value');
3466
3597
  chip.parentNode.removeChild(chip);
3467
- if (onRemove) onRemove(val);
3598
+ if (onRemove) onRemove(text);
3468
3599
  }
3469
3600
  },
3470
3601
  c: '\u00D7'
@@ -3492,7 +3623,7 @@ export function makeChipInput(props = {}) {
3492
3623
  // Insert chip before the input
3493
3624
  var chipEl = document.createElement('span');
3494
3625
  chipEl.className = 'bw_chip';
3495
- chipEl.setAttribute('data-chip-value', val);
3626
+ chipEl._bw_chipValue = val;
3496
3627
  chipEl.innerHTML = '';
3497
3628
  chipEl.textContent = val;
3498
3629
  var removeBtn = document.createElement('button');
@@ -3515,7 +3646,7 @@ export function makeChipInput(props = {}) {
3515
3646
  var chipEls = wrapper.querySelectorAll('.bw_chip');
3516
3647
  if (chipEls.length) {
3517
3648
  var last = chipEls[chipEls.length - 1];
3518
- var removedVal = last.getAttribute('data-chip-value');
3649
+ var removedVal = last._bw_chipValue || last.firstChild.textContent;
3519
3650
  last.parentNode.removeChild(last);
3520
3651
  if (onRemove) onRemove(removedVal);
3521
3652
  }
@@ -3524,7 +3655,50 @@ export function makeChipInput(props = {}) {
3524
3655
  }
3525
3656
  }
3526
3657
  ],
3527
- o: { type: 'chip-input' }
3658
+ o: {
3659
+ type: 'chip-input',
3660
+ handle: {
3661
+ addChip: function(el, text) {
3662
+ if (!text) return;
3663
+ var input = el.querySelector('.bw_chip_field');
3664
+ var chipEl = document.createElement('span');
3665
+ chipEl.className = 'bw_chip';
3666
+ chipEl._bw_chipValue = text;
3667
+ chipEl.textContent = text;
3668
+ var removeBtn = document.createElement('button');
3669
+ removeBtn.type = 'button';
3670
+ removeBtn.className = 'bw_chip_remove';
3671
+ removeBtn.setAttribute('aria-label', 'Remove ' + text);
3672
+ removeBtn.textContent = '\u00D7';
3673
+ removeBtn.onclick = function() { chipEl.parentNode.removeChild(chipEl); };
3674
+ chipEl.appendChild(removeBtn);
3675
+ el.insertBefore(chipEl, input);
3676
+ },
3677
+ removeChip: function(el, text) {
3678
+ var chips = el.querySelectorAll('.bw_chip');
3679
+ for (var i = 0; i < chips.length; i++) {
3680
+ if ((chips[i]._bw_chipValue || chips[i].firstChild.textContent) === text) {
3681
+ chips[i].parentNode.removeChild(chips[i]);
3682
+ return;
3683
+ }
3684
+ }
3685
+ },
3686
+ getChips: function(el) {
3687
+ var chips = el.querySelectorAll('.bw_chip');
3688
+ var values = [];
3689
+ for (var i = 0; i < chips.length; i++) {
3690
+ values.push(chips[i]._bw_chipValue || chips[i].firstChild.textContent);
3691
+ }
3692
+ return values;
3693
+ },
3694
+ clear: function(el) {
3695
+ var chips = el.querySelectorAll('.bw_chip');
3696
+ for (var i = chips.length - 1; i >= 0; i--) {
3697
+ chips[i].parentNode.removeChild(chips[i]);
3698
+ }
3699
+ }
3700
+ }
3701
+ }
3528
3702
  };
3529
3703
  }
3530
3704
 
@@ -4,11 +4,9 @@
4
4
  * Provides bw.highlight() for tokenizing JS/CSS/HTML into TACO spans,
5
5
  * and bw.codeEditor() for a live editable code block with syntax coloring.
6
6
  *
7
- * Theme integration: The editor chrome (background, text color, font) reads
8
- * from CSS custom properties --bw_code_bg, --bw_code_text, --bw_font_mono,
9
- * falling back to built-in dark values when no theme is active. Syntax
10
- * highlighting colors are intentionally fixed (they are a code color scheme,
11
- * not brand colors). The bw_ce_light class is still supported for manual
7
+ * Theme integration: The editor chrome uses self-contained dark defaults.
8
+ * Syntax highlighting colors are intentionally fixed (they are a code color
9
+ * scheme, not brand colors). The bw_ce_light class is supported for manual
12
10
  * light-mode override.
13
11
  *
14
12
  * Can be loaded standalone (browser script tag after bitwrench.umd.js),
@@ -21,9 +19,9 @@
21
19
  // -- CSS (injected once) ----------------------------------------------
22
20
  var _cssInjected = false;
23
21
  var CSS_TEXT =
24
- '.bw_ce{background:var(--bw_code_bg,#1e293b);border-radius:6px;border:1px solid rgba(255,255,255,0.08);overflow:auto}' +
22
+ '.bw_ce{background:#1e293b;border-radius:6px;border:1px solid rgba(255,255,255,0.08);overflow:auto}' +
25
23
  '.bw_ce pre{margin:0;padding:0}' +
26
- '.bw_ce code{font-family:var(--bw_font_mono,"SF Mono",Monaco,"Cascadia Code",Consolas,monospace);font-size:0.875rem;line-height:1.6;color:var(--bw_code_text,#e2e8f0);outline:none;white-space:pre-wrap;display:block;padding:0.75rem 1rem}' +
24
+ '.bw_ce code{font-family:"SF Mono",Monaco,"Cascadia Code",Consolas,monospace;font-size:0.875rem;line-height:1.6;color:#e2e8f0;outline:none;white-space:pre-wrap;display:block;padding:0.75rem 1rem}' +
27
25
  '.bw_ce code:empty::before{content:"\\200b"}' +
28
26
  '.bw_ce .bw_ce_keyword{color:#c792ea}' +
29
27
  '.bw_ce .bw_ce_string{color:#c3e88d}' +
@@ -64,7 +62,7 @@ var CSS_TEXT =
64
62
  '.bw_ce_light.bw_ce .bw_ce_template_interp{color:#0891b2}' +
65
63
  // Line number gutter (opt-in via lineNumbers option)
66
64
  '.bw_ce_wrap{display:flex;flex-direction:row}' +
67
- '.bw_ce_gutter{flex:0 0 auto;padding:0.75rem 0;text-align:right;user-select:none;-webkit-user-select:none;color:#546e7a;font-family:var(--bw_font_mono,"SF Mono",Monaco,"Cascadia Code",Consolas,monospace);font-size:0.875rem;line-height:1.6;border-right:1px solid rgba(255,255,255,0.08);overflow:hidden}' +
65
+ '.bw_ce_gutter{flex:0 0 auto;padding:0.75rem 0;text-align:right;user-select:none;-webkit-user-select:none;color:#546e7a;font-family:"SF Mono",Monaco,"Cascadia Code",Consolas,monospace;font-size:0.875rem;line-height:1.6;border-right:1px solid rgba(255,255,255,0.08);overflow:hidden}' +
68
66
  '.bw_ce_gutter span{display:block;padding:0 0.5rem 0 0.75rem}' +
69
67
  '.bw_ce_light .bw_ce_gutter{color:#9ca3af;border-right-color:#d8d8d8}';
70
68
 
@@ -289,13 +289,18 @@ export function harmonize(sourceHex, targetHex, amount) {
289
289
  */
290
290
  export function deriveShades(hex) {
291
291
  var rgb = colorParse(hex);
292
+ // For light input colors (L > 75), mixing toward white produces invisible borders.
293
+ // Darken instead so borders remain visible against light backgrounds.
294
+ var borderColor = hexToHsl(hex)[2] > 75
295
+ ? adjustLightness(hex, -18)
296
+ : mixColor(hex, '#ffffff', 0.60);
292
297
  return {
293
298
  base: hex,
294
299
  hover: adjustLightness(hex, -10),
295
300
  active: adjustLightness(hex, -15),
296
301
  light: mixColor(hex, '#ffffff', 0.85),
297
302
  darkText: adjustLightness(hex, -40),
298
- border: mixColor(hex, '#ffffff', 0.60),
303
+ border: borderColor,
299
304
  focus: 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',0.25)',
300
305
  textOn: textOnColor(hex)
301
306
  };
@@ -354,19 +359,27 @@ export function deriveAlternateConfig(config) {
354
359
  alt.secondary = deriveAlternateSeed(config.secondary);
355
360
  alt.tertiary = config.tertiary ? deriveAlternateSeed(config.tertiary) : alt.primary;
356
361
 
357
- // Derive alternate surface colors from primary hue
362
+ // Derive alternate surface colors from primary hue.
363
+ // Check actual page surface brightness (not seed color brightness) to decide
364
+ // whether alternate should be dark or light. The page surface is what the
365
+ // user sees; seeds can be dark while the page is still light (default L=96).
358
366
  var priHsl = hexToHsl(config.primary);
359
367
  var h = priHsl[0];
360
- var isLight = isLightPalette(config);
368
+ var primarySurface = config.surface || hslToHex([h, 8, 96]);
369
+ var isLight = relativeLuminance(primarySurface) > 0.179;
361
370
 
362
371
  if (isLight) {
363
- // Primary is light → alternate needs dark surfaces
372
+ // Page surface is light → alternate needs dark surfaces
364
373
  alt.light = hslToHex([h, Math.min(priHsl[1], 15), 15]);
365
374
  alt.dark = hslToHex([h, 5, 88]);
375
+ alt.surface = hslToHex([h, 12, 18]);
376
+ alt.background = hslToHex([h, 10, 14]);
366
377
  } else {
367
- // Primary is dark → alternate needs light surfaces
378
+ // Page surface is dark → alternate needs light surfaces
368
379
  alt.light = hslToHex([h, Math.min(priHsl[1], 10), 96]);
369
380
  alt.dark = hslToHex([h, 10, 18]);
381
+ alt.surface = hslToHex([h, 8, 96]);
382
+ alt.background = hslToHex([h, 6, 98]);
370
383
  }
371
384
 
372
385
  // Semantic colors: harmonize toward primary, then invert for alternate
@@ -414,10 +427,18 @@ export function derivePalette(config) {
414
427
  var darkBase = config.dark || hslToHex([h, 10, 13]);
415
428
 
416
429
  // Background & surface tokens — tinted with primary hue for theme personality.
417
- // Very subtle: bg at L=98/S=6, surface at L=96/S=8.
430
+ // Saturation high enough that the hue is visible (each theme feels distinct)
431
+ // but low enough to stay neutral and readable.
418
432
  // User can override with config.background / config.surface.
419
- var bgBase = config.background || hslToHex([h, 6, 98]);
420
- var surfBase = config.surface || hslToHex([h, 8, 96]);
433
+ var bgBase = config.background || hslToHex([h, 22, 96]);
434
+ var surfBase = config.surface || hslToHex([h, 25, 94]);
435
+
436
+ // surfaceAlt: subtle background variant for striped rows, hover states, headers.
437
+ // Slightly lighter than surface in dark mode, slightly darker in light mode.
438
+ var surfHsl = hexToHsl(surfBase);
439
+ var surfAlt = surfHsl[2] <= 50
440
+ ? hslToHex([surfHsl[0], surfHsl[1], Math.min(surfHsl[2] + 8, 100)])
441
+ : hslToHex([surfHsl[0], surfHsl[1], Math.max(surfHsl[2] - 3, 0)]);
421
442
 
422
443
  var palette = {
423
444
  primary: deriveShades(config.primary),
@@ -430,7 +451,8 @@ export function derivePalette(config) {
430
451
  light: deriveShades(lightBase),
431
452
  dark: deriveShades(darkBase),
432
453
  background: bgBase,
433
- surface: surfBase
454
+ surface: surfBase,
455
+ surfaceAlt: surfAlt
434
456
  };
435
457
 
436
458
  return palette;