bitwrench 2.0.11 → 2.0.13

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.13 | 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) :
@@ -203,14 +203,14 @@
203
203
  */
204
204
 
205
205
  var VERSION_INFO = {
206
- version: '2.0.11',
206
+ version: '2.0.13',
207
207
  name: 'bitwrench',
208
208
  description: 'A library for javascript UI functions.',
209
209
  license: 'BSD-2-Clause',
210
210
  homepage: 'https://deftio.github.com/bitwrench/pages',
211
211
  repository: 'git+https://github.com/deftio/bitwrench.git',
212
212
  author: 'manu a. chatterjee <deftio@deftio.com> (https://deftio.com/)',
213
- buildDate: '2026-03-07T11:05:08.522Z'
213
+ buildDate: '2026-03-07T22:35:06.056Z'
214
214
  };
215
215
 
216
216
  /**
@@ -1151,14 +1151,34 @@
1151
1151
  rules[scopeSelector(scope, '.bw-accordion-button')] = {
1152
1152
  'color': palette.dark.base
1153
1153
  };
1154
+ rules[scopeSelector(scope, '.bw-accordion-button:not(.bw-collapsed)')] = {
1155
+ 'color': palette.primary.darkText,
1156
+ 'background-color': palette.primary.light
1157
+ };
1154
1158
  rules[scopeSelector(scope, '.bw-accordion-button:hover')] = {
1155
1159
  'background-color': palette.light.light
1156
1160
  };
1161
+ rules[scopeSelector(scope, '.bw-accordion-button:not(.bw-collapsed):hover')] = {
1162
+ 'background-color': palette.primary.hover
1163
+ };
1164
+ rules[scopeSelector(scope, '.bw-accordion-button:focus-visible')] = {
1165
+ 'box-shadow': '0 0 0 0.2rem ' + palette.primary.focus
1166
+ };
1157
1167
  rules[scopeSelector(scope, '.bw-accordion-body')] = {
1158
1168
  'border-top': '1px solid ' + palette.light.border
1159
1169
  };
1160
1170
  return rules;
1161
1171
  }
1172
+ function generateCarouselThemed(scope, palette) {
1173
+ var rules = {};
1174
+ rules[scopeSelector(scope, '.bw-carousel')] = {
1175
+ 'background-color': palette.light.light
1176
+ };
1177
+ rules[scopeSelector(scope, '.bw-carousel-indicator.active')] = {
1178
+ 'background-color': palette.primary.base
1179
+ };
1180
+ return rules;
1181
+ }
1162
1182
  function generateModalThemed(scope, palette) {
1163
1183
  var rules = {};
1164
1184
  rules[scopeSelector(scope, '.bw-modal-content')] = {
@@ -1235,7 +1255,7 @@
1235
1255
  function generateSkeletonThemed(scope, palette) {
1236
1256
  var rules = {};
1237
1257
  rules[scopeSelector(scope, '.bw-skeleton')] = {
1238
- 'background-color': palette.light.border
1258
+ 'background': 'linear-gradient(90deg, ' + palette.light.border + ' 25%, ' + palette.light.light + ' 37%, ' + palette.light.border + ' 63%)'
1239
1259
  };
1240
1260
  return rules;
1241
1261
  }
@@ -1261,7 +1281,7 @@
1261
1281
  * @returns {Object} CSS rules object
1262
1282
  */
