bitwrench 2.0.11 → 2.0.12

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.
@@ -1,4 +1,4 @@
1
- /*! bitwrench v2.0.11 | BSD-2-Clause | https://deftio.github.com/bitwrench/pages */
1
+ /*! bitwrench v2.0.12 | 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.11',
14
+ version: '2.0.12',
15
15
  name: 'bitwrench',
16
16
  description: 'A library for javascript UI functions.',
17
17
  license: 'BSD-2-Clause',
18
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-07T11:05:08.522Z'
21
+ buildDate: '2026-03-07T22:31:35.755Z'
22
22
  };
23
23
 
24
24
  /**
@@ -893,15 +893,36 @@
893
893
  rules[scopeSelector(scope, '.bw-accordion-button')] = {
894
894
  'color': palette.dark.base
895
895
  };
896
+ rules[scopeSelector(scope, '.bw-accordion-button:not(.bw-collapsed)')] = {
897
+ 'color': palette.primary.darkText,
898
+ 'background-color': palette.primary.light
899
+ };
896
900
  rules[scopeSelector(scope, '.bw-accordion-button:hover')] = {
897
901
  'background-color': palette.light.light
898
902
  };
903
+ rules[scopeSelector(scope, '.bw-accordion-button:not(.bw-collapsed):hover')] = {
904
+ 'background-color': palette.primary.hover
905
+ };
906
+ rules[scopeSelector(scope, '.bw-accordion-button:focus-visible')] = {
907
+ 'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
908
+ };
899
909
  rules[scopeSelector(scope, '.bw-accordion-body')] = {
900
910
  'border-top': '1px solid ' + palette.light.border
901
911
  };
902
912
  return rules;
903
913
  }
904
914
 
915
+ function generateCarouselThemed(scope, palette) {
916
+ var rules = {};
917
+ rules[scopeSelector(scope, '.bw-carousel')] = {
918
+ 'background-color': palette.light.light
919
+ };
920
+ rules[scopeSelector(scope, '.bw-carousel-indicator.active')] = {
921
+ 'background-color': palette.primary.base
922
+ };
923
+ return rules;
924
+ }
925
+
905
926
  function generateModalThemed(scope, palette) {
906
927
  var rules = {};
907
928
  rules[scopeSelector(scope, '.bw-modal-content')] = {
@@ -982,7 +1003,7 @@
982
1003
  function generateSkeletonThemed(scope, palette) {
983
1004
  var rules = {};
984
1005
  rules[scopeSelector(scope, '.bw-skeleton')] = {
985
- 'background-color': palette.light.border
1006
+ 'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.light.light + ' 37%, ' + palette.light.border + ' 63%)'
986
1007
  };
987
1008
  return rules;
988
1009
  }
@@ -1029,6 +1050,7 @@
1029
1050
  generateCloseButtonThemed(scopeName, palette),
1030
1051
  generateSectionsThemed(scopeName, palette),
1031
1052
  generateAccordionThemed(scopeName, palette),
1053
+ generateCarouselThemed(scopeName, palette),
1032
1054
  generateModalThemed(scopeName, palette),
1033
1055
  generateToastThemed(scopeName, palette),
1034
1056
  generateDropdownThemed(scopeName, palette),
@@ -1388,6 +1410,8 @@
1388
1410
  rules['.bw-tab-content'] = { 'padding': '1.25rem 0' };
1389
1411
  rules['.bw-tab-pane'] = { 'display': 'none' };
1390
1412
  rules['.bw-tab-pane.active'] = { 'display': 'block' };
1413
+ rules['.bw-nav-scrollable'] = { 'flex-wrap': 'nowrap', 'overflow-x': 'auto', '-webkit-overflow-scrolling': 'touch', 'scrollbar-width': 'none' };
1414
+ rules['.bw-nav-scrollable .bw-nav-link'] = { 'white-space': 'nowrap' };
1391
1415
 
1392
1416
  // List groups (structural)
1393
1417
  rules['.bw-list-group'] = { 'display': 'flex', 'flex-direction': 'column', 'padding-left': '0', 'margin-bottom': '0', 'border-radius': '0.375rem' };
@@ -1554,6 +1578,29 @@
1554
1578
  rules['.bw-modal-body'] = { 'position': 'relative', 'flex': '1 1 auto', 'padding': '1.5rem' };
1555
1579
  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
1580
 
1581
+ // Carousel (structural)
1582
+ rules['.bw-carousel'] = { 'position': 'relative', 'overflow': 'hidden', 'border-radius': '8px' };
1583
+ rules['.bw-carousel-track'] = { 'display': 'flex', 'transition': 'transform 0.4s ease', 'height': '100%' };
1584
+ rules['.bw-carousel-slide'] = { 'min-width': '100%', 'flex-shrink': '0', 'overflow': 'hidden', 'position': 'relative', 'display': 'flex', 'align-items': 'center', 'justify-content': 'center' };
1585
+ rules['.bw-carousel-slide img'] = { 'width': '100%', 'height': '100%', 'object-fit': 'cover' };
1586
+ rules['.bw-carousel-caption'] = { 'position': 'absolute', 'bottom': '0', 'left': '0', 'right': '0', 'padding': '0.75rem 1rem' };
1587
+ rules['.bw-carousel-control'] = {
1588
+ 'position': 'absolute', 'top': '50%', 'transform': 'translateY(-50%)', 'width': '40px', 'height': '40px',
1589
+ 'border': 'none', 'border-radius': '50%', 'cursor': 'pointer', 'display': 'flex', 'align-items': 'center',
1590
+ 'justify-content': 'center', 'z-index': '2', 'padding': '0', 'transition': 'background-color 0.2s ease'
1591
+ };
1592
+ rules['.bw-carousel-control img'] = { 'width': '20px', 'height': '20px', 'pointer-events': 'none' };
1593
+ rules['.bw-carousel-control-prev'] = { 'left': '10px' };
1594
+ rules['.bw-carousel-control-next'] = { 'right': '10px' };
1595
+ rules['.bw-carousel-indicators'] = {
1596
+ 'position': 'absolute', 'bottom': '12px', 'left': '50%', 'transform': 'translateX(-50%)',
1597
+ 'display': 'flex', 'gap': '6px', 'z-index': '2'
1598
+ };
1599
+ rules['.bw-carousel-indicator'] = {
1600
+ 'width': '10px', 'height': '10px', 'border-radius': '50%', 'border': '2px solid transparent',
1601
+ 'padding': '0', 'cursor': 'pointer', 'transition': 'opacity 0.2s ease, background-color 0.2s ease'
1602
+ };
1603
+
1557
1604
  // Toast (structural)
1558
1605
  rules['.bw-toast-container'] = {
1559
1606
  'position': 'fixed', 'z-index': '1080', 'pointer-events': 'none',
@@ -1603,12 +1650,12 @@
1603
1650
  rules['.bw-form-switch .bw-switch-input:disabled'] = { 'opacity': '0.5', 'cursor': 'not-allowed' };
1604
1651
 
1605
1652
  // Skeleton (structural)
1606
- rules['.bw-skeleton'] = { 'border-radius': '4px', 'animation': 'bw-skeleton-pulse 1.5s ease-in-out infinite' };
1653
+ rules['.bw-skeleton'] = { 'border-radius': '4px', 'background-size': '400% 100%', 'animation': 'bw-skeleton-shimmer 1.4s ease infinite' };
1607
1654
  rules['.bw-skeleton-text'] = { 'height': '1em', 'margin-bottom': '0.5rem' };
1608
1655
  rules['.bw-skeleton-circle'] = { 'border-radius': '50%' };
1609
1656
  rules['.bw-skeleton-rect'] = { 'border-radius': '8px' };
1610
1657
  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' } };
1658
+ rules['@keyframes bw-skeleton-shimmer'] = { '0%': { 'background-position': '100% 50%' }, '100%': { 'background-position': '0 50%' } };
1612
1659
 
1613
1660
  // Avatar (structural)
1614
1661
  rules['.bw-avatar'] = {
@@ -1970,12 +2017,31 @@
1970
2017
  '.bw-dark .bw-accordion-button': {
1971
2018
  'color': textColor
1972
2019
  },
2020
+ '.bw-dark .bw-accordion-button:not(.bw-collapsed)': {
2021
+ 'color': '#7dd3e0',
2022
+ 'background-color': 'rgba(125, 211, 224, 0.1)'
2023
+ },
1973
2024
  '.bw-dark .bw-accordion-button:hover': {
1974
2025
  'background-color': bodyBg
1975
2026
  },
2027
+ '.bw-dark .bw-accordion-button:not(.bw-collapsed):hover': {
2028
+ 'background-color': 'rgba(125, 211, 224, 0.15)'
2029
+ },
2030
+ '.bw-dark .bw-accordion-button:focus-visible': {
2031
+ 'box-shadow': '0 0 0 0.2rem rgba(125, 211, 224, 0.3)'
2032
+ },
1976
2033
  '.bw-dark .bw-accordion-body': {
1977
2034
  'border-top-color': borderColor
1978
2035
  },
2036
+ '.bw-dark .bw-carousel': {
2037
+ 'background-color': bodyBg
2038
+ },
2039
+ '.bw-dark .bw-carousel-control': {
2040
+ 'background-color': 'rgba(255,255,255,0.15)'
2041
+ },
2042
+ '.bw-dark .bw-carousel-control:hover': {
2043
+ 'background-color': 'rgba(255,255,255,0.25)'
2044
+ },
1979
2045
  '.bw-dark .bw-modal-content': {
1980
2046
  'background-color': surfaceBg,
1981
2047
  'border-color': borderColor
@@ -2011,7 +2077,7 @@
2011
2077
  'border-top-color': borderColor
2012
2078
  },
2013
2079
  '.bw-dark .bw-skeleton': {
2014
- 'background-color': borderColor
2080
+ 'background': 'linear-gradient(90deg, ' + borderColor + ' 25%, ' + surfaceBg + ' 37%, ' + borderColor + ' 63%)'
2015
2081
  },
2016
2082
  '.bw-dark h1, .bw-dark h2, .bw-dark h3, .bw-dark h4, .bw-dark h5, .bw-dark h6': {
2017
2083
  'color': textColor
@@ -4761,6 +4827,182 @@
4761
4827
  };
4762
4828
  }
4763
4829
 
4830
+ /**
4831
+ * Create a carousel/slideshow component with slide transitions
4832
+ *
4833
+ * Supports image slides, TACO content slides, captions, prev/next controls,
4834
+ * dot indicators, and optional auto-play. Uses CSS translateX transitions.
4835
+ *
4836
+ * @param {Object} [props] - Carousel configuration
4837
+ * @param {Array<Object>} [props.items=[]] - Slide items
4838
+ * @param {string|Object} props.items[].content - Slide content (TACO, string, or img element)
4839
+ * @param {string} [props.items[].caption] - Caption text shown at bottom of slide
4840
+ * @param {boolean} [props.showControls=true] - Show prev/next arrow buttons
4841
+ * @param {boolean} [props.showIndicators=true] - Show dot navigation
4842
+ * @param {boolean} [props.autoPlay=false] - Auto-advance slides
4843
+ * @param {number} [props.interval=5000] - Auto-advance interval in ms
4844
+ * @param {string} [props.height='300px'] - Carousel height
4845
+ * @param {number} [props.startIndex=0] - Initial slide index
4846
+ * @param {string} [props.className] - Additional CSS classes
4847
+ * @returns {Object} TACO object representing a carousel
4848
+ * @category Component Builders
4849
+ * @example
4850
+ * const carousel = makeCarousel({
4851
+ * items: [
4852
+ * { content: { t: 'img', a: { src: 'photo.jpg' } }, caption: 'Photo 1' },
4853
+ * { content: { t: 'div', c: 'Text slide' } }
4854
+ * ],
4855
+ * autoPlay: true,
4856
+ * interval: 3000
4857
+ * });
4858
+ */
4859
+ function makeCarousel(props = {}) {
4860
+ const {
4861
+ items = [],
4862
+ showControls = true,
4863
+ showIndicators = true,
4864
+ autoPlay = false,
4865
+ interval = 5000,
4866
+ height = '300px',
4867
+ startIndex = 0,
4868
+ className = ''
4869
+ } = props;
4870
+
4871
+ // Shared navigation logic
4872
+ function goToSlide(carouselEl, index) {
4873
+ var total = carouselEl.querySelectorAll('.bw-carousel-slide').length;
4874
+ if (index < 0) index = total - 1;
4875
+ if (index >= total) index = 0;
4876
+ carouselEl.setAttribute('data-carousel-index', index);
4877
+ var track = carouselEl.querySelector('.bw-carousel-track');
4878
+ track.style.transform = 'translateX(-' + (index * 100) + '%)';
4879
+ // Update indicators
4880
+ var indicators = carouselEl.querySelectorAll('.bw-carousel-indicator');
4881
+ for (var i = 0; i < indicators.length; i++) {
4882
+ if (i === index) {
4883
+ indicators[i].classList.add('active');
4884
+ } else {
4885
+ indicators[i].classList.remove('active');
4886
+ }
4887
+ }
4888
+ }
4889
+
4890
+ // Arrow SVGs (inline data URIs, same pattern as accordion chevrons)
4891
+ var prevArrow = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e";
4892
+ var nextArrow = "data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e";
4893
+
4894
+ var slides = items.map(function(item) {
4895
+ var slideContent = [
4896
+ item.content,
4897
+ item.caption && {
4898
+ t: 'div',
4899
+ a: { class: 'bw-carousel-caption' },
4900
+ c: item.caption
4901
+ }
4902
+ ].filter(Boolean);
4903
+
4904
+ return {
4905
+ t: 'div',
4906
+ a: { class: 'bw-carousel-slide' },
4907
+ c: slideContent.length === 1 ? slideContent[0] : slideContent
4908
+ };
4909
+ });
4910
+
4911
+ var children = [
4912
+ // Track
4913
+ {
4914
+ t: 'div',
4915
+ a: {
4916
+ class: 'bw-carousel-track',
4917
+ style: 'transform: translateX(-' + (startIndex * 100) + '%)'
4918
+ },
4919
+ c: slides
4920
+ }
4921
+ ];
4922
+
4923
+ // Prev/Next controls
4924
+ if (showControls && items.length > 1) {
4925
+ children.push({
4926
+ t: 'button',
4927
+ a: {
4928
+ class: 'bw-carousel-control bw-carousel-control-prev',
4929
+ type: 'button',
4930
+ 'aria-label': 'Previous slide',
4931
+ onclick: function(e) {
4932
+ var carousel = e.target.closest('.bw-carousel');
4933
+ var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
4934
+ goToSlide(carousel, idx - 1);
4935
+ }
4936
+ },
4937
+ c: { t: 'img', a: { src: prevArrow, alt: '', role: 'presentation' } }
4938
+ });
4939
+ children.push({
4940
+ t: 'button',
4941
+ a: {
4942
+ class: 'bw-carousel-control bw-carousel-control-next',
4943
+ type: 'button',
4944
+ 'aria-label': 'Next slide',
4945
+ onclick: function(e) {
4946
+ var carousel = e.target.closest('.bw-carousel');
4947
+ var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
4948
+ goToSlide(carousel, idx + 1);
4949
+ }
4950
+ },
4951
+ c: { t: 'img', a: { src: nextArrow, alt: '', role: 'presentation' } }
4952
+ });
4953
+ }
4954
+
4955
+ // Indicators
4956
+ if (showIndicators && items.length > 1) {
4957
+ children.push({
4958
+ t: 'div',
4959
+ a: { class: 'bw-carousel-indicators' },
4960
+ c: items.map(function(_, i) {
4961
+ return {
4962
+ t: 'button',
4963
+ a: {
4964
+ class: 'bw-carousel-indicator' + (i === startIndex ? ' active' : ''),
4965
+ type: 'button',
4966
+ 'aria-label': 'Go to slide ' + (i + 1),
4967
+ 'data-slide-index': i,
4968
+ onclick: function(e) {
4969
+ var carousel = e.target.closest('.bw-carousel');
4970
+ var idx = parseInt(e.target.getAttribute('data-slide-index'));
4971
+ goToSlide(carousel, idx);
4972
+ }
4973
+ }
4974
+ };
4975
+ })
4976
+ });
4977
+ }
4978
+
4979
+ return {
4980
+ t: 'div',
4981
+ a: {
4982
+ class: ('bw-carousel ' + className).trim(),
4983
+ style: 'height: ' + height,
4984
+ 'data-carousel-index': startIndex
4985
+ },
4986
+ c: children,
4987
+ o: {
4988
+ type: 'carousel',
4989
+ state: { activeIndex: startIndex, autoPlay: autoPlay, interval: interval },
4990
+ mounted: autoPlay ? function(el) {
4991
+ var intervalId = setInterval(function() {
4992
+ var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
4993
+ goToSlide(el, idx + 1);
4994
+ }, interval);
4995
+ el._bw_carouselInterval = intervalId;
4996
+ } : undefined,
4997
+ unmount: autoPlay ? function(el) {
4998
+ if (el._bw_carouselInterval) {
4999
+ clearInterval(el._bw_carouselInterval);
5000
+ }
5001
+ } : undefined
5002
+ }
5003
+ };
5004
+ }
5005
+
4764
5006
  const componentHandles = {
4765
5007
  card: CardHandle,
4766
5008
  table: TableHandle,
@@ -4786,6 +5028,7 @@
4786
5028
  makeButtonGroup: makeButtonGroup,
4787
5029
  makeCTA: makeCTA,
4788
5030
  makeCard: makeCard,
5031
+ makeCarousel: makeCarousel,
4789
5032
  makeCheckbox: makeCheckbox,
4790
5033
  makeCodeDemo: makeCodeDemo,
4791
5034
  makeCol: makeCol,
@@ -5235,6 +5478,26 @@
5235
5478
  return str.replace(/[&<>"'/]/g, (char) => escapeMap[char]);
5236
5479
  };
5237
5480
 
5481
+ /**
5482
+ * Mark a string as raw HTML so it will not be escaped by bw.html() or bw.createDOM().
5483
+ *
5484
+ * By default, bitwrench escapes all text content to prevent XSS. Use bw.raw()
5485
+ * when you need to embed pre-sanitized HTML, entities, or inline markup.
5486
+ *
5487
+ * @param {string} str - HTML string to mark as raw
5488
+ * @returns {Object} Marked object recognized by bw.html() and bw.createDOM()
5489
+ * @category DOM Generation
5490
+ * @see bw.escapeHTML
5491
+ * @see bw.html
5492
+ * @example
5493
+ * bw.raw('Hello &mdash; World')
5494
+ * // Used in TACO content:
5495
+ * { t: 'p', c: bw.raw('Price: <strong>$9.99</strong>') }
5496
+ */
5497
+ bw.raw = function(str) {
5498
+ return { __bw_raw: true, v: String(str) };
5499
+ };
5500
+
5238
5501
  /**
5239
5502
  * Normalize CSS class names by converting underscores to hyphens for bw-prefixed classes.
5240
5503
  *
@@ -5286,6 +5549,11 @@
5286
5549
  return taco.map(t => bw.html(t, options)).join('');
5287
5550
  }
5288
5551
 
5552
+ // Handle bw.raw() marked content
5553
+ if (taco && taco.__bw_raw) {
5554
+ return taco.v;
5555
+ }
5556
+
5289
5557
  // Handle primitives and non-TACO objects
5290
5558
  if (typeof taco !== 'object' || !taco.t) {
5291
5559
  return options.raw ? String(taco) : bw.escapeHTML(String(taco));
@@ -5386,12 +5654,21 @@
5386
5654
 
5387
5655
  // Handle null/undefined
5388
5656
  if (taco == null) return document.createTextNode('');
5389
-
5657
+
5658
+ // Handle bw.raw() marked content — inject as HTML
5659
+ if (taco && taco.__bw_raw) {
5660
+ var frag = document.createDocumentFragment();
5661
+ var tmp = document.createElement('span');
5662
+ tmp.innerHTML = taco.v;
5663
+ while (tmp.firstChild) frag.appendChild(tmp.firstChild);
5664
+ return frag;
5665
+ }
5666
+
5390
5667
  // Handle text nodes
5391
5668
  if (typeof taco !== 'object' || !taco.t) {
5392
5669
  return document.createTextNode(String(taco));
5393
5670
  }
5394
-
5671
+
5395
5672
  const { t: tag, a: attrs = {}, c: content, o: opts = {} } = taco;
5396
5673
 
5397
5674
  // Create element
@@ -5456,6 +5733,9 @@
5456
5733
  }
5457
5734
  }
5458
5735
  });
5736
+ } else if (typeof content === 'object' && content.__bw_raw) {
5737
+ // Raw HTML content — inject via innerHTML
5738
+ el.innerHTML = content.v;
5459
5739
  } else if (typeof content === 'object' && content.t) {
5460
5740
  var childEl = bw.createDOM(content, options);
5461
5741
  el.appendChild(childEl);