bitwrench 2.0.10 → 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.
package/src/bitwrench.js CHANGED
@@ -426,6 +426,26 @@ bw.escapeHTML = function(str) {
426
426
  return str.replace(/[&<>"'/]/g, (char) => escapeMap[char]);
427
427
  };
428
428
 
429
+ /**
430
+ * Mark a string as raw HTML so it will not be escaped by bw.html() or bw.createDOM().
431
+ *
432
+ * By default, bitwrench escapes all text content to prevent XSS. Use bw.raw()
433
+ * when you need to embed pre-sanitized HTML, entities, or inline markup.
434
+ *
435
+ * @param {string} str - HTML string to mark as raw
436
+ * @returns {Object} Marked object recognized by bw.html() and bw.createDOM()
437
+ * @category DOM Generation
438
+ * @see bw.escapeHTML
439
+ * @see bw.html
440
+ * @example
441
+ * bw.raw('Hello &mdash; World')
442
+ * // Used in TACO content:
443
+ * { t: 'p', c: bw.raw('Price: <strong>$9.99</strong>') }
444
+ */
445
+ bw.raw = function(str) {
446
+ return { __bw_raw: true, v: String(str) };
447
+ };
448
+
429
449
  /**
430
450
  * Normalize CSS class names by converting underscores to hyphens for bw-prefixed classes.
431
451
  *
@@ -477,6 +497,11 @@ bw.html = function(taco, options = {}) {
477
497
  return taco.map(t => bw.html(t, options)).join('');
478
498
  }
479
499
 
500
+ // Handle bw.raw() marked content
501
+ if (taco && taco.__bw_raw) {
502
+ return taco.v;
503
+ }
504
+
480
505
  // Handle primitives and non-TACO objects
481
506
  if (typeof taco !== 'object' || !taco.t) {
482
507
  return options.raw ? String(taco) : bw.escapeHTML(String(taco));
@@ -577,12 +602,21 @@ bw.createDOM = function(taco, options = {}) {
577
602
 
578
603
  // Handle null/undefined
579
604
  if (taco == null) return document.createTextNode('');
580
-
605
+
606
+ // Handle bw.raw() marked content — inject as HTML
607
+ if (taco && taco.__bw_raw) {
608
+ var frag = document.createDocumentFragment();
609
+ var tmp = document.createElement('span');
610
+ tmp.innerHTML = taco.v;
611
+ while (tmp.firstChild) frag.appendChild(tmp.firstChild);
612
+ return frag;
613
+ }
614
+
581
615
  // Handle text nodes
582
616
  if (typeof taco !== 'object' || !taco.t) {
583
617
  return document.createTextNode(String(taco));
584
618
  }
585
-
619
+
586
620
  const { t: tag, a: attrs = {}, c: content, o: opts = {} } = taco;
587
621
 
588
622
  // Create element
@@ -647,6 +681,9 @@ bw.createDOM = function(taco, options = {}) {
647
681
  }
648
682
  }
649
683
  });
684
+ } else if (typeof content === 'object' && content.__bw_raw) {
685
+ // Raw HTML content — inject via innerHTML
686
+ el.innerHTML = content.v;
650
687
  } else if (typeof content === 'object' && content.t) {
651
688
  var childEl = bw.createDOM(content, options);
652
689
  el.appendChild(childEl);
@@ -1145,6 +1182,16 @@ bw.patch = function(id, content, attr) {
1145
1182
  if (attr) {
1146
1183
  // Patch an attribute
1147
1184
  el.setAttribute(attr, String(content));
1185
+ } else if (Array.isArray(content)) {
1186
+ // Patch with array of children (strings and/or TACOs)
1187
+ el.innerHTML = '';
1188
+ content.forEach(function(item) {
1189
+ if (typeof item === 'string' || typeof item === 'number') {
1190
+ el.appendChild(document.createTextNode(String(item)));
1191
+ } else if (item && item.t) {
1192
+ el.appendChild(bw.createDOM(item));
1193
+ }
1194
+ });
1148
1195
  } else if (typeof content === 'object' && content !== null && content.t) {
1149
1196
  // Patch with a TACO — replace children
1150
1197
  el.innerHTML = '';
@@ -2341,6 +2388,7 @@ bw.getURLParam = function(key, defaultValue) {
2341
2388
  * @see bw.makeTable
2342
2389
  */
2343
2390
  bw.htmlTable = function(data, opts = {}) {
2391
+ console.warn('bw.htmlTable() is deprecated. Use bw.makeTableFromArray() for TACO output or bw.makeTable() for object-array data.');
2344
2392
  if (bw.typeOf(data) !== "array" || data.length < 1) return "";
2345
2393
 
2346
2394
  const dopts = {
@@ -2440,6 +2488,7 @@ bw._attrsToStr = function(attrs) {
2440
2488
  * @see bw.makeTabs
2441
2489
  */
2442
2490
  bw.htmlTabs = function(tabData, opts = {}) {
2491
+ console.warn('bw.htmlTabs() is deprecated. Use bw.makeTabs() instead.');
2443
2492
  if (bw.typeOf(tabData) !== "array" || tabData.length < 1) return "";
2444
2493
 
2445
2494
  const dopts = {
@@ -2486,6 +2535,7 @@ bw.htmlTabs = function(tabData, opts = {}) {
2486
2535
  * @category Legacy (v1)
2487
2536
  */
2488
2537
  bw.selectTabContent = function(tabElement) {
2538
+ console.warn('bw.selectTabContent() is deprecated. Use bw.makeTabs() instead.');
2489
2539
  if (!bw._isBrowser || !tabElement) return;
2490
2540
 
2491
2541
  const container = tabElement.closest(".bw-tab-container");
@@ -3014,12 +3064,21 @@ bw.makeTable = function(config) {
3014
3064
  const {
3015
3065
  data = [],
3016
3066
  columns,
3017
- className = "table",
3067
+ className = '',
3068
+ striped = false,
3069
+ hover = false,
3018
3070
  sortable = true,
3019
3071
  onSort,
3020
3072
  sortColumn,
3021
3073
  sortDirection = 'asc'
3022
3074
  } = config;
3075
+
3076
+ // Build class list: always include bw-table, add striped/hover, append user className
3077
+ let cls = 'bw-table';
3078
+ if (striped) cls += ' bw-table-striped';
3079
+ if (hover) cls += ' bw-table-hover';
3080
+ if (className) cls += ' ' + className;
3081
+ cls = cls.trim();
3023
3082
 
3024
3083
  // Auto-detect columns if not provided
3025
3084
  const cols = columns || (data.length > 0
@@ -3107,11 +3166,176 @@ bw.makeTable = function(config) {
3107
3166
 
3108
3167
  return {
3109
3168
  t: 'table',
3110
- a: { class: className },
3169
+ a: { class: cls },
3111
3170
  c: [thead, tbody]
3112
3171
  };
3113
3172
  };
3114
3173
 
3174
+ /**
3175
+ * Create a table from a 2D array.
3176
+ *
3177
+ * Converts a 2D array into the object-array format that `bw.makeTable()`
3178
+ * expects, then delegates. By default, the first row is used as column
3179
+ * headers. All standard `makeTable` props (striped, hover, sortable,
3180
+ * columns, onSort, etc.) are passed through.
3181
+ *
3182
+ * @param {Object} config - Configuration object
3183
+ * @param {Array<Array>} config.data - 2D array of values
3184
+ * @param {boolean} [config.headerRow=true] - Treat first row as column headers
3185
+ * @param {boolean} [config.striped=false] - Striped rows
3186
+ * @param {boolean} [config.hover=false] - Hover highlight
3187
+ * @param {boolean} [config.sortable=true] - Enable sort
3188
+ * @param {Array<Object>} [config.columns] - Override auto-generated column defs
3189
+ * @param {string} [config.className=''] - Additional CSS classes
3190
+ * @param {Function} [config.onSort] - Sort callback
3191
+ * @param {string} [config.sortColumn] - Currently sorted column key
3192
+ * @param {string} [config.sortDirection='asc'] - Sort direction
3193
+ * @returns {Object} TACO object for table
3194
+ * @category Component Builders
3195
+ * @see bw.makeTable
3196
+ * @example
3197
+ * bw.makeTableFromArray({
3198
+ * data: [
3199
+ * ['Name', 'Role', 'Status'],
3200
+ * ['Alice', 'Engineer', 'Active'],
3201
+ * ['Bob', 'Designer', 'Away']
3202
+ * ],
3203
+ * striped: true,
3204
+ * hover: true
3205
+ * });
3206
+ */
3207
+ bw.makeTableFromArray = function(config) {
3208
+ const { data = [], headerRow = true, columns, ...rest } = config;
3209
+
3210
+ if (!Array.isArray(data) || data.length === 0) {
3211
+ return bw.makeTable({ data: [], columns: columns || [], ...rest });
3212
+ }
3213
+
3214
+ // Determine headers
3215
+ let headers;
3216
+ let rows;
3217
+ if (headerRow && data.length > 0) {
3218
+ headers = data[0].map(function(h) { return String(h); });
3219
+ rows = data.slice(1);
3220
+ } else {
3221
+ // Generate col0, col1, ... headers
3222
+ const width = data[0].length;
3223
+ headers = [];
3224
+ for (let i = 0; i < width; i++) {
3225
+ headers.push('col' + i);
3226
+ }
3227
+ rows = data;
3228
+ }
3229
+
3230
+ // Convert rows to object arrays
3231
+ const objData = rows.map(function(row) {
3232
+ const obj = {};
3233
+ headers.forEach(function(key, i) {
3234
+ obj[key] = row[i] !== undefined ? row[i] : '';
3235
+ });
3236
+ return obj;
3237
+ });
3238
+
3239
+ // Auto-generate column defs if not provided
3240
+ const cols = columns || headers.map(function(key) {
3241
+ return { key: key, label: key };
3242
+ });
3243
+
3244
+ return bw.makeTable({ data: objData, columns: cols, ...rest });
3245
+ };
3246
+
3247
+ /**
3248
+ * Create a vertical bar chart from data.
3249
+ *
3250
+ * Renders a pure-CSS bar chart using flexbox and percentage heights.
3251
+ * No canvas, SVG, or external charting library required.
3252
+ *
3253
+ * @param {Object} config - Chart configuration
3254
+ * @param {Array<Object>} config.data - Array of data objects
3255
+ * @param {string} [config.labelKey='label'] - Key for bar labels
3256
+ * @param {string} [config.valueKey='value'] - Key for bar values
3257
+ * @param {string} [config.title] - Chart title
3258
+ * @param {string} [config.color='#006666'] - Bar color (hex or CSS color)
3259
+ * @param {string} [config.height='200px'] - Height of the chart area
3260
+ * @param {Function} [config.formatValue] - Value label formatter: (value) => string
3261
+ * @param {boolean} [config.showValues=true] - Show value labels above bars
3262
+ * @param {boolean} [config.showLabels=true] - Show labels below bars
3263
+ * @param {string} [config.className=''] - Additional CSS classes
3264
+ * @returns {Object} TACO object
3265
+ * @category Component Builders
3266
+ * @example
3267
+ * bw.makeBarChart({
3268
+ * data: [
3269
+ * { label: 'Jan', value: 12400 },
3270
+ * { label: 'Feb', value: 15800 },
3271
+ * { label: 'Mar', value: 9200 }
3272
+ * ],
3273
+ * title: 'Monthly Revenue',
3274
+ * color: '#0077b6',
3275
+ * formatValue: (v) => '$' + (v / 1000).toFixed(1) + 'k'
3276
+ * });
3277
+ */
3278
+ bw.makeBarChart = function(config) {
3279
+ const {
3280
+ data = [],
3281
+ labelKey = 'label',
3282
+ valueKey = 'value',
3283
+ title,
3284
+ color = '#006666',
3285
+ height = '200px',
3286
+ formatValue,
3287
+ showValues = true,
3288
+ showLabels = true,
3289
+ className = ''
3290
+ } = config;
3291
+
3292
+ if (!Array.isArray(data) || data.length === 0) {
3293
+ return { t: 'div', a: { class: ('bw-bar-chart-container ' + className).trim() }, c: '' };
3294
+ }
3295
+
3296
+ const values = data.map(function(d) { return Number(d[valueKey]) || 0; });
3297
+ const maxVal = Math.max.apply(null, values);
3298
+
3299
+ const bars = data.map(function(d, i) {
3300
+ const val = values[i];
3301
+ const pct = maxVal > 0 ? (val / maxVal * 100) : 0;
3302
+ const formatted = formatValue ? formatValue(val) : String(val);
3303
+
3304
+ const children = [];
3305
+ if (showValues) {
3306
+ children.push({ t: 'div', a: { class: 'bw-bar-value' }, c: formatted });
3307
+ }
3308
+ children.push({
3309
+ t: 'div',
3310
+ a: {
3311
+ class: 'bw-bar',
3312
+ style: 'height:' + pct + '%;background:' + color + ';'
3313
+ }
3314
+ });
3315
+ if (showLabels) {
3316
+ children.push({ t: 'div', a: { class: 'bw-bar-label' }, c: String(d[labelKey] || '') });
3317
+ }
3318
+
3319
+ return { t: 'div', a: { class: 'bw-bar-group' }, c: children };
3320
+ });
3321
+
3322
+ const chartChildren = [];
3323
+ if (title) {
3324
+ chartChildren.push({ t: 'h3', a: { class: 'bw-bar-chart-title' }, c: title });
3325
+ }
3326
+ chartChildren.push({
3327
+ t: 'div',
3328
+ a: { class: 'bw-bar-chart', style: 'height:' + height + ';' },
3329
+ c: bars
3330
+ });
3331
+
3332
+ return {
3333
+ t: 'div',
3334
+ a: { class: ('bw-bar-chart-container ' + className).trim() },
3335
+ c: chartChildren
3336
+ };
3337
+ };
3338
+
3115
3339
  /**
3116
3340
  * Create a responsive data table with title and optional wrapper
3117
3341
  *
@@ -3122,7 +3346,9 @@ bw.makeTable = function(config) {
3122
3346
  * @param {string} [config.title] - Table title heading
3123
3347
  * @param {Array<Object>} config.data - Array of row objects
3124
3348
  * @param {Array<Object>} [config.columns] - Column definitions
3125
- * @param {string} [config.className="table table-striped table-hover"] - Table CSS class
3349
+ * @param {string} [config.className=''] - Additional CSS classes for the table
3350
+ * @param {boolean} [config.striped=true] - Add striped row styling
3351
+ * @param {boolean} [config.hover=true] - Add hover row highlighting
3126
3352
  * @param {boolean} [config.responsive=true] - Wrap table in responsive overflow div
3127
3353
  * @returns {Object} TACO object for table with wrapper
3128
3354
  * @example
@@ -3137,7 +3363,9 @@ bw.makeDataTable = function(config) {
3137
3363
  title,
3138
3364
  data,
3139
3365
  columns,
3140
- className = "table table-striped table-hover",
3366
+ className = '',
3367
+ striped = true,
3368
+ hover = true,
3141
3369
  responsive = true,
3142
3370
  ...tableConfig
3143
3371
  } = config;
@@ -3146,6 +3374,8 @@ bw.makeDataTable = function(config) {
3146
3374
  data,
3147
3375
  columns,
3148
3376
  className,
3377
+ striped,
3378
+ hover,
3149
3379
  ...tableConfig
3150
3380
  });
3151
3381
 
@@ -3,7 +3,7 @@
3
3
  * Creates class-based CSS to prevent collisions with other frameworks
4
4
  */
5
5
 
6
- import { getAllStyles } from './bitwrench-styles.js';
6
+ import { getAllStyles, getStructuralStyles } from './bitwrench-styles.js';
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
9
 
@@ -92,8 +92,25 @@ function processRules(rules, indent = '') {
92
92
  return css;
93
93
  }
94
94
 
95
- // Generate the CSS
96
- const styles = getAllStyles();
95
+ // Deep-merge themed + structural: structural properties override themed
96
+ // per-property within the same selector (not overwriting entire selectors)
97
+ function deepMergeStyles(base, overrides) {
98
+ const merged = Object.assign({}, base);
99
+ for (const [selector, rules] of Object.entries(overrides)) {
100
+ if (merged[selector] && typeof merged[selector] === 'object' && typeof rules === 'object') {
101
+ merged[selector] = Object.assign({}, merged[selector], rules);
102
+ } else {
103
+ merged[selector] = rules;
104
+ }
105
+ }
106
+ return merged;
107
+ }
108
+
109
+ // Generate the CSS — merge themed styles + structural styles
110
+ const themed = getAllStyles();
111
+ const structural = getStructuralStyles();
112
+ // Structural properties take precedence; themed properties preserved where structural doesn't override
113
+ const styles = deepMergeStyles(themed, structural);
97
114
  const css = stylesToCSS(styles);
98
115
 
99
116
  // Add additional bitwrench-specific styles
package/src/version.js CHANGED
@@ -3,14 +3,14 @@
3
3
  * DO NOT EDIT DIRECTLY - Use npm run generate-version
4
4
  */
5
5
 
6
- export const VERSION = '2.0.10';
6
+ export const VERSION = '2.0.12';
7
7
  export const VERSION_INFO = {
8
- version: '2.0.10',
8
+ version: '2.0.12',
9
9
  name: 'bitwrench',
10
10
  description: 'A library for javascript UI functions.',
11
11
  license: 'BSD-2-Clause',
12
- homepage: 'http://deftio.com/bitwrench',
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-07T03:14:16.606Z'
15
+ buildDate: '2026-03-07T22:31:35.755Z'
16
16
  };