1263
1283
  function generateThemedCSS(scopeName, palette, layout) {
1264
- return Object.assign({}, generateResetThemed(scopeName, palette), generateTypographyThemed(scopeName, palette), generateButtons(scopeName, palette, layout), generateAlerts(scopeName, palette, layout), generateBadges(scopeName, palette), generateCards(scopeName, palette, layout), generateForms(scopeName, palette, layout), generateNavigation(scopeName, palette), generateTables(scopeName, palette, layout), generateTabs(scopeName, palette), generateListGroups(scopeName, palette, layout), generatePagination(scopeName, palette), generateProgress(scopeName, palette), generateHero(scopeName, palette), generateBreadcrumbThemed(scopeName, palette), generateSpinnerThemed(scopeName, palette), generateCloseButtonThemed(scopeName, palette), generateSectionsThemed(scopeName, palette), generateAccordionThemed(scopeName, palette), generateModalThemed(scopeName, palette), generateToastThemed(scopeName, palette), generateDropdownThemed(scopeName, palette), generateSwitchThemed(scopeName, palette), generateSkeletonThemed(scopeName, palette), generateAvatarThemed(scopeName, palette), generateUtilityColors(scopeName, palette));
1284
+ return Object.assign({}, generateResetThemed(scopeName, palette), generateTypographyThemed(scopeName, palette), generateButtons(scopeName, palette, layout), generateAlerts(scopeName, palette, layout), generateBadges(scopeName, palette), generateCards(scopeName, palette, layout), generateForms(scopeName, palette, layout), generateNavigation(scopeName, palette), generateTables(scopeName, palette, layout), generateTabs(scopeName, palette), generateListGroups(scopeName, palette, layout), generatePagination(scopeName, palette), generateProgress(scopeName, palette), generateHero(scopeName, palette), generateBreadcrumbThemed(scopeName, palette), generateSpinnerThemed(scopeName, palette), generateCloseButtonThemed(scopeName, palette), generateSectionsThemed(scopeName, palette), generateAccordionThemed(scopeName, palette), generateCarouselThemed(scopeName, palette), generateModalThemed(scopeName, palette), generateToastThemed(scopeName, palette), generateDropdownThemed(scopeName, palette), generateSwitchThemed(scopeName, palette), generateSkeletonThemed(scopeName, palette), generateAvatarThemed(scopeName, palette), generateUtilityColors(scopeName, palette));
1265
1285
  }
1266
1286
 
1267
1287
  // =========================================================================
@@ -1288,7 +1308,7 @@
1288
1308
  'font-weight': '600',
1289
1309
  'line-height': '1.25',
1290
1310
  'letter-spacing': '-0.01em',
1291
- 'color': '#1a1a1a'
1311
+ 'color': 'inherit'
1292
1312
  },
