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
  'use strict';
3
3
 
4
4
  /**
@@ -7,14 +7,14 @@
7
7
  */
8
8
 
9
9
  const VERSION_INFO = {
10
- version: '2.0.11',
10
+ version: '2.0.12',
11
11
  name: 'bitwrench',
12
12
  description: 'A library for javascript UI functions.',
13
13
  license: 'BSD-2-Clause',
14
14
  homepage: 'https://deftio.github.com/bitwrench/pages',
15
15
  repository: 'git+https://github.com/deftio/bitwrench.git',
16
16
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
17
- buildDate: '2026-03-07T11:05:08.522Z'
17
+ buildDate: '2026-03-07T22:31:35.755Z'
18
18
  };
19
19
 
20
20
  /**
@@ -889,15 +889,36 @@ function generateAccordionThemed(scope, palette) {
889
889
  rules[scopeSelector(scope, '.bw-accordion-button')] = {
890
890
  'color': palette.dark.base
891
891
  };
892
+ rules[scopeSelector(scope, '.bw-accordion-button:not(.bw-collapsed)')] = {
893
+ 'color': palette.primary.darkText,
894
+ 'background-color': palette.primary.light
895
+ };
892
896
  rules[scopeSelector(scope, '.bw-accordion-button:hover')] = {
893
897
  'background-color': palette.light.light
894
898
  };
899
+ rules[scopeSelector(scope, '.bw-accordion-button:not(.bw-collapsed):hover')] = {
900
+ 'background-color': palette.primary.hover
901
+ };
902
+ rules[scopeSelector(scope, '.bw-accordion-button:focus-visible')] = {
903
+ 'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
904
+ };
895
905
  rules[scopeSelector(scope, '.bw-accordion-body')] = {
896
906
  'border-top': '1px solid ' + palette.light.border
897
907
  };
898
908
  return rules;
899
909
  }
900
910
 
911
+ function generateCarouselThemed(scope, palette) {
912
+ var rules = {};
913
+ rules[scopeSelector(scope, '.bw-carousel')] = {
914
+ 'background-color': palette.light.light
915
+ };
916
+ rules[scopeSelector(scope, '.bw-carousel-indicator.active')] = {
917
+ 'background-color': palette.primary.base
918
+ };
919
+ return rules;
920
+ }
921
+
901
922
  function generateModalThemed(scope, palette) {
902
923
  var rules = {};
903
924
  rules[scopeSelector(scope, '.bw-modal-content')] = {
@@ -978,7 +999,7 @@ function generateSwitchThemed(scope, palette) {
978
999
  function generateSkeletonThemed(scope, palette) {
979
1000
  var rules = {};
980
1001
  rules[scopeSelector(scope, '.bw-skeleton')] = {
981
- 'background-color': palette.light.border
1002
+ 'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.light.light + ' 37%, ' + palette.light.border + ' 63%)'
982
1003
  };
983
1004
  return rules;
984
1005
  }
@@ -1025,6 +1046,7 @@ function generateThemedCSS(scopeName, palette, layout) {
1025
1046
  generateCloseButtonThemed(scopeName, palette),
1026
1047
  generateSectionsThemed(scopeName, palette),
1027
1048
  generateAccordionThemed(scopeName, palette),
1049
+ generateCarouselThemed(scopeName, palette),
1028
1050
  generateModalThemed(scopeName, palette),
1029
1051
  generateToastThemed(scopeName, palette),
1030
1052
  generateDropdownThemed(scopeName, palette),
@@ -1384,6 +1406,8 @@ function getStructuralStyles() {
1384
1406
  rules['.bw-tab-content'] = { 'padding': '1.25rem 0' };
1385
1407
  rules['.bw-tab-pane'] = { 'display': 'none' };
1386
1408
  rules['.bw-tab-pane.active'] = { 'display': 'block' };
1409
+ rules['.bw-nav-scrollable'] = { 'flex-wrap': 'nowrap', 'overflow-x': 'auto', '-webkit-overflow-scrolling': 'touch', 'scrollbar-width': 'none' };
1410
+ rules['.bw-nav-scrollable .bw-nav-link'] = { 'white-space': 'nowrap' };
1387
1411
 
1388
1412
  // List groups (structural)
1389
1413
  rules['.bw-list-group'] = { 'display': 'flex', 'flex-direction': 'column', 'padding-left': '0', 'margin-bottom': '0', 'border-radius': '0.375rem' };
@@ -1550,6 +1574,29 @@ function getStructuralStyles() {
1550
1574
  rules['.bw-modal-body'] = { 'position': 'relative', 'flex': '1 1 auto', 'padding': '1.5rem' };
1551
1575
  rules['.bw-modal-footer'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'justify-content': 'flex-end', 'padding': '0.75rem 1.5rem', 'gap': '0.5rem' };
1552
1576
 
1577
+ // Carousel (structural)
1578
+ rules['.bw-carousel'] = { 'position': 'relative', 'overflow': 'hidden', 'border-radius': '8px' };
1579
+ rules['.bw-carousel-track'] = { 'display': 'flex', 'transition': 'transform 0.4s ease', 'height': '100%' };
1580
+ rules['.bw-carousel-slide'] = { 'min-width': '100%', 'flex-shrink': '0', 'overflow': 'hidden', 'position': 'relative', 'display': 'flex', 'align-items': 'center', 'justify-content': 'center' };
1581
+ rules['.bw-carousel-slide img'] = { 'width': '100%', 'height': '100%', 'object-fit': 'cover' };
1582
+ rules['.bw-carousel-caption'] = { 'position': 'absolute', 'bottom': '0', 'left': '0', 'right': '0', 'padding': '0.75rem 1rem' };
1583
+ rules['.bw-carousel-control'] = {
1584
+ 'position': 'absolute', 'top': '50%', 'transform': 'translateY(-50%)', 'width': '40px', 'height': '40px',
1585
+ 'border': 'none', 'border-radius': '50%', 'cursor': 'pointer', 'display': 'flex', 'align-items': 'center',
1586
+ 'justify-content': 'center', 'z-index': '2', 'padding': '0', 'transition': 'background-color 0.2s ease'
1587
+ };
1588
+ rules['.bw-carousel-control img'] = { 'width': '20px', 'height': '20px', 'pointer-events': 'none' };
1589
+ rules['.bw-carousel-control-prev'] = { 'left': '10px' };
1590
+ rules['.bw-carousel-control-next'] = { 'right': '10px' };
1591
+ rules['.bw-carousel-indicators'] = {
1592
+ 'position': 'absolute', 'bottom': '12px', 'left': '50%', 'transform': 'translateX(-50%)',
1593
+ 'display': 'flex', 'gap': '6px', 'z-index': '2'
1594
+ };
1595
+ rules['.bw-carousel-indicator'] = {
1596
+ 'width': '10px', 'height': '10px', 'border-radius': '50%', 'border': '2px solid transparent',
1597
+ 'padding': '0', 'cursor': 'pointer', 'transition': 'opacity 0.2s ease, background-color 0.2s ease'
1598
+ };
1599
+
1553
1600
  // Toast (structural)
1554
1601
  rules['.bw-toast-container'] = {
1555
1602
  'position': 'fixed', 'z-index': '1080', 'pointer-events': 'none',
@@ -1599,12 +1646,12 @@ function getStructuralStyles() {
1599
1646
  rules['.bw-form-switch .bw-switch-input:disabled'] = { 'opacity': '0.5', 'cursor': 'not-allowed' };
1600
1647
 
1601
1648
  // Skeleton (structural)
1602
- rules['.bw-skeleton'] = { 'border-radius': '4px', 'animation': 'bw-skeleton-pulse 1.5s ease-in-out infinite' };
1649
+ rules['.bw-skeleton'] = { 'border-radius': '4px', 'background-size': '400% 100%', 'animation': 'bw-skeleton-shimmer 1.4s ease infinite' };
1603
1650
  rules['.bw-skeleton-text'] = { 'height': '1em', 'margin-bottom': '0.5rem' };
1604
1651
  rules['.bw-skeleton-circle'] = { 'border-radius': '50%' };
1605
1652
  rules['.bw-skeleton-rect'] = { 'border-radius': '8px' };
1606
1653
  rules['.bw-skeleton-group'] = { 'display': 'flex', 'flex-direction': 'column' };
1607
- rules['@keyframes bw-skeleton-pulse'] = { '0%': { 'opacity': '1' }, '50%': { 'opacity': '0.4' }, '100%': { 'opacity': '1' } };
1654
+ rules['@keyframes bw-skeleton-shimmer'] = { '0%': { 'background-position': '100% 50%' }, '100%': { 'background-position': '0 50%' } };
1608
1655
 
1609
1656
  // Avatar (structural)
1610
1657
  rules['.bw-avatar'] = {
@@ -1966,12 +2013,31 @@ function generateDarkModeCSS(palette) {
1966
2013
  '.bw-dark .bw-accordion-button': {
1967
2014
  'color': textColor
1968
2015
  },
2016
+ '.bw-dark .bw-accordion-button:not(.bw-collapsed)': {
2017
+ 'color': '#7dd3e0',
2018
+ 'background-color': 'rgba(125, 211, 224, 0.1)'
2019
+ },
1969
2020
  '.bw-dark .bw-accordion-button:hover': {
1970
2021
  'background-color': bodyBg
1971
2022
  },
2023
+ '.bw-dark .bw-accordion-button:not(.bw-collapsed):hover': {
2024
+ 'background-color': 'rgba(125, 211, 224, 0.15)'
2025
+ },
2026
+ '.bw-dark .bw-accordion-button:focus-visible': {
2027
+ 'box-shadow': '0 0 0 0.2rem rgba(125, 211, 224, 0.3)'
2028
+ },
1972
2029
  '.bw-dark .bw-accordion-body': {
1973
2030
  'border-top-color': borderColor
1974
2031
  },
2032
+ '.bw-dark .bw-carousel': {
2033
+ 'background-color': bodyBg
2034
+ },
2035
+ '.bw-dark .bw-carousel-control': {
2036
+ 'background-color': 'rgba(255,255,255,0.15)'
2037
+ },
2038
+ '.bw-dark .bw-carousel-control:hover': {
2039
+ 'background-color': 'rgba(255,255,255,0.25)'
2040
+ },
1975
2041
  '.bw-dark .bw-modal-content': {
1976
2042
  'background-color': surfaceBg,
1977
2043
  'border-color': borderColor
@@ -2007,7 +2073,7 @@ function generateDarkModeCSS(palette) {
2007
2073
  'border-top-color': borderColor
2008
2074
  },
2009
2075
  '.bw-dark .bw-skeleton': {
2010
- 'background-color': borderColor
2076
+ 'background': 'linear-gradient(90deg, ' + borderColor + ' 25%, ' + surfaceBg + ' 37%, ' + borderColor + ' 63%)'
2011
2077
  },
2012
2078
  '.bw-dark h1, .bw-dark h2, .bw-dark h3, .bw-dark h4, .bw-dark h5, .bw-dark h6': {
2013
2079
  'color': textColor
@@ -4757,6 +4823,182 @@ function makeAvatar(props = {}) {
4757
4823
  };
4758
4824
  }
4759
4825
 
4826
+ /**
4827
+ * Create a carousel/slideshow component with slide transitions
4828
+ *
4829
+ * Supports image slides, TACO content slides, captions, prev/next controls,
4830
+ * dot indicators, and optional auto-play. Uses CSS translateX transitions.
4831
+ *
4832
+ * @param {Object} [props] - Carousel configuration
4833
+ * @param {Array<Object>} [props.items=[]] - Slide items
4834
+ * @param {string|Object} props.items[].content - Slide content (TACO, string, or img element)
4835
+ * @param {string} [props.items[].caption] - Caption text shown at bottom of slide
4836
+ * @param {boolean} [props.showControls=true] - Show prev/next arrow buttons
4837
+ * @param {boolean} [props.showIndicators=true] - Show dot navigation
4838
+ * @param {boolean} [props.autoPlay=false] - Auto-advance slides
4839
+ * @param {number} [props.interval=5000] - Auto-advance interval in ms
4840
+ * @param {string} [props.height='300px'] - Carousel height
4841
+ * @param {number} [props.startIndex=0] - Initial slide index
4842
+ * @param {string} [props.className] - Additional CSS classes
4843
+ * @returns {Object} TACO object representing a carousel
4844
+ * @category Component Builders
4845
+ * @example
4846
+ * const carousel = makeCarousel({
4847
+ * items: [
4848
+ * { content: { t: 'img', a: { src: 'photo.jpg' } }, caption: 'Photo 1' },
4849
+ * { content: { t: 'div', c: 'Text slide' } }
4850
+ * ],
4851
+ * autoPlay: true,
4852
+ * interval: 3000
4853
+ * });
4854
+ */
4855
+ function makeCarousel(props = {}) {
4856
+ const {
4857
+ items = [],
4858
+ showControls = true,
4859
+ showIndicators = true,
4860
+ autoPlay = false,
4861
+ interval = 5000,
4862
+ height = '300px',
4863
+ startIndex = 0,
4864
+ className = ''
4865
+ } = props;
4866
+
4867
+ // Shared navigation logic
4868
+ function goToSlide(carouselEl, index) {
4869
+ var total = carouselEl.querySelectorAll('.bw-carousel-slide').length;
4870
+ if (index < 0) index = total - 1;
4871
+ if (index >= total) index = 0;
4872
+ carouselEl.setAttribute('data-carousel-index', index);
4873
+ var track = carouselEl.querySelector('.bw-carousel-track');
4874
+ track.style.transform = 'translateX(-' + (index * 100) + '%)';
4875
+ // Update indicators
4876
+ var indicators = carouselEl.querySelectorAll('.bw-carousel-indicator');
4877
+ for (var i = 0; i < indicators.length; i++) {
4878
+ if (i === index) {
4879
+ indicators[i].classList.add('active');
4880
+ } else {
4881
+ indicators[i].classList.remove('active');
4882
+ }
4883
+ }
4884
+ }
4885
+
4886
+ // Arrow SVGs (inline data URIs, same pattern as accordion chevrons)
4887
+ 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";
4888
+ 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";
4889
+
4890
+ var slides = items.map(function(item) {
4891
+ var slideContent = [
4892
+ item.content,
4893
+ item.caption && {
4894
+ t: 'div',
4895
+ a: { class: 'bw-carousel-caption' },
4896
+ c: item.caption
4897
+ }
4898
+ ].filter(Boolean);
4899
+
4900
+ return {
4901
+ t: 'div',
4902
+ a: { class: 'bw-carousel-slide' },
4903
+ c: slideContent.length === 1 ? slideContent[0] : slideContent
4904
+ };
4905
+ });
4906
+
4907
+ var children = [
4908
+ // Track
4909
+ {
4910
+ t: 'div',
4911
+ a: {
4912
+ class: 'bw-carousel-track',
4913
+ style: 'transform: translateX(-' + (startIndex * 100) + '%)'
4914
+ },
4915
+ c: slides
4916
+ }
4917
+ ];
4918
+
4919
+ // Prev/Next controls
4920
+ if (showControls && items.length > 1) {
4921
+ children.push({
4922
+ t: 'button',
4923
+ a: {
4924
+ class: 'bw-carousel-control bw-carousel-control-prev',
4925
+ type: 'button',
4926
+ 'aria-label': 'Previous slide',
4927
+ onclick: function(e) {
4928
+ var carousel = e.target.closest('.bw-carousel');
4929
+ var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
4930
+ goToSlide(carousel, idx - 1);
4931
+ }
4932
+ },
4933
+ c: { t: 'img', a: { src: prevArrow, alt: '', role: 'presentation' } }
4934
+ });
4935
+ children.push({
4936
+ t: 'button',
4937
+ a: {
4938
+ class: 'bw-carousel-control bw-carousel-control-next',
4939
+ type: 'button',
4940
+ 'aria-label': 'Next slide',
4941
+ onclick: function(e) {
4942
+ var carousel = e.target.closest('.bw-carousel');
4943
+ var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
4944
+ goToSlide(carousel, idx + 1);
4945
+ }
4946
+ },
4947
+ c: { t: 'img', a: { src: nextArrow, alt: '', role: 'presentation' } }
4948
+ });
4949
+ }
4950
+
4951
+ // Indicators
4952
+ if (showIndicators && items.length > 1) {
4953
+ children.push({
4954
+ t: 'div',
4955
+ a: { class: 'bw-carousel-indicators' },
4956
+ c: items.map(function(_, i) {
4957
+ return {
4958
+ t: 'button',
4959
+ a: {
4960
+ class: 'bw-carousel-indicator' + (i === startIndex ? ' active' : ''),
4961
+ type: 'button',
4962
+ 'aria-label': 'Go to slide ' + (i + 1),
4963
+ 'data-slide-index': i,
4964
+ onclick: function(e) {
4965
+ var carousel = e.target.closest('.bw-carousel');
4966
+ var idx = parseInt(e.target.getAttribute('data-slide-index'));
4967
+ goToSlide(carousel, idx);
4968
+ }
4969
+ }
4970
+ };
4971
+ })
4972
+ });
4973
+ }
4974
+
4975
+ return {
4976
+ t: 'div',
4977
+ a: {
4978
+ class: ('bw-carousel ' + className).trim(),
4979
+ style: 'height: ' + height,
4980
+ 'data-carousel-index': startIndex
4981
+ },
4982
+ c: children,
4983
+ o: {
4984
+ type: 'carousel',
4985
+ state: { activeIndex: startIndex, autoPlay: autoPlay, interval: interval },
4986
+ mounted: autoPlay ? function(el) {
4987
+ var intervalId = setInterval(function() {
4988
+ var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
4989
+ goToSlide(el, idx + 1);
4990
+ }, interval);
4991
+ el._bw_carouselInterval = intervalId;
4992
+ } : undefined,
4993
+ unmount: autoPlay ? function(el) {
4994
+ if (el._bw_carouselInterval) {
4995
+ clearInterval(el._bw_carouselInterval);
4996
+ }
4997
+ } : undefined
4998
+ }
4999
+ };
5000
+ }
5001
+
4760
5002
  const componentHandles = {
4761
5003
  card: CardHandle,
4762
5004
  table: TableHandle,
@@ -4782,6 +5024,7 @@ var components = /*#__PURE__*/Object.freeze({
4782
5024
  makeButtonGroup: makeButtonGroup,
4783
5025
  makeCTA: makeCTA,
4784
5026
  makeCard: makeCard,
5027
+ makeCarousel: makeCarousel,
4785
5028
  makeCheckbox: makeCheckbox,
4786
5029
  makeCodeDemo: makeCodeDemo,
4787
5030
  makeCol: makeCol,
@@ -5231,6 +5474,26 @@ bw.escapeHTML = function(str) {
5231
5474
  return str.replace(/[&<>"'/]/g, (char) => escapeMap[char]);
5232
5475
  };
5233
5476
 
5477
+ /**
5478
+ * Mark a string as raw HTML so it will not be escaped by bw.html() or bw.createDOM().
5479
+ *
5480
+ * By default, bitwrench escapes all text content to prevent XSS. Use bw.raw()
5481
+ * when you need to embed pre-sanitized HTML, entities, or inline markup.
5482
+ *
5483
+ * @param {string} str - HTML string to mark as raw
5484
+ * @returns {Object} Marked object recognized by bw.html() and bw.createDOM()
5485
+ * @category DOM Generation
5486
+ * @see bw.escapeHTML
5487
+ * @see bw.html
5488
+ * @example
5489
+ * bw.raw('Hello &mdash; World')
5490
+ * // Used in TACO content:
5491
+ * { t: 'p', c: bw.raw('Price: <strong>$9.99</strong>') }
5492
+ */
5493
+ bw.raw = function(str) {
5494
+ return { __bw_raw: true, v: String(str) };
5495
+ };
5496
+
5234
5497
  /**
5235
5498
  * Normalize CSS class names by converting underscores to hyphens for bw-prefixed classes.
5236
5499
  *
@@ -5282,6 +5545,11 @@ bw.html = function(taco, options = {}) {
5282
5545
  return taco.map(t => bw.html(t, options)).join('');
5283
5546
  }
5284
5547
 
5548
+ // Handle bw.raw() marked content
5549
+ if (taco && taco.__bw_raw) {
5550
+ return taco.v;
5551
+ }
5552
+
5285
5553
  // Handle primitives and non-TACO objects
5286
5554
  if (typeof taco !== 'object' || !taco.t) {
5287
5555
  return options.raw ? String(taco) : bw.escapeHTML(String(taco));
@@ -5382,12 +5650,21 @@ bw.createDOM = function(taco, options = {}) {
5382
5650
 
5383
5651
  // Handle null/undefined
5384
5652
  if (taco == null) return document.createTextNode('');
5385
-
5653
+
5654
+ // Handle bw.raw() marked content — inject as HTML
5655
+ if (taco && taco.__bw_raw) {
5656
+ var frag = document.createDocumentFragment();
5657
+ var tmp = document.createElement('span');
5658
+ tmp.innerHTML = taco.v;
5659
+ while (tmp.firstChild) frag.appendChild(tmp.firstChild);
5660
+ return frag;
5661
+ }
5662
+
5386
5663
  // Handle text nodes
5387
5664
  if (typeof taco !== 'object' || !taco.t) {
5388
5665
  return document.createTextNode(String(taco));
5389
5666
  }
5390
-
5667
+
5391
5668
  const { t: tag, a: attrs = {}, c: content, o: opts = {} } = taco;
5392
5669
 
5393
5670
  // Create element
@@ -5452,6 +5729,9 @@ bw.createDOM = function(taco, options = {}) {
5452
5729
  }
5453
5730
  }
5454
5731
  });
5732
+ } else if (typeof content === 'object' && content.__bw_raw) {
5733
+ // Raw HTML content — inject via innerHTML
5734
+ el.innerHTML = content.v;
5455
5735
  } else if (typeof content === 'object' && content.t) {
5456
5736
  var childEl = bw.createDOM(content, options);
5457
5737
  el.appendChild(childEl);