1293
1313
  'h1': {
1294
1314
  'font-size': 'calc(1.375rem + 1.5vw)'
@@ -2111,6 +2131,15 @@
2111
2131
  rules['.bw-tab-pane.active'] = {
2112
2132
  'display': 'block'
2113
2133
  };
2134
+ rules['.bw-nav-scrollable'] = {
2135
+ 'flex-wrap': 'nowrap',
2136
+ 'overflow-x': 'auto',
2137
+ '-webkit-overflow-scrolling': 'touch',
2138
+ 'scrollbar-width': 'none'
2139
+ };
2140
+ rules['.bw-nav-scrollable .bw-nav-link'] = {
2141
+ 'white-space': 'nowrap'
2142
+ };
2114
2143
 
2115
2144
  // List groups (structural)
2116
2145
  rules['.bw-list-group'] = {
@@ -2619,6 +2648,84 @@
2619
2648
  'gap': '0.5rem'
2620
2649
  };
2621
2650
 
2651
+ // Carousel (structural)
2652
+ rules['.bw-carousel'] = {
2653
+ 'position': 'relative',
2654
+ 'overflow': 'hidden',
2655
+ 'border-radius': '8px'
2656
+ };
2657
+ rules['.bw-carousel-track'] = {
2658
+ 'display': 'flex',
2659
+ 'transition': 'transform 0.4s ease',
2660
+ 'height': '100%'
2661
+ };
2662
+ rules['.bw-carousel-slide'] = {
2663
+ 'min-width': '100%',
2664
+ 'flex-shrink': '0',
2665
+ 'overflow': 'hidden',
2666
+ 'position': 'relative',
2667
+ 'display': 'flex',
2668
+ 'align-items': 'center',
2669
+ 'justify-content': 'center'
2670
+ };
2671
+ rules['.bw-carousel-slide img'] = {
2672
+ 'width': '100%',
2673
+ 'height': '100%',
2674
+ 'object-fit': 'cover'
2675
+ };
2676
+ rules['.bw-carousel-caption'] = {
2677
+ 'position': 'absolute',
2678
+ 'bottom': '0',
2679
+ 'left': '0',
2680
+ 'right': '0',
2681
+ 'padding': '0.75rem 1rem'
2682
+ };
2683
+ rules['.bw-carousel-control'] = {
2684
+ 'position': 'absolute',
2685
+ 'top': '50%',
2686
+ 'transform': 'translateY(-50%)',
2687
+ 'width': '40px',
2688
+ 'height': '40px',
2689
+ 'border': 'none',
2690
+ 'border-radius': '50%',
2691
+ 'cursor': 'pointer',
2692
+ 'display': 'flex',
2693
+ 'align-items': 'center',
2694
+ 'justify-content': 'center',
2695
+ 'z-index': '2',
2696
+ 'padding': '0',
2697
+ 'transition': 'background-color 0.2s ease'
2698
+ };
2699
+ rules['.bw-carousel-control img'] = {
2700
+ 'width': '20px',
2701
+ 'height': '20px',
2702
+ 'pointer-events': 'none'
2703
+ };
2704
+ rules['.bw-carousel-control-prev'] = {
2705
+ 'left': '10px'
2706
+ };
2707
+ rules['.bw-carousel-control-next'] = {
2708
+ 'right': '10px'
2709
+ };
2710
+ rules['.bw-carousel-indicators'] = {
2711
+ 'position': 'absolute',
2712
+ 'bottom': '12px',
2713
+ 'left': '50%',
2714
+ 'transform': 'translateX(-50%)',
2715
+ 'display': 'flex',
2716
+ 'gap': '6px',
2717
+ 'z-index': '2'
2718
+ };
2719
+ rules['.bw-carousel-indicator'] = {
2720
+ 'width': '10px',
2721
+ 'height': '10px',
2722
+ 'border-radius': '50%',
2723
+ 'border': '2px solid transparent',
2724
+ 'padding': '0',
2725
+ 'cursor': 'pointer',
2726
+ 'transition': 'opacity 0.2s ease, background-color 0.2s ease'
2727
+ };
2728
+
2622
2729
  // Toast (structural)
2623
2730
  rules['.bw-toast-container'] = {
2624
2731
  'position': 'fixed',
@@ -2741,7 +2848,8 @@
2741
2848
  // Skeleton (structural)
2742
2849
  rules['.bw-skeleton'] = {
2743
2850
  'border-radius': '4px',
2744
- 'animation': 'bw-skeleton-pulse 1.5s ease-in-out infinite'
2851
+ 'background-size': '400% 100%',
2852
+ 'animation': 'bw-skeleton-shimmer 1.4s ease infinite'
2745
2853
  };
2746
2854
  rules['.bw-skeleton-text'] = {
2747
2855
  'height': '1em',
@@ -2757,15 +2865,12 @@
2757
2865
  'display': 'flex',
2758
2866
  'flex-direction': 'column'
2759
2867
  };
2760
- rules['@keyframes bw-skeleton-pulse'] = {
2868
+ rules['@keyframes bw-skeleton-shimmer'] = {
2761
2869
  '0%': {
2762
- 'opacity': '1'
2763
- },
2764
- '50%': {
2765
- 'opacity': '0.4'
2870
+ 'background-position': '100% 50%'
2766
2871
  },
2767
2872
  '100%': {
2768
- 'opacity': '1'
2873
+ 'background-position': '0 50%'
2769
2874
  }
2770
2875
  };
2771
2876
 
@@ -3333,12 +3438,31 @@
3333
3438
  '.bw-dark .bw-accordion-button': {
3334
3439
  'color': textColor
3335
3440
  },
3441
+ '.bw-dark .bw-accordion-button:not(.bw-collapsed)': {
3442
+ 'color': '#7dd3e0',
3443
+ 'background-color': 'rgba(125, 211, 224, 0.1)'
3444
+ },
3336
3445
  '.bw-dark .bw-accordion-button:hover': {
3337
3446
  'background-color': bodyBg
3338
3447
  },
3448
+ '.bw-dark .bw-accordion-button:not(.bw-collapsed):hover': {
3449
+ 'background-color': 'rgba(125, 211, 224, 0.15)'
3450
+ },
3451
+ '.bw-dark .bw-accordion-button:focus-visible': {
3452
+ 'box-shadow': '0 0 0 0.2rem rgba(125, 211, 224, 0.3)'
3453
+ },
3339
3454
  '.bw-dark .bw-accordion-body': {
3340
3455
  'border-top-color': borderColor
3341
3456
  },
3457
+ '.bw-dark .bw-carousel': {
3458
+ 'background-color': bodyBg
3459
+ },
3460
+ '.bw-dark .bw-carousel-control': {
3461
+ 'background-color': 'rgba(255,255,255,0.15)'
3462
+ },
3463
+ '.bw-dark .bw-carousel-control:hover': {
3464
+ 'background-color': 'rgba(255,255,255,0.25)'
3465
+ },
3342
3466
  '.bw-dark .bw-modal-content': {
3343
3467
  'background-color': surfaceBg,
3344
3468
  'border-color': borderColor
@@ -3374,7 +3498,7 @@
3374
3498
  'border-top-color': borderColor
3375
3499
  },
3376
3500
  '.bw-dark .bw-skeleton': {
3377
- 'background-color': borderColor
3501
+ 'background': 'linear-gradient(90deg, ' + borderColor + ' 25%, ' + surfaceBg + ' 37%, ' + borderColor + ' 63%)'
3378
3502
  },
3379
3503
  '.bw-dark h1, .bw-dark h2, .bw-dark h3, .bw-dark h4, .bw-dark h5, .bw-dark h6': {
3380
3504
  'color': textColor
@@ -6380,6 +6504,205 @@
6380
6504
  c: initials || ''
6381
6505
  };
6382
6506
  }
6507
+
6508
+ /**
6509
+ * Create a carousel/slideshow component with slide transitions
6510
+ *
6511
+ * Supports image slides, TACO content slides, captions, prev/next controls,
6512
+ * dot indicators, and optional auto-play. Uses CSS translateX transitions.
6513
+ *
6514
+ * @param {Object} [props] - Carousel configuration
6515
+ * @param {Array<Object>} [props.items=[]] - Slide items
6516
+ * @param {string|Object} props.items[].content - Slide content (TACO, string, or img element)
6517
+ * @param {string} [props.items[].caption] - Caption text shown at bottom of slide
6518
+ * @param {boolean} [props.showControls=true] - Show prev/next arrow buttons
6519
+ * @param {boolean} [props.showIndicators=true] - Show dot navigation
6520
+ * @param {boolean} [props.autoPlay=false] - Auto-advance slides
6521
+ * @param {number} [props.interval=5000] - Auto-advance interval in ms
6522
+ * @param {string} [props.height='300px'] - Carousel height
6523
+ * @param {number} [props.startIndex=0] - Initial slide index
6524
+ * @param {string} [props.className] - Additional CSS classes
6525
+ * @returns {Object} TACO object representing a carousel
6526
+ * @category Component Builders
6527
+ * @example
6528
+ * const carousel = makeCarousel({
6529
+ * items: [
6530
+ * { content: { t: 'img', a: { src: 'photo.jpg' } }, caption: 'Photo 1' },
6531
+ * { content: { t: 'div', c: 'Text slide' } }
6532
+ * ],
6533
+ * autoPlay: true,
6534
+ * interval: 3000
6535
+ * });
6536
+ */
6537
+ function makeCarousel() {
6538
+ var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
6539
+ var _props$items7 = props.items,
6540
+ items = _props$items7 === void 0 ? [] : _props$items7,
6541
+ _props$showControls = props.showControls,
6542
+ showControls = _props$showControls === void 0 ? true : _props$showControls,
6543
+ _props$showIndicators = props.showIndicators,
6544
+ showIndicators = _props$showIndicators === void 0 ? true : _props$showIndicators,
6545
+ _props$autoPlay = props.autoPlay,
6546
+ autoPlay = _props$autoPlay === void 0 ? false : _props$autoPlay,
6547
+ _props$interval = props.interval,
6548
+ interval = _props$interval === void 0 ? 5000 : _props$interval,
6549
+ _props$height = props.height,
6550
+ height = _props$height === void 0 ? '300px' : _props$height,
6551
+ _props$startIndex = props.startIndex,
6552
+ startIndex = _props$startIndex === void 0 ? 0 : _props$startIndex,
6553
+ _props$className28 = props.className,
6554
+ className = _props$className28 === void 0 ? '' : _props$className28;
6555
+
6556
+ // Shared navigation logic
6557
+ function goToSlide(carouselEl, index) {
6558
+ var total = carouselEl.querySelectorAll('.bw-carousel-slide').length;
6559
+ if (index < 0) index = total - 1;
6560
+ if (index >= total) index = 0;
6561
+ carouselEl.setAttribute('data-carousel-index', index);
6562
+ var track = carouselEl.querySelector('.bw-carousel-track');
6563
+ track.style.transform = 'translateX(-' + index * 100 + '%)';
6564
+ // Update indicators
6565
+ var indicators = carouselEl.querySelectorAll('.bw-carousel-indicator');
6566
+ for (var i = 0; i < indicators.length; i++) {
6567
+ if (i === index) {
6568
+ indicators[i].classList.add('active');
6569
+ } else {
6570
+ indicators[i].classList.remove('active');
6571
+ }
6572
+ }
6573
+ }
6574
+
6575
+ // Arrow SVGs (inline data URIs, same pattern as accordion chevrons)
6576
+ 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";
6577
+ 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";
6578
+ var slides = items.map(function (item) {
6579
+ var slideContent = [item.content, item.caption && {
6580
+ t: 'div',
6581
+ a: {
6582
+ "class": 'bw-carousel-caption'
6583
+ },
6584
+ c: item.caption
6585
+ }].filter(Boolean);
6586
+ return {
6587
+ t: 'div',
6588
+ a: {
6589
+ "class": 'bw-carousel-slide'
6590
+ },
6591
+ c: slideContent.length === 1 ? slideContent[0] : slideContent
6592
+ };
6593
+ });
6594
+ var children = [
6595
+ // Track
6596
+ {
6597
+ t: 'div',
6598
+ a: {
6599
+ "class": 'bw-carousel-track',
6600
+ style: 'transform: translateX(-' + startIndex * 100 + '%)'
6601
+ },
6602
+ c: slides
6603
+ }];
6604
+
6605
+ // Prev/Next controls
6606
+ if (showControls && items.length > 1) {
6607
+ children.push({
6608
+ t: 'button',
6609
+ a: {
6610
+ "class": 'bw-carousel-control bw-carousel-control-prev',
6611
+ type: 'button',
6612
+ 'aria-label': 'Previous slide',
6613
+ onclick: function onclick(e) {
6614
+ var carousel = e.target.closest('.bw-carousel');
6615
+ var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
6616
+ goToSlide(carousel, idx - 1);
6617
+ }
6618
+ },
6619
+ c: {
6620
+ t: 'img',
6621
+ a: {
6622
+ src: prevArrow,
6623
+ alt: '',
6624
+ role: 'presentation'
6625
+ }
6626
+ }
6627
+ });
6628
+ children.push({
6629
+ t: 'button',
6630
+ a: {
6631
+ "class": 'bw-carousel-control bw-carousel-control-next',
6632
+ type: 'button',
6633
+ 'aria-label': 'Next slide',
6634
+ onclick: function onclick(e) {
6635
+ var carousel = e.target.closest('.bw-carousel');
6636
+ var idx = parseInt(carousel.getAttribute('data-carousel-index') || '0');
6637
+ goToSlide(carousel, idx + 1);
6638
+ }
6639
+ },
6640
+ c: {
6641
+ t: 'img',
6642
+ a: {
6643
+ src: nextArrow,
6644
+ alt: '',
6645
+ role: 'presentation'
6646
+ }
6647
+ }
6648
+ });
6649
+ }
6650
+
6651
+ // Indicators
6652
+ if (showIndicators && items.length > 1) {
6653
+ children.push({
6654
+ t: 'div',
6655
+ a: {
6656
+ "class": 'bw-carousel-indicators'
6657
+ },
6658
+ c: items.map(function (_, i) {
6659
+ return {
6660
+ t: 'button',
6661
+ a: {
6662
+ "class": 'bw-carousel-indicator' + (i === startIndex ? ' active' : ''),
6663
+ type: 'button',
6664
+ 'aria-label': 'Go to slide ' + (i + 1),
6665
+ 'data-slide-index': i,
6666
+ onclick: function onclick(e) {
6667
+ var carousel = e.target.closest('.bw-carousel');
6668
+ var idx = parseInt(e.target.getAttribute('data-slide-index'));
6669
+ goToSlide(carousel, idx);
6670
+ }
6671
+ }
6672
+ };
6673
+ })
6674
+ });
6675
+ }
6676
+ return {
6677
+ t: 'div',
6678
+ a: {
6679
+ "class": ('bw-carousel ' + className).trim(),
6680
+ style: 'height: ' + height,
6681
+ 'data-carousel-index': startIndex
6682
+ },
6683
+ c: children,
6684
+ o: {
6685
+ type: 'carousel',
6686
+ state: {
6687
+ activeIndex: startIndex,
6688
+ autoPlay: autoPlay,
6689
+ interval: interval
6690
+ },
6691
+ mounted: autoPlay ? function (el) {
6692
+ var intervalId = setInterval(function () {
6693
+ var idx = parseInt(el.getAttribute('data-carousel-index') || '0');
6694
+ goToSlide(el, idx + 1);
6695
+ }, interval);
6696
+ el._bw_carouselInterval = intervalId;
6697
+ } : undefined,
6698
+ unmount: autoPlay ? function (el) {
6699
+ if (el._bw_carouselInterval) {
6700
+ clearInterval(el._bw_carouselInterval);
6701
+ }
6702
+ } : undefined
6703
+ }
6704
+ };
6705
+ }
6383
6706
  var componentHandles = {
6384
6707
  card: CardHandle,
6385
6708
  table: TableHandle,
@@ -6405,6 +6728,7 @@
6405
6728
  makeButtonGroup: makeButtonGroup,
6406
6729
  makeCTA: makeCTA,
6407
6730
  makeCard: makeCard,
6731
+ makeCarousel: makeCarousel,
6408
6732
  makeCheckbox: makeCheckbox,
6409
6733
  makeCodeDemo: makeCodeDemo,
6410
6734
  makeCol: makeCol,
@@ -6840,6 +7164,29 @@
6840
7164
  });
6841
7165
  };
6842
7166
 
7167
+ /**
7168
+ * Mark a string as raw HTML so it will not be escaped by bw.html() or bw.createDOM().
7169
+ *
7170
+ * By default, bitwrench escapes all text content to prevent XSS. Use bw.raw()
7171
+ * when you need to embed pre-sanitized HTML, entities, or inline markup.
7172
+ *
7173
+ * @param {string} str - HTML string to mark as raw
7174
+ * @returns {Object} Marked object recognized by bw.html() and bw.createDOM()
7175
+ * @category DOM Generation
7176
+ * @see bw.escapeHTML
7177
+ * @see bw.html
7178
+ * @example
7179
+ * bw.raw('Hello &mdash; World')
7180
+ * // Used in TACO content:
7181
+ * { t: 'p', c: bw.raw('Price: <strong>$9.99</strong>') }
7182
+ */
7183
+ bw.raw = function (str) {
7184
+ return {
7185
+ __bw_raw: true,
7186
+ v: String(str)
7187
+ };
7188
+ };
7189
+
6843
7190
  /**
6844
7191
  * Normalize CSS class names by converting underscores to hyphens for bw-prefixed classes.
6845
7192
  *
@@ -6895,6 +7242,11 @@
6895
7242
  }).join('');
6896
7243
  }
6897
7244
 
7245
+ // Handle bw.raw() marked content
7246
+ if (taco && taco.__bw_raw) {
7247
+ return taco.v;
7248
+ }
7249
+
6898
7250
  // Handle primitives and non-TACO objects
6899
7251
  if (_typeof(taco) !== 'object' || !taco.t) {
6900
7252
  return options.raw ? String(taco) : bw.escapeHTML(String(taco));
@@ -7002,6 +7354,15 @@
7002
7354
  // Handle null/undefined
7003
7355
  if (taco == null) return document.createTextNode('');
7004
7356
 
7357
+ // Handle bw.raw() marked content — inject as HTML
7358
+ if (taco && taco.__bw_raw) {
7359
+ var frag = document.createDocumentFragment();
7360
+ var tmp = document.createElement('span');
7361
+ tmp.innerHTML = taco.v;
7362
+ while (tmp.firstChild) frag.appendChild(tmp.firstChild);
7363
+ return frag;
7364
+ }
7365
+
7005
7366
  // Handle text nodes
7006
7367
  if (_typeof(taco) !== 'object' || !taco.t) {
7007
7368
  return document.createTextNode(String(taco));
@@ -7073,6 +7434,9 @@
7073
7434
  }
7074
7435
  }
7075
7436
  });
7437
+ } else if (_typeof(content) === 'object' && content.__bw_raw) {
7438
+ // Raw HTML content — inject via innerHTML
7439
+ el.innerHTML = content.v;
7076
7440
  } else if (_typeof(content) === 'object' && content.t) {
7077
7441
  var childEl = bw.createDOM(content, options);
7078
7442
  el.appendChild(childEl);