bitwrench 2.0.12 → 2.0.14
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/README.md +4 -4
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +659 -346
- package/dist/bitwrench-lean.cjs.min.js +5 -5
- package/dist/bitwrench-lean.es5.js +960 -347
- package/dist/bitwrench-lean.es5.min.js +3 -3
- package/dist/bitwrench-lean.esm.js +659 -346
- package/dist/bitwrench-lean.esm.min.js +5 -5
- package/dist/bitwrench-lean.umd.js +659 -346
- package/dist/bitwrench-lean.umd.min.js +5 -5
- package/dist/bitwrench.cjs.js +1737 -452
- package/dist/bitwrench.cjs.min.js +6 -6
- package/dist/bitwrench.css +1625 -168
- package/dist/bitwrench.es5.js +4016 -2341
- package/dist/bitwrench.es5.min.js +4 -4
- package/dist/bitwrench.esm.js +1737 -452
- package/dist/bitwrench.esm.min.js +6 -6
- package/dist/bitwrench.umd.js +1737 -452
- package/dist/bitwrench.umd.min.js +6 -6
- package/dist/builds.json +61 -61
- package/dist/sri.json +26 -26
- package/package.json +1 -1
- package/readme.html +5 -5
- package/src/bitwrench-color-utils.js +137 -17
- package/src/bitwrench-components-v2.js +997 -35
- package/src/bitwrench-styles.js +1098 -370
- package/src/bitwrench.js +128 -75
- package/src/version.js +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! bitwrench-lean v2.0.
|
|
1
|
+
/*! bitwrench-lean v2.0.14 | 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.
|
|
14
|
+
version: '2.0.14',
|
|
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-
|
|
21
|
+
buildDate: '2026-03-08T08:04:06.572Z'
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -276,6 +276,29 @@
|
|
|
276
276
|
return relativeLuminance(hex) > 0.179 ? '#000' : '#fff';
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/**
|
|
280
|
+
* Shift a color's hue toward a target hue by a given amount.
|
|
281
|
+
* Uses shortest-arc interpolation on the hue wheel.
|
|
282
|
+
* @param {string} sourceHex - Color to shift
|
|
283
|
+
* @param {string} targetHex - Color whose hue to shift toward
|
|
284
|
+
* @param {number} [amount=0.20] - 0 = no shift, 1 = full shift to target hue
|
|
285
|
+
* @returns {string} Harmonized hex color
|
|
286
|
+
*/
|
|
287
|
+
function harmonize(sourceHex, targetHex, amount) {
|
|
288
|
+
if (amount === undefined) amount = 0.20;
|
|
289
|
+
if (amount === 0) return sourceHex;
|
|
290
|
+
var srcHsl = hexToHsl(sourceHex);
|
|
291
|
+
var tgtHsl = hexToHsl(targetHex);
|
|
292
|
+
|
|
293
|
+
// Shortest-arc hue interpolation
|
|
294
|
+
var diff = tgtHsl[0] - srcHsl[0];
|
|
295
|
+
if (diff > 180) diff -= 360;
|
|
296
|
+
if (diff < -180) diff += 360;
|
|
297
|
+
|
|
298
|
+
var newHue = (srcHsl[0] + diff * amount + 360) % 360;
|
|
299
|
+
return hslToHex([newHue, srcHsl[1], srcHsl[2]]);
|
|
300
|
+
}
|
|
301
|
+
|
|
279
302
|
/**
|
|
280
303
|
* Derive a full shade palette for a single semantic color.
|
|
281
304
|
* @param {string} hex - Base color hex
|
|
@@ -295,31 +318,128 @@
|
|
|
295
318
|
};
|
|
296
319
|
}
|
|
297
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Derive the alternate (luminance-inverted) version of a single seed color.
|
|
323
|
+
* Preserves hue, mirrors lightness, adjusts saturation for readability.
|
|
324
|
+
* @param {string} hex - Seed hex color
|
|
325
|
+
* @returns {string} Alternate hex color
|
|
326
|
+
*/
|
|
327
|
+
function deriveAlternateSeed(hex) {
|
|
328
|
+
var hsl = hexToHsl(hex);
|
|
329
|
+
var h = hsl[0], s = hsl[1], l = hsl[2];
|
|
330
|
+
var altL, altS;
|
|
331
|
+
|
|
332
|
+
if (l > 50) {
|
|
333
|
+
// Light color → make dark. Map 50-100 → 30-10 range
|
|
334
|
+
altL = clip(100 - l - 10, 8, 40);
|
|
335
|
+
// Reduce saturation slightly — vivid colors at low lightness look garish
|
|
336
|
+
altS = clip(s * 0.85, 0, 100);
|
|
337
|
+
} else {
|
|
338
|
+
// Dark color → make light. Map 0-50 → 65-92 range
|
|
339
|
+
altL = clip(100 - l + 10, 60, 92);
|
|
340
|
+
// Slightly increase saturation for light variant
|
|
341
|
+
altS = clip(s * 1.1, 0, 100);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return hslToHex([h, altS, altL]);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Determine whether a palette config is "light-flavored" based on
|
|
349
|
+
* the average luminance of its seed colors.
|
|
350
|
+
* @param {Object} config - Theme config with primary, secondary hex colors
|
|
351
|
+
* @returns {boolean} true if the seeds are predominantly light
|
|
352
|
+
*/
|
|
353
|
+
function isLightPalette(config) {
|
|
354
|
+
var lum = relativeLuminance(config.primary);
|
|
355
|
+
if (config.secondary) lum = (lum + relativeLuminance(config.secondary)) / 2;
|
|
356
|
+
if (config.tertiary) lum = (lum * 2 + relativeLuminance(config.tertiary)) / 3;
|
|
357
|
+
return lum > 0.179;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Derive a complete alternate config from a primary theme config.
|
|
362
|
+
* Each seed color is luminance-inverted; semantic colors are adjusted for
|
|
363
|
+
* the new luminance context.
|
|
364
|
+
* @param {Object} config - Primary theme config
|
|
365
|
+
* @returns {Object} Alternate theme config (same shape, inverted lightness)
|
|
366
|
+
*/
|
|
367
|
+
function deriveAlternateConfig(config) {
|
|
368
|
+
var alt = {};
|
|
369
|
+
// Invert the user's seed colors
|
|
370
|
+
alt.primary = deriveAlternateSeed(config.primary);
|
|
371
|
+
alt.secondary = deriveAlternateSeed(config.secondary);
|
|
372
|
+
alt.tertiary = config.tertiary ? deriveAlternateSeed(config.tertiary) : alt.primary;
|
|
373
|
+
|
|
374
|
+
// Derive alternate surface colors from primary hue
|
|
375
|
+
var priHsl = hexToHsl(config.primary);
|
|
376
|
+
var h = priHsl[0];
|
|
377
|
+
var isLight = isLightPalette(config);
|
|
378
|
+
|
|
379
|
+
if (isLight) {
|
|
380
|
+
// Primary is light → alternate needs dark surfaces
|
|
381
|
+
alt.light = hslToHex([h, Math.min(priHsl[1], 15), 15]);
|
|
382
|
+
alt.dark = hslToHex([h, 5, 88]);
|
|
383
|
+
} else {
|
|
384
|
+
// Primary is dark → alternate needs light surfaces
|
|
385
|
+
alt.light = hslToHex([h, Math.min(priHsl[1], 10), 96]);
|
|
386
|
+
alt.dark = hslToHex([h, 10, 18]);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Semantic colors: harmonize toward primary, then invert for alternate
|
|
390
|
+
var amt = config.harmonize !== undefined ? config.harmonize : 0.20;
|
|
391
|
+
var semanticDefaults = {
|
|
392
|
+
success: '#198754', danger: '#dc3545',
|
|
393
|
+
warning: '#f0ad4e', info: '#17a2b8'
|
|
394
|
+
};
|
|
395
|
+
var semantics = ['success', 'danger', 'warning', 'info'];
|
|
396
|
+
for (var i = 0; i < semantics.length; i++) {
|
|
397
|
+
var key = semantics[i];
|
|
398
|
+
var seed = config[key] || semanticDefaults[key];
|
|
399
|
+
var harmonized = harmonize(seed, config.primary, amt);
|
|
400
|
+
alt[key] = deriveAlternateSeed(harmonized);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Semantic colors are already harmonized+inverted — don't re-harmonize in derivePalette
|
|
404
|
+
alt.harmonize = 0;
|
|
405
|
+
|
|
406
|
+
return alt;
|
|
407
|
+
}
|
|
408
|
+
|
|
298
409
|
/**
|
|
299
410
|
* Derive complete palette from a theme config object.
|
|
411
|
+
* Semantic colors are harmonized toward the primary hue (configurable).
|
|
412
|
+
* Light/dark surface colors are tinted with the primary hue.
|
|
300
413
|
* @param {Object} config - Theme config with primary, secondary, tertiary, etc.
|
|
301
|
-
* @
|
|
414
|
+
* @param {number} [config.harmonize=0.20] - Hue shift amount for semantic colors (0-1)
|
|
415
|
+
* @returns {Object} Full palette with shades for all 9 semantic colors
|
|
302
416
|
*/
|
|
303
417
|
function derivePalette(config) {
|
|
304
|
-
var
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
418
|
+
var amt = config.harmonize !== undefined ? config.harmonize : 0.20;
|
|
419
|
+
var pri = config.primary;
|
|
420
|
+
var priHsl = hexToHsl(pri);
|
|
421
|
+
var h = priHsl[0];
|
|
422
|
+
|
|
423
|
+
// Semantic defaults — harmonized toward primary hue
|
|
424
|
+
var successBase = harmonize(config.success || '#198754', pri, amt);
|
|
425
|
+
var dangerBase = harmonize(config.danger || '#dc3545', pri, amt);
|
|
426
|
+
var warningBase = harmonize(config.warning || '#f0ad4e', pri, amt);
|
|
427
|
+
var infoBase = harmonize(config.info || '#17a2b8', pri, amt);
|
|
428
|
+
|
|
429
|
+
// Light/dark: derive from primary hue with low saturation (if not user-supplied)
|
|
430
|
+
var lightBase = config.light || hslToHex([h, 8, 97]);
|
|
431
|
+
var darkBase = config.dark || hslToHex([h, 10, 13]);
|
|
312
432
|
|
|
313
433
|
var palette = {
|
|
314
|
-
primary:
|
|
434
|
+
primary: deriveShades(config.primary),
|
|
315
435
|
secondary: deriveShades(config.secondary),
|
|
316
|
-
tertiary:
|
|
317
|
-
success:
|
|
318
|
-
danger:
|
|
319
|
-
warning:
|
|
320
|
-
info:
|
|
321
|
-
light:
|
|
322
|
-
dark:
|
|
436
|
+
tertiary: deriveShades(config.tertiary),
|
|
437
|
+
success: deriveShades(successBase),
|
|
438
|
+
danger: deriveShades(dangerBase),
|
|
439
|
+
warning: deriveShades(warningBase),
|
|
440
|
+
info: deriveShades(infoBase),
|
|
441
|
+
light: deriveShades(lightBase),
|
|
442
|
+
dark: deriveShades(darkBase)
|
|
323
443
|
};
|
|
324
444
|
|
|
325
445
|
return palette;
|
|
@@ -368,6 +488,88 @@
|
|
|
368
488
|
pill: { btn: '50rem', card: '1rem', badge: '50rem', alert: '1rem', input: '50rem' }
|
|
369
489
|
};
|
|
370
490
|
|
|
491
|
+
// ---- Typography scale presets ----
|
|
492
|
+
|
|
493
|
+
var TYPE_RATIO_PRESETS = {
|
|
494
|
+
tight: 1.125,
|
|
495
|
+
normal: 1.200,
|
|
496
|
+
relaxed: 1.250,
|
|
497
|
+
dramatic: 1.333
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Generate a modular type scale from a base size and ratio.
|
|
502
|
+
* @param {number} base - Base font size in px (default 16)
|
|
503
|
+
* @param {number} ratio - Scale ratio (default 1.200)
|
|
504
|
+
* @returns {Object} { xs, sm, base, lg, xl, '2xl', '3xl', '4xl' } in px
|
|
505
|
+
*/
|
|
506
|
+
function generateTypeScale(base, ratio) {
|
|
507
|
+
if (!base) base = 16;
|
|
508
|
+
if (!ratio) ratio = 1.200;
|
|
509
|
+
return {
|
|
510
|
+
xs: Math.round(base / (ratio * ratio)),
|
|
511
|
+
sm: Math.round(base / ratio),
|
|
512
|
+
base: base,
|
|
513
|
+
lg: Math.round(base * ratio),
|
|
514
|
+
xl: Math.round(base * ratio * ratio),
|
|
515
|
+
'2xl': Math.round(base * Math.pow(ratio, 3)),
|
|
516
|
+
'3xl': Math.round(base * Math.pow(ratio, 4)),
|
|
517
|
+
'4xl': Math.round(base * Math.pow(ratio, 5))
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ---- Elevation (shadow depth) presets ----
|
|
522
|
+
|
|
523
|
+
var ELEVATION_PRESETS = {
|
|
524
|
+
flat: {
|
|
525
|
+
sm: 'none',
|
|
526
|
+
md: 'none',
|
|
527
|
+
lg: 'none',
|
|
528
|
+
xl: 'none'
|
|
529
|
+
},
|
|
530
|
+
sm: {
|
|
531
|
+
sm: '0 1px 2px rgba(0,0,0,0.05)',
|
|
532
|
+
md: '0 1px 3px rgba(0,0,0,0.08)',
|
|
533
|
+
lg: '0 2px 6px rgba(0,0,0,0.10)',
|
|
534
|
+
xl: '0 4px 12px rgba(0,0,0,0.12)'
|
|
535
|
+
},
|
|
536
|
+
md: {
|
|
537
|
+
sm: '0 1px 3px rgba(0,0,0,0.08)',
|
|
538
|
+
md: '0 2px 6px rgba(0,0,0,0.12)',
|
|
539
|
+
lg: '0 4px 12px rgba(0,0,0,0.16)',
|
|
540
|
+
xl: '0 8px 24px rgba(0,0,0,0.20)'
|
|
541
|
+
},
|
|
542
|
+
lg: {
|
|
543
|
+
sm: '0 2px 4px rgba(0,0,0,0.10)',
|
|
544
|
+
md: '0 4px 12px rgba(0,0,0,0.16)',
|
|
545
|
+
lg: '0 8px 24px rgba(0,0,0,0.22)',
|
|
546
|
+
xl: '0 16px 48px rgba(0,0,0,0.28)'
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
// ---- Motion (transition) presets ----
|
|
551
|
+
|
|
552
|
+
var MOTION_PRESETS = {
|
|
553
|
+
reduced: {
|
|
554
|
+
fast: '0ms',
|
|
555
|
+
normal: '0ms',
|
|
556
|
+
slow: '0ms',
|
|
557
|
+
easing: 'linear'
|
|
558
|
+
},
|
|
559
|
+
standard: {
|
|
560
|
+
fast: '100ms',
|
|
561
|
+
normal: '200ms',
|
|
562
|
+
slow: '300ms',
|
|
563
|
+
easing: 'ease-out'
|
|
564
|
+
},
|
|
565
|
+
expressive: {
|
|
566
|
+
fast: '150ms',
|
|
567
|
+
normal: '300ms',
|
|
568
|
+
slow: '500ms',
|
|
569
|
+
easing: 'cubic-bezier(0.34, 1.56, 0.64, 1)'
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
371
573
|
/**
|
|
372
574
|
* Default palette config — matches existing hardcoded colors
|
|
373
575
|
*/
|
|
@@ -377,8 +579,8 @@
|
|
|
377
579
|
tertiary: '#006666',
|
|
378
580
|
success: '#198754',
|
|
379
581
|
danger: '#dc3545',
|
|
380
|
-
warning: '#
|
|
381
|
-
info: '#
|
|
582
|
+
warning: '#b38600',
|
|
583
|
+
info: '#0891b2',
|
|
382
584
|
light: '#f8f9fa',
|
|
383
585
|
dark: '#212529'
|
|
384
586
|
};
|
|
@@ -403,18 +605,32 @@
|
|
|
403
605
|
};
|
|
404
606
|
|
|
405
607
|
/**
|
|
406
|
-
* Resolve layout config to spacing
|
|
407
|
-
* @param {Object} config - { spacing, radius, fontSize }
|
|
408
|
-
* @returns {Object} { spacing, radius, fontSize }
|
|
608
|
+
* Resolve layout config to spacing, radius, typeScale, elevation, and motion objects.
|
|
609
|
+
* @param {Object} config - { spacing, radius, fontSize, typeRatio, elevation, motion }
|
|
610
|
+
* @returns {Object} { spacing, radius, fontSize, typeScale, elevation, motion }
|
|
409
611
|
*/
|
|
410
612
|
function resolveLayout(config) {
|
|
411
613
|
var sp = (config && config.spacing) || 'normal';
|
|
412
614
|
var rd = (config && config.radius) || 'md';
|
|
413
615
|
var fs = (config && config.fontSize) || 1.0;
|
|
616
|
+
|
|
617
|
+
// typeRatio: accept preset name or number
|
|
618
|
+
var tr = (config && config.typeRatio) || 'normal';
|
|
619
|
+
var ratioNum = typeof tr === 'string' ? (TYPE_RATIO_PRESETS[tr] || TYPE_RATIO_PRESETS.normal) : tr;
|
|
620
|
+
|
|
621
|
+
// elevation: accept preset name or object
|
|
622
|
+
var el = (config && config.elevation) || 'md';
|
|
623
|
+
|
|
624
|
+
// motion: accept preset name or object
|
|
625
|
+
var mo = (config && config.motion) || 'standard';
|
|
626
|
+
|
|
414
627
|
return {
|
|
415
628
|
spacing: typeof sp === 'string' ? (SPACING_PRESETS[sp] || SPACING_PRESETS.normal) : sp,
|
|
416
629
|
radius: typeof rd === 'string' ? (RADIUS_PRESETS[rd] || RADIUS_PRESETS.md) : rd,
|
|
417
|
-
fontSize: fs
|
|
630
|
+
fontSize: fs,
|
|
631
|
+
typeScale: generateTypeScale(16, ratioNum),
|
|
632
|
+
elevation: typeof el === 'string' ? (ELEVATION_PRESETS[el] || ELEVATION_PRESETS.md) : el,
|
|
633
|
+
motion: typeof mo === 'string' ? (MOTION_PRESETS[mo] || MOTION_PRESETS.standard) : mo
|
|
418
634
|
};
|
|
419
635
|
}
|
|
420
636
|
|
|
@@ -438,12 +654,13 @@
|
|
|
438
654
|
// Themed CSS generators
|
|
439
655
|
// =========================================================================
|
|
440
656
|
|
|
441
|
-
function generateTypographyThemed(scope, palette) {
|
|
657
|
+
function generateTypographyThemed(scope, palette, layout) {
|
|
658
|
+
var mot = layout.motion;
|
|
442
659
|
var rules = {};
|
|
443
660
|
rules[scopeSelector(scope, 'a')] = {
|
|
444
661
|
'color': palette.primary.base,
|
|
445
662
|
'text-decoration': 'none',
|
|
446
|
-
'transition': 'color
|
|
663
|
+
'transition': 'color ' + mot.fast + ' ' + mot.easing
|
|
447
664
|
};
|
|
448
665
|
rules[scopeSelector(scope, 'a:hover')] = {
|
|
449
666
|
'color': palette.primary.hover,
|
|
@@ -463,7 +680,8 @@
|
|
|
463
680
|
'border-radius': rd.btn
|
|
464
681
|
};
|
|
465
682
|
rules[scopeSelector(scope, '.bw-btn:focus-visible')] = {
|
|
466
|
-
'outline': '
|
|
683
|
+
'outline': '2px solid currentColor',
|
|
684
|
+
'outline-offset': '2px',
|
|
467
685
|
'box-shadow': '0 0 0 3px ' + palette.primary.focus
|
|
468
686
|
};
|
|
469
687
|
|
|
@@ -553,14 +771,15 @@
|
|
|
553
771
|
var sp = layout.spacing;
|
|
554
772
|
var rd = layout.radius;
|
|
555
773
|
|
|
774
|
+
var elev = layout.elevation;
|
|
556
775
|
rules[scopeSelector(scope, '.bw-card')] = {
|
|
557
776
|
'background-color': '#fff',
|
|
558
777
|
'border': '1px solid ' + palette.light.border,
|
|
559
778
|
'border-radius': rd.card,
|
|
560
|
-
'box-shadow':
|
|
779
|
+
'box-shadow': elev.sm
|
|
561
780
|
};
|
|
562
781
|
rules[scopeSelector(scope, '.bw-card:hover')] = {
|
|
563
|
-
'box-shadow':
|
|
782
|
+
'box-shadow': elev.md
|
|
564
783
|
};
|
|
565
784
|
rules[scopeSelector(scope, '.bw-card-body')] = {
|
|
566
785
|
'padding': sp.card
|
|
@@ -607,6 +826,8 @@
|
|
|
607
826
|
};
|
|
608
827
|
rules[scopeSelector(scope, '.bw-form-control:focus')] = {
|
|
609
828
|
'border-color': palette.primary.border,
|
|
829
|
+
'outline': '2px solid ' + palette.primary.base,
|
|
830
|
+
'outline-offset': '-1px',
|
|
610
831
|
'box-shadow': '0 0 0 0.25rem ' + palette.primary.focus
|
|
611
832
|
};
|
|
612
833
|
rules[scopeSelector(scope, '.bw-form-control::placeholder')] = {
|
|
@@ -764,7 +985,8 @@
|
|
|
764
985
|
'border-color': palette.light.border
|
|
765
986
|
};
|
|
766
987
|
rules[scopeSelector(scope, '.bw-page-link:focus')] = {
|
|
767
|
-
'
|
|
988
|
+
'outline': '2px solid ' + palette.primary.base,
|
|
989
|
+
'outline-offset': '-2px'
|
|
768
990
|
};
|
|
769
991
|
rules[scopeSelector(scope, '.bw-page-item.bw-active .bw-page-link')] = {
|
|
770
992
|
'color': palette.primary.textOn,
|
|
@@ -923,12 +1145,12 @@
|
|
|
923
1145
|
return rules;
|
|
924
1146
|
}
|
|
925
1147
|
|
|
926
|
-
function generateModalThemed(scope, palette) {
|
|
1148
|
+
function generateModalThemed(scope, palette, layout) {
|
|
927
1149
|
var rules = {};
|
|
928
1150
|
rules[scopeSelector(scope, '.bw-modal-content')] = {
|
|
929
1151
|
'background-color': '#fff',
|
|
930
1152
|
'border-color': palette.light.border,
|
|
931
|
-
'box-shadow':
|
|
1153
|
+
'box-shadow': layout.elevation.lg
|
|
932
1154
|
};
|
|
933
1155
|
rules[scopeSelector(scope, '.bw-modal-header')] = {
|
|
934
1156
|
'border-bottom-color': palette.light.border
|
|
@@ -942,12 +1164,12 @@
|
|
|
942
1164
|
return rules;
|
|
943
1165
|
}
|
|
944
1166
|
|
|
945
|
-
function generateToastThemed(scope, palette) {
|
|
1167
|
+
function generateToastThemed(scope, palette, layout) {
|
|
946
1168
|
var rules = {};
|
|
947
1169
|
rules[scopeSelector(scope, '.bw-toast')] = {
|
|
948
1170
|
'background-color': '#fff',
|
|
949
1171
|
'border-color': 'rgba(0,0,0,0.1)',
|
|
950
|
-
'box-shadow':
|
|
1172
|
+
'box-shadow': layout.elevation.lg
|
|
951
1173
|
};
|
|
952
1174
|
rules[scopeSelector(scope, '.bw-toast-header')] = {
|
|
953
1175
|
'border-bottom-color': 'rgba(0,0,0,0.05)'
|
|
@@ -961,12 +1183,12 @@
|
|
|
961
1183
|
return rules;
|
|
962
1184
|
}
|
|
963
1185
|
|
|
964
|
-
function generateDropdownThemed(scope, palette) {
|
|
1186
|
+
function generateDropdownThemed(scope, palette, layout) {
|
|
965
1187
|
var rules = {};
|
|
966
1188
|
rules[scopeSelector(scope, '.bw-dropdown-menu')] = {
|
|
967
1189
|
'background-color': '#fff',
|
|
968
1190
|
'border-color': palette.light.border,
|
|
969
|
-
'box-shadow':
|
|
1191
|
+
'box-shadow': layout.elevation.md
|
|
970
1192
|
};
|
|
971
1193
|
rules[scopeSelector(scope, '.bw-dropdown-item')] = {
|
|
972
1194
|
'color': palette.dark.base
|
|
@@ -1032,7 +1254,7 @@
|
|
|
1032
1254
|
function generateThemedCSS(scopeName, palette, layout) {
|
|
1033
1255
|
return Object.assign({},
|
|
1034
1256
|
generateResetThemed(scopeName, palette),
|
|
1035
|
-
generateTypographyThemed(scopeName, palette),
|
|
1257
|
+
generateTypographyThemed(scopeName, palette, layout),
|
|
1036
1258
|
generateButtons(scopeName, palette, layout),
|
|
1037
1259
|
generateAlerts(scopeName, palette, layout),
|
|
1038
1260
|
generateBadges(scopeName, palette),
|
|
@@ -1051,9 +1273,9 @@
|
|
|
1051
1273
|
generateSectionsThemed(scopeName, palette),
|
|
1052
1274
|
generateAccordionThemed(scopeName, palette),
|
|
1053
1275
|
generateCarouselThemed(scopeName, palette),
|
|
1054
|
-
generateModalThemed(scopeName, palette),
|
|
1055
|
-
generateToastThemed(scopeName, palette),
|
|
1056
|
-
generateDropdownThemed(scopeName, palette),
|
|
1276
|
+
generateModalThemed(scopeName, palette, layout),
|
|
1277
|
+
generateToastThemed(scopeName, palette, layout),
|
|
1278
|
+
generateDropdownThemed(scopeName, palette, layout),
|
|
1057
1279
|
generateSwitchThemed(scopeName, palette),
|
|
1058
1280
|
generateSkeletonThemed(scopeName, palette),
|
|
1059
1281
|
generateAvatarThemed(scopeName, palette),
|
|
@@ -1206,11 +1428,23 @@
|
|
|
1206
1428
|
// =========================================================================
|
|
1207
1429
|
|
|
1208
1430
|
/**
|
|
1209
|
-
* Structural styles
|
|
1210
|
-
* properties. No colors, backgrounds, shadows, or border-colors.
|
|
1211
|
-
* These never change with themes.
|
|
1431
|
+
* Structural styles — layout, sizing, spacing, positioning, and behavior.
|
|
1212
1432
|
*
|
|
1213
|
-
*
|
|
1433
|
+
* POLICY: No colors, backgrounds, shadows, or border-colors in this function.
|
|
1434
|
+
* All cosmetic values belong in `defaultStyles.*` sections (unthemed defaults)
|
|
1435
|
+
* or in `generateThemedCSS()` (theme-driven colors).
|
|
1436
|
+
*
|
|
1437
|
+
* Exception: `.bw-progress-bar-striped` uses rgba(255,255,255,.15) for the
|
|
1438
|
+
* stripe pattern overlay. This is theme-neutral — a semi-transparent white
|
|
1439
|
+
* gradient that creates visible stripes on any background color.
|
|
1440
|
+
*
|
|
1441
|
+
* Architecture:
|
|
1442
|
+
* getStructuralStyles() → layout-only rules (never change with themes)
|
|
1443
|
+
* defaultStyles.* → cosmetic defaults (colors, shadows, borders)
|
|
1444
|
+
* generateThemedCSS() → palette-driven cosmetics from seed colors
|
|
1445
|
+
* generateAlternateCSS() → alternate palette (luminance-inverted)
|
|
1446
|
+
*
|
|
1447
|
+
* @returns {Object} CSS rules object (layout-only, theme-independent)
|
|
1214
1448
|
*/
|
|
1215
1449
|
function getStructuralStyles() {
|
|
1216
1450
|
var rules = {};
|
|
@@ -1261,12 +1495,12 @@
|
|
|
1261
1495
|
'text-decoration': 'none', 'vertical-align': 'middle', 'cursor': 'pointer',
|
|
1262
1496
|
'user-select': 'none', 'border': '1px solid transparent',
|
|
1263
1497
|
'padding': '0.5rem 1.125rem', 'font-size': '0.875rem', 'font-family': 'inherit',
|
|
1264
|
-
'border-radius': '6px', 'transition': 'all 0.15s
|
|
1498
|
+
'border-radius': '6px', 'transition': 'all 0.15s ease-out',
|
|
1265
1499
|
'gap': '0.5rem'
|
|
1266
1500
|
};
|
|
1267
1501
|
rules['.bw-btn:hover'] = { 'text-decoration': 'none', 'transform': 'translateY(-1px)' };
|
|
1268
1502
|
rules['.bw-btn:active'] = { 'transform': 'translateY(0)' };
|
|
1269
|
-
rules['.bw-btn:focus-visible'] = { 'outline': '
|
|
1503
|
+
rules['.bw-btn:focus-visible'] = { 'outline': '2px solid currentColor', 'outline-offset': '2px' };
|
|
1270
1504
|
rules['.bw-btn:disabled'] = { 'opacity': '0.5', 'cursor': 'not-allowed', 'pointer-events': 'none' };
|
|
1271
1505
|
rules['.bw-btn-lg'] = { 'padding': '0.625rem 1.5rem', 'font-size': '1rem', 'border-radius': '8px' };
|
|
1272
1506
|
rules['.bw-btn-sm'] = { 'padding': '0.25rem 0.75rem', 'font-size': '0.8125rem', 'border-radius': '5px' };
|
|
@@ -1276,7 +1510,7 @@
|
|
|
1276
1510
|
'position': 'relative', 'display': 'flex', 'flex-direction': 'column',
|
|
1277
1511
|
'min-width': '0', 'height': '100%', 'word-wrap': 'break-word',
|
|
1278
1512
|
'background-clip': 'border-box', 'border': '1px solid transparent',
|
|
1279
|
-
'border-radius': '8px', 'transition': 'box-shadow 0.2s
|
|
1513
|
+
'border-radius': '8px', 'transition': 'box-shadow 0.2s ease-out, transform 0.2s ease-out',
|
|
1280
1514
|
'margin-bottom': '1.5rem', 'overflow': 'hidden'
|
|
1281
1515
|
};
|
|
1282
1516
|
rules['.bw-card-body'] = { 'flex': '1 1 auto', 'padding': '1.25rem 1.5rem' };
|
|
@@ -1285,7 +1519,7 @@
|
|
|
1285
1519
|
rules['.bw-card-text'] = { 'margin-bottom': '0', 'font-size': '0.9375rem', 'line-height': '1.6' };
|
|
1286
1520
|
rules['.bw-card-header'] = { 'padding': '0.875rem 1.5rem', 'margin-bottom': '0', 'font-weight': '600', 'font-size': '0.875rem' };
|
|
1287
1521
|
rules['.bw-card-footer'] = { 'padding': '0.75rem 1.5rem', 'font-size': '0.875rem' };
|
|
1288
|
-
rules['.bw-card-hoverable'] = { 'transition': 'all 0.3s
|
|
1522
|
+
rules['.bw-card-hoverable'] = { 'transition': 'all 0.3s ease-out' };
|
|
1289
1523
|
rules['.bw-card-img-top'] = { 'width': '100%', 'border-top-left-radius': '7px', 'border-top-right-radius': '7px' };
|
|
1290
1524
|
rules['.bw-card-img-bottom'] = { 'width': '100%', 'border-bottom-left-radius': '7px', 'border-bottom-right-radius': '7px' };
|
|
1291
1525
|
rules['.bw-card-img-left'] = { 'width': '40%', 'object-fit': 'cover' };
|
|
@@ -1298,10 +1532,10 @@
|
|
|
1298
1532
|
'font-size': '0.9375rem', 'font-weight': '400', 'line-height': '1.5',
|
|
1299
1533
|
'background-clip': 'padding-box', 'appearance': 'none',
|
|
1300
1534
|
'border': '1px solid transparent', 'border-radius': '6px',
|
|
1301
|
-
'transition': 'border-color 0.15s ease-
|
|
1535
|
+
'transition': 'border-color 0.15s ease-out, box-shadow 0.15s ease-out',
|
|
1302
1536
|
'font-family': 'inherit'
|
|
1303
1537
|
};
|
|
1304
|
-
rules['.bw-form-control:focus'] = { 'outline': '
|
|
1538
|
+
rules['.bw-form-control:focus'] = { 'outline': '2px solid currentColor', 'outline-offset': '-1px' };
|
|
1305
1539
|
rules['.bw-form-control::placeholder'] = { 'opacity': '1' };
|
|
1306
1540
|
rules['.bw-form-label'] = { 'display': 'block', 'margin-bottom': '0.375rem', 'font-size': '0.875rem', 'font-weight': '600' };
|
|
1307
1541
|
rules['.bw-form-group'] = { 'margin-bottom': '1.25rem' };
|
|
@@ -1313,6 +1547,10 @@
|
|
|
1313
1547
|
};
|
|
1314
1548
|
rules['textarea.bw-form-control'] = { 'min-height': '5rem', 'resize': 'vertical' };
|
|
1315
1549
|
|
|
1550
|
+
// Form validation (structural)
|
|
1551
|
+
rules['.bw-valid-feedback'] = { 'display': 'block', 'font-size': '0.875rem', 'margin-top': '0.25rem' };
|
|
1552
|
+
rules['.bw-invalid-feedback'] = { 'display': 'block', 'font-size': '0.875rem', 'margin-top': '0.25rem' };
|
|
1553
|
+
|
|
1316
1554
|
// Form checks (structural)
|
|
1317
1555
|
Object.assign(rules, {
|
|
1318
1556
|
'.bw-form-check': { 'display': 'flex', 'align-items': 'center', 'gap': '0.5rem', 'min-height': '1.5rem', 'margin-bottom': '0.25rem' },
|
|
@@ -1371,13 +1609,13 @@
|
|
|
1371
1609
|
|
|
1372
1610
|
// Badges (structural)
|
|
1373
1611
|
rules['.bw-badge'] = {
|
|
1374
|
-
'display': 'inline-block', 'padding': '.
|
|
1612
|
+
'display': 'inline-block', 'padding': '0.375rem 0.625rem', 'font-size': '0.875rem',
|
|
1375
1613
|
'font-weight': '600', 'line-height': '1.3', 'text-align': 'center',
|
|
1376
1614
|
'white-space': 'nowrap', 'vertical-align': 'baseline', 'border-radius': '.375rem'
|
|
1377
1615
|
};
|
|
1378
1616
|
rules['.bw-badge:empty'] = { 'display': 'none' };
|
|
1379
|
-
rules['.bw-badge-sm'] = { 'font-size': '.
|
|
1380
|
-
rules['.bw-badge-lg'] = { 'font-size': '
|
|
1617
|
+
rules['.bw-badge-sm'] = { 'font-size': '0.75rem', 'padding': '0.25rem 0.5rem' };
|
|
1618
|
+
rules['.bw-badge-lg'] = { 'font-size': '1rem', 'padding': '0.5rem 0.875rem' };
|
|
1381
1619
|
rules['.bw-badge-pill'] = { 'border-radius': '50rem' };
|
|
1382
1620
|
|
|
1383
1621
|
// Progress (structural)
|
|
@@ -1385,7 +1623,7 @@
|
|
|
1385
1623
|
rules['.bw-progress-bar'] = {
|
|
1386
1624
|
'display': 'flex', 'flex-direction': 'column', 'justify-content': 'center',
|
|
1387
1625
|
'overflow': 'hidden', 'text-align': 'center', 'white-space': 'nowrap',
|
|
1388
|
-
'transition': 'width .
|
|
1626
|
+
'transition': 'width 0.3s ease-out', 'font-weight': '600'
|
|
1389
1627
|
};
|
|
1390
1628
|
rules['.bw-progress-bar-striped'] = {
|
|
1391
1629
|
'background-image': 'linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)',
|
|
@@ -1402,7 +1640,7 @@
|
|
|
1402
1640
|
'display': 'block', 'padding': '0.625rem 1rem', 'font-size': '0.875rem',
|
|
1403
1641
|
'font-weight': '500', 'text-decoration': 'none', 'cursor': 'pointer',
|
|
1404
1642
|
'border': 'none', 'background': 'transparent',
|
|
1405
|
-
'transition': 'color 0.15s, border-color 0.15s', 'font-family': 'inherit'
|
|
1643
|
+
'transition': 'color 0.15s ease-out, background-color 0.15s ease-out, border-color 0.15s ease-out', 'font-family': 'inherit'
|
|
1406
1644
|
};
|
|
1407
1645
|
rules['.bw-nav-tabs .bw-nav-link'] = { 'border': 'none', 'border-bottom': '2px solid transparent', 'border-radius': '0', 'background-color': 'transparent' };
|
|
1408
1646
|
rules['.bw-nav-pills .bw-nav-link'] = { 'border-radius': '6px' };
|
|
@@ -1420,7 +1658,8 @@
|
|
|
1420
1658
|
rules['.bw-list-group-item:last-child'] = { 'border-bottom-right-radius': 'inherit', 'border-bottom-left-radius': 'inherit' };
|
|
1421
1659
|
rules['.bw-list-group-item + .bw-list-group-item'] = { 'border-top-width': '0' };
|
|
1422
1660
|
rules['.bw-list-group-item.disabled'] = { 'pointer-events': 'none' };
|
|
1423
|
-
rules['a.bw-list-group-item'] = { 'cursor': 'pointer' };
|
|
1661
|
+
rules['a.bw-list-group-item'] = { 'cursor': 'pointer', 'transition': 'background-color 0.15s ease-out, color 0.15s ease-out' };
|
|
1662
|
+
rules['a.bw-list-group-item:focus-visible, .bw-list-group-item:focus-visible'] = { 'z-index': '2', 'outline': '2px solid currentColor', 'outline-offset': '-2px' };
|
|
1424
1663
|
rules['.bw-list-group-flush'] = { 'border-radius': '0' };
|
|
1425
1664
|
rules['.bw-list-group-flush > .bw-list-group-item'] = { 'border-width': '0 0 1px', 'border-radius': '0' };
|
|
1426
1665
|
rules['.bw-list-group-flush > .bw-list-group-item:last-child'] = { 'border-bottom-width': '0' };
|
|
@@ -1431,16 +1670,19 @@
|
|
|
1431
1670
|
rules['.bw-page-link'] = {
|
|
1432
1671
|
'position': 'relative', 'display': 'block', 'padding': '0.375rem 0.75rem',
|
|
1433
1672
|
'margin-left': '-1px', 'line-height': '1.25', 'text-decoration': 'none',
|
|
1434
|
-
'transition': 'color 0.15s ease-
|
|
1673
|
+
'transition': 'color 0.15s ease-out, background-color 0.15s ease-out, border-color 0.15s ease-out'
|
|
1435
1674
|
};
|
|
1436
1675
|
rules['.bw-page-item:first-child .bw-page-link'] = { 'margin-left': '0', 'border-top-left-radius': '0.375rem', 'border-bottom-left-radius': '0.375rem' };
|
|
1437
1676
|
rules['.bw-page-item:last-child .bw-page-link'] = { 'border-top-right-radius': '0.375rem', 'border-bottom-right-radius': '0.375rem' };
|
|
1677
|
+
rules['.bw-page-link:focus-visible'] = { 'z-index': '3', 'outline': '2px solid currentColor', 'outline-offset': '-2px' };
|
|
1438
1678
|
|
|
1439
1679
|
// Breadcrumb (structural)
|
|
1440
1680
|
rules['.bw-breadcrumb'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'padding': '0 0', 'margin-bottom': '1rem', 'list-style': 'none' };
|
|
1441
1681
|
rules['.bw-breadcrumb-item'] = { 'display': 'flex' };
|
|
1442
1682
|
rules['.bw-breadcrumb-item + .bw-breadcrumb-item'] = { 'padding-left': '0.5rem' };
|
|
1443
1683
|
rules['.bw-breadcrumb-item + .bw-breadcrumb-item::before'] = { 'float': 'left', 'padding-right': '0.5rem', 'content': '"/"' };
|
|
1684
|
+
rules['.bw-breadcrumb-item a'] = { 'text-decoration': 'none', 'transition': 'color 0.15s ease-out' };
|
|
1685
|
+
rules['.bw-breadcrumb-item.active'] = { 'font-weight': '500' };
|
|
1444
1686
|
|
|
1445
1687
|
// Hero (structural)
|
|
1446
1688
|
rules['.bw-hero'] = { 'position': 'relative', 'overflow': 'hidden' };
|
|
@@ -1543,12 +1785,12 @@
|
|
|
1543
1785
|
'position': 'relative', 'display': 'flex', 'align-items': 'center', 'width': '100%',
|
|
1544
1786
|
'padding': '1rem 1.25rem', 'font-size': '1rem', 'font-weight': '500', 'text-align': 'left',
|
|
1545
1787
|
'background-color': 'transparent', 'border': '0', 'overflow-anchor': 'none', 'cursor': 'pointer',
|
|
1546
|
-
'font-family': 'inherit', 'transition': 'color 0.15s ease-
|
|
1788
|
+
'font-family': 'inherit', 'transition': 'color 0.15s ease-out, background-color 0.15s ease-out'
|
|
1547
1789
|
};
|
|
1548
1790
|
rules['.bw-accordion-button::after'] = {
|
|
1549
1791
|
'flex-shrink': '0', 'width': '1.25rem', 'height': '1.25rem', 'margin-left': 'auto',
|
|
1550
1792
|
'content': '""', 'background-repeat': 'no-repeat', 'background-size': '1.25rem',
|
|
1551
|
-
'transition': 'transform 0.2s ease-
|
|
1793
|
+
'transition': 'transform 0.2s ease-out'
|
|
1552
1794
|
};
|
|
1553
1795
|
rules['.bw-accordion-button:not(.bw-collapsed)::after'] = { 'transform': 'rotate(-180deg)' };
|
|
1554
1796
|
rules['.bw-accordion-collapse'] = { 'max-height': '0', 'overflow': 'hidden', 'transition': 'max-height 0.3s ease' };
|
|
@@ -1557,10 +1799,13 @@
|
|
|
1557
1799
|
|
|
1558
1800
|
// Modal (structural)
|
|
1559
1801
|
rules['.bw-modal'] = {
|
|
1560
|
-
'display': '
|
|
1561
|
-
'
|
|
1802
|
+
'display': 'flex', 'align-items': 'center', 'justify-content': 'center',
|
|
1803
|
+
'position': 'fixed', 'top': '0', 'left': '0', 'width': '100%', 'height': '100%',
|
|
1804
|
+
'z-index': '1050', 'overflow-x': 'hidden', 'overflow-y': 'auto',
|
|
1805
|
+
'opacity': '0', 'visibility': 'hidden', 'pointer-events': 'none',
|
|
1806
|
+
'transition': 'opacity 0.2s ease, visibility 0.2s ease'
|
|
1562
1807
|
};
|
|
1563
|
-
rules['.bw-modal.bw-modal-show'] = { '
|
|
1808
|
+
rules['.bw-modal.bw-modal-show'] = { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' };
|
|
1564
1809
|
rules['.bw-modal-dialog'] = {
|
|
1565
1810
|
'position': 'relative', 'width': '100%', 'max-width': '500px', 'margin': '1.75rem auto',
|
|
1566
1811
|
'pointer-events': 'none', 'transform': 'translateY(-20px)', 'transition': 'transform 0.2s ease-out'
|
|
@@ -1580,7 +1825,7 @@
|
|
|
1580
1825
|
|
|
1581
1826
|
// Carousel (structural)
|
|
1582
1827
|
rules['.bw-carousel'] = { 'position': 'relative', 'overflow': 'hidden', 'border-radius': '8px' };
|
|
1583
|
-
rules['.bw-carousel-track'] = { 'display': 'flex', 'transition': 'transform 0.
|
|
1828
|
+
rules['.bw-carousel-track'] = { 'display': 'flex', 'transition': 'transform 0.3s ease-out', 'height': '100%' };
|
|
1584
1829
|
rules['.bw-carousel-slide'] = { 'min-width': '100%', 'flex-shrink': '0', 'overflow': 'hidden', 'position': 'relative', 'display': 'flex', 'align-items': 'center', 'justify-content': 'center' };
|
|
1585
1830
|
rules['.bw-carousel-slide img'] = { 'width': '100%', 'height': '100%', 'object-fit': 'cover' };
|
|
1586
1831
|
rules['.bw-carousel-caption'] = { 'position': 'absolute', 'bottom': '0', 'left': '0', 'right': '0', 'padding': '0.75rem 1rem' };
|
|
@@ -1624,11 +1869,14 @@
|
|
|
1624
1869
|
'border-bottom': '0', 'border-left': '0.3em solid transparent'
|
|
1625
1870
|
};
|
|
1626
1871
|
rules['.bw-dropdown-menu'] = {
|
|
1627
|
-
'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': '
|
|
1872
|
+
'position': 'absolute', 'top': '100%', 'left': '0', 'z-index': '1000', 'display': 'block',
|
|
1628
1873
|
'min-width': '10rem', 'padding': '0.5rem 0', 'margin': '0.125rem 0 0',
|
|
1629
|
-
'background-clip': 'padding-box', 'border-radius': '6px'
|
|
1874
|
+
'background-clip': 'padding-box', 'border-radius': '6px',
|
|
1875
|
+
'opacity': '0', 'visibility': 'hidden', 'transform': 'translateY(-4px)',
|
|
1876
|
+
'pointer-events': 'none',
|
|
1877
|
+
'transition': 'opacity 0.15s ease, transform 0.15s ease, visibility 0.15s ease'
|
|
1630
1878
|
};
|
|
1631
|
-
rules['.bw-dropdown-menu.bw-dropdown-show'] = { '
|
|
1879
|
+
rules['.bw-dropdown-menu.bw-dropdown-show'] = { 'opacity': '1', 'visibility': 'visible', 'transform': 'translateY(0)', 'pointer-events': 'auto' };
|
|
1632
1880
|
rules['.bw-dropdown-menu-end'] = { 'left': 'auto', 'right': '0' };
|
|
1633
1881
|
rules['.bw-dropdown-item'] = {
|
|
1634
1882
|
'display': 'block', 'width': '100%', 'padding': '0.375rem 1rem', 'clear': 'both',
|
|
@@ -1636,6 +1884,7 @@
|
|
|
1636
1884
|
'background-color': 'transparent', 'border': '0', 'font-size': '0.9375rem',
|
|
1637
1885
|
'transition': 'background-color 0.15s, color 0.15s'
|
|
1638
1886
|
};
|
|
1887
|
+
rules['.bw-dropdown-item:focus-visible'] = { 'outline': '2px solid currentColor', 'outline-offset': '-2px' };
|
|
1639
1888
|
rules['.bw-dropdown-divider'] = { 'height': '0', 'margin': '0.5rem 0', 'overflow': 'hidden', 'opacity': '1' };
|
|
1640
1889
|
|
|
1641
1890
|
// Switch (structural)
|
|
@@ -1643,7 +1892,7 @@
|
|
|
1643
1892
|
rules['.bw-form-switch .bw-switch-input'] = {
|
|
1644
1893
|
'width': '2em', 'height': '1.125em', 'margin-left': '-2.5em', 'border-radius': '2em',
|
|
1645
1894
|
'appearance': 'none', 'background-position': 'left center', 'background-repeat': 'no-repeat',
|
|
1646
|
-
'background-size': 'contain', 'transition': 'background-position 0.15s ease-
|
|
1895
|
+
'background-size': 'contain', 'transition': 'background-position 0.15s ease-out, background-color 0.15s ease-out',
|
|
1647
1896
|
'cursor': 'pointer'
|
|
1648
1897
|
};
|
|
1649
1898
|
rules['.bw-form-switch .bw-switch-input:checked'] = { 'background-position': 'right center' };
|
|
@@ -1668,6 +1917,123 @@
|
|
|
1668
1917
|
rules['.bw-avatar-lg'] = { 'width': '4rem', 'height': '4rem', 'font-size': '1.25rem' };
|
|
1669
1918
|
rules['.bw-avatar-xl'] = { 'width': '5rem', 'height': '5rem', 'font-size': '1.5rem' };
|
|
1670
1919
|
|
|
1920
|
+
// Stat card (structural)
|
|
1921
|
+
rules['.bw-stat-card'] = {
|
|
1922
|
+
'border-radius': '8px', 'padding': '1.25rem',
|
|
1923
|
+
'border-left': '4px solid transparent',
|
|
1924
|
+
'transition': 'box-shadow 0.15s ease-out, transform 0.15s ease-out'
|
|
1925
|
+
};
|
|
1926
|
+
rules['.bw-stat-card:hover'] = { 'transform': 'translateY(-1px)' };
|
|
1927
|
+
rules['.bw-stat-icon'] = { 'font-size': '1.5rem', 'margin-bottom': '0.5rem' };
|
|
1928
|
+
rules['.bw-stat-value'] = { 'font-size': '2rem', 'font-weight': '700', 'line-height': '1.2' };
|
|
1929
|
+
rules['.bw-stat-label'] = { 'font-size': '0.875rem', 'margin-top': '0.25rem' };
|
|
1930
|
+
rules['.bw-stat-change'] = { 'font-size': '0.875rem', 'font-weight': '500', 'margin-top': '0.5rem' };
|
|
1931
|
+
|
|
1932
|
+
// Tooltip (structural)
|
|
1933
|
+
rules['.bw-tooltip-wrapper'] = { 'position': 'relative', 'display': 'inline-block' };
|
|
1934
|
+
rules['.bw-tooltip'] = {
|
|
1935
|
+
'position': 'absolute', 'z-index': '999',
|
|
1936
|
+
'padding': '0.375rem 0.75rem', 'border-radius': '4px', 'font-size': '0.875rem',
|
|
1937
|
+
'white-space': 'nowrap', 'pointer-events': 'none',
|
|
1938
|
+
'opacity': '0', 'visibility': 'hidden',
|
|
1939
|
+
'transition': 'opacity 0.15s ease, visibility 0.15s ease, transform 0.15s ease'
|
|
1940
|
+
};
|
|
1941
|
+
rules['.bw-tooltip.bw-tooltip-show'] = { 'opacity': '1', 'visibility': 'visible' };
|
|
1942
|
+
rules['.bw-tooltip-top'] = { 'bottom': '100%', 'left': '50%', 'transform': 'translateX(-50%) translateY(-4px)', 'margin-bottom': '4px' };
|
|
1943
|
+
rules['.bw-tooltip-top.bw-tooltip-show'] = { 'transform': 'translateX(-50%) translateY(0)' };
|
|
1944
|
+
rules['.bw-tooltip-bottom'] = { 'top': '100%', 'left': '50%', 'transform': 'translateX(-50%) translateY(4px)', 'margin-top': '4px' };
|
|
1945
|
+
rules['.bw-tooltip-bottom.bw-tooltip-show'] = { 'transform': 'translateX(-50%) translateY(0)' };
|
|
1946
|
+
rules['.bw-tooltip-left'] = { 'right': '100%', 'top': '50%', 'transform': 'translateY(-50%) translateX(-4px)', 'margin-right': '4px' };
|
|
1947
|
+
rules['.bw-tooltip-left.bw-tooltip-show'] = { 'transform': 'translateY(-50%) translateX(0)' };
|
|
1948
|
+
rules['.bw-tooltip-right'] = { 'left': '100%', 'top': '50%', 'transform': 'translateY(-50%) translateX(4px)', 'margin-left': '4px' };
|
|
1949
|
+
rules['.bw-tooltip-right.bw-tooltip-show'] = { 'transform': 'translateY(-50%) translateX(0)' };
|
|
1950
|
+
|
|
1951
|
+
// Search input (structural)
|
|
1952
|
+
rules['.bw-search-input'] = { 'position': 'relative', 'display': 'flex', 'align-items': 'center' };
|
|
1953
|
+
rules['.bw-search-input .bw-search-field'] = { 'padding-right': '2.5rem' };
|
|
1954
|
+
rules['.bw-search-clear'] = {
|
|
1955
|
+
'position': 'absolute', 'right': '0.5rem',
|
|
1956
|
+
'display': 'flex', 'align-items': 'center', 'justify-content': 'center',
|
|
1957
|
+
'width': '1.5rem', 'height': '1.5rem',
|
|
1958
|
+
'border': 'none', 'background': 'none',
|
|
1959
|
+
'font-size': '1.25rem', 'cursor': 'pointer', 'padding': '0',
|
|
1960
|
+
'border-radius': '50%', 'transition': 'color 0.15s ease-out'
|
|
1961
|
+
};
|
|
1962
|
+
|
|
1963
|
+
// Range slider (structural)
|
|
1964
|
+
rules['.bw-range-wrapper'] = { 'margin-bottom': '1rem' };
|
|
1965
|
+
rules['.bw-range-label'] = { 'display': 'flex', 'justify-content': 'space-between', 'align-items': 'center', 'margin-bottom': '0.5rem', 'font-size': '0.875rem', 'font-weight': '500' };
|
|
1966
|
+
rules['.bw-range-value'] = { 'font-weight': '600' };
|
|
1967
|
+
rules['.bw-range'] = { 'width': '100%', 'height': '0.5rem', 'padding': '0', 'appearance': 'none', 'border': 'none', 'border-radius': '0.25rem', 'cursor': 'pointer', 'outline': 'none' };
|
|
1968
|
+
rules['.bw-range:disabled'] = { 'opacity': '0.5', 'cursor': 'not-allowed' };
|
|
1969
|
+
|
|
1970
|
+
// Media object (structural)
|
|
1971
|
+
rules['.bw-media'] = { 'display': 'flex', 'align-items': 'flex-start', 'gap': '1rem' };
|
|
1972
|
+
rules['.bw-media-reverse'] = { 'flex-direction': 'row-reverse' };
|
|
1973
|
+
rules['.bw-media-img'] = { 'border-radius': '50%', 'object-fit': 'cover', 'flex-shrink': '0' };
|
|
1974
|
+
rules['.bw-media-body'] = { 'flex': '1', 'min-width': '0' };
|
|
1975
|
+
rules['.bw-media-title'] = { 'margin': '0 0 0.25rem 0', 'font-size': '1rem', 'font-weight': '600', 'line-height': '1.3' };
|
|
1976
|
+
|
|
1977
|
+
// File upload (structural)
|
|
1978
|
+
rules['.bw-file-upload'] = {
|
|
1979
|
+
'display': 'flex', 'flex-direction': 'column', 'align-items': 'center', 'justify-content': 'center',
|
|
1980
|
+
'padding': '2rem', 'border': '2px dashed transparent', 'border-radius': '8px',
|
|
1981
|
+
'cursor': 'pointer', 'text-align': 'center', 'position': 'relative',
|
|
1982
|
+
'transition': 'border-color 0.15s ease-out, background-color 0.15s ease-out'
|
|
1983
|
+
};
|
|
1984
|
+
rules['.bw-file-upload-icon'] = { 'font-size': '2rem', 'margin-bottom': '0.5rem' };
|
|
1985
|
+
rules['.bw-file-upload-text'] = { 'font-size': '0.875rem' };
|
|
1986
|
+
rules['.bw-file-upload-input'] = {
|
|
1987
|
+
'position': 'absolute', 'width': '1px', 'height': '1px', 'padding': '0',
|
|
1988
|
+
'margin': '-1px', 'overflow': 'hidden', 'clip': 'rect(0,0,0,0)', 'border': '0'
|
|
1989
|
+
};
|
|
1990
|
+
|
|
1991
|
+
// Timeline (structural)
|
|
1992
|
+
rules['.bw-timeline'] = { 'position': 'relative', 'padding-left': '2rem' };
|
|
1993
|
+
rules['.bw-timeline-item'] = { 'position': 'relative', 'padding-bottom': '1.5rem' };
|
|
1994
|
+
rules['.bw-timeline-item:last-child'] = { 'padding-bottom': '0' };
|
|
1995
|
+
rules['.bw-timeline-marker'] = { 'position': 'absolute', 'left': '-1.75rem', 'top': '0.25rem', 'width': '0.75rem', 'height': '0.75rem', 'border-radius': '50%' };
|
|
1996
|
+
rules['.bw-timeline-content'] = { 'padding-left': '0.5rem' };
|
|
1997
|
+
rules['.bw-timeline-date'] = { 'font-size': '0.75rem', 'margin-bottom': '0.25rem', 'font-weight': '500' };
|
|
1998
|
+
rules['.bw-timeline-title'] = { 'font-size': '1rem', 'font-weight': '600', 'margin': '0 0 0.25rem 0', 'line-height': '1.3' };
|
|
1999
|
+
rules['.bw-timeline-text'] = { 'font-size': '0.875rem', 'margin': '0', 'line-height': '1.5' };
|
|
2000
|
+
|
|
2001
|
+
// Stepper (structural)
|
|
2002
|
+
rules['.bw-stepper'] = { 'display': 'flex', 'gap': '0' };
|
|
2003
|
+
rules['.bw-step'] = { 'flex': '1', 'display': 'flex', 'flex-direction': 'column', 'align-items': 'center', 'text-align': 'center', 'position': 'relative' };
|
|
2004
|
+
rules['.bw-step-indicator'] = { 'width': '2rem', 'height': '2rem', 'border-radius': '50%', 'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'font-size': '0.875rem', 'font-weight': '600', 'position': 'relative', 'z-index': '1', 'transition': 'background-color 0.2s ease-out, color 0.2s ease-out' };
|
|
2005
|
+
rules['.bw-step-body'] = { 'margin-top': '0.5rem' };
|
|
2006
|
+
rules['.bw-step-label'] = { 'font-size': '0.875rem', 'font-weight': '500' };
|
|
2007
|
+
rules['.bw-step-description'] = { 'font-size': '0.75rem', 'margin-top': '0.125rem' };
|
|
2008
|
+
|
|
2009
|
+
// Chip input (structural)
|
|
2010
|
+
rules['.bw-chip-input'] = { 'display': 'flex', 'flex-wrap': 'wrap', 'align-items': 'center', 'gap': '0.375rem', 'padding': '0.375rem 0.5rem', 'border-radius': '6px', 'min-height': '2.5rem', 'cursor': 'text', 'transition': 'border-color 0.15s ease-out, box-shadow 0.15s ease-out' };
|
|
2011
|
+
rules['.bw-chip'] = { 'display': 'inline-flex', 'align-items': 'center', 'gap': '0.25rem', 'padding': '0.125rem 0.5rem', 'border-radius': '1rem', 'font-size': '0.8125rem', 'line-height': '1.5', 'white-space': 'nowrap' };
|
|
2012
|
+
rules['.bw-chip-remove'] = { 'display': 'inline-flex', 'align-items': 'center', 'justify-content': 'center', 'width': '1rem', 'height': '1rem', 'border': 'none', 'background': 'none', 'font-size': '0.875rem', 'cursor': 'pointer', 'padding': '0', 'border-radius': '50%', 'transition': 'color 0.15s ease-out, background-color 0.15s ease-out' };
|
|
2013
|
+
rules['.bw-chip-field'] = { 'flex': '1', 'min-width': '80px', 'border': 'none', 'outline': 'none', 'font-size': '0.875rem', 'padding': '0.125rem 0', 'background': 'transparent' };
|
|
2014
|
+
|
|
2015
|
+
// Popover (structural)
|
|
2016
|
+
rules['.bw-popover-wrapper'] = { 'position': 'relative', 'display': 'inline-block' };
|
|
2017
|
+
rules['.bw-popover-trigger'] = { 'cursor': 'pointer' };
|
|
2018
|
+
rules['.bw-popover'] = {
|
|
2019
|
+
'position': 'absolute', 'z-index': '1000',
|
|
2020
|
+
'min-width': '200px', 'max-width': '320px',
|
|
2021
|
+
'border-radius': '8px',
|
|
2022
|
+
'pointer-events': 'none', 'opacity': '0', 'visibility': 'hidden',
|
|
2023
|
+
'transition': 'opacity 0.15s ease, visibility 0.15s ease, transform 0.15s ease'
|
|
2024
|
+
};
|
|
2025
|
+
rules['.bw-popover.bw-popover-show'] = { 'opacity': '1', 'visibility': 'visible', 'pointer-events': 'auto' };
|
|
2026
|
+
rules['.bw-popover-header'] = { 'padding': '0.625rem 0.875rem', 'font-weight': '600', 'font-size': '0.9375rem' };
|
|
2027
|
+
rules['.bw-popover-body'] = { 'padding': '0.75rem 0.875rem', 'font-size': '0.875rem', 'line-height': '1.5' };
|
|
2028
|
+
rules['.bw-popover-top'] = { 'bottom': '100%', 'left': '50%', 'transform': 'translateX(-50%) translateY(-8px)', 'margin-bottom': '8px' };
|
|
2029
|
+
rules['.bw-popover-top.bw-popover-show'] = { 'transform': 'translateX(-50%) translateY(0)' };
|
|
2030
|
+
rules['.bw-popover-bottom'] = { 'top': '100%', 'left': '50%', 'transform': 'translateX(-50%) translateY(8px)', 'margin-top': '8px' };
|
|
2031
|
+
rules['.bw-popover-bottom.bw-popover-show'] = { 'transform': 'translateX(-50%) translateY(0)' };
|
|
2032
|
+
rules['.bw-popover-left'] = { 'right': '100%', 'top': '50%', 'transform': 'translateY(-50%) translateX(-8px)', 'margin-right': '8px' };
|
|
2033
|
+
rules['.bw-popover-left.bw-popover-show'] = { 'transform': 'translateY(-50%) translateX(0)' };
|
|
2034
|
+
rules['.bw-popover-right'] = { 'left': '100%', 'top': '50%', 'transform': 'translateY(-50%) translateX(8px)', 'margin-left': '8px' };
|
|
2035
|
+
rules['.bw-popover-right.bw-popover-show'] = { 'transform': 'translateY(-50%) translateX(0)' };
|
|
2036
|
+
|
|
1671
2037
|
// Bar chart (structural)
|
|
1672
2038
|
rules['.bw-bar-chart-container'] = {
|
|
1673
2039
|
'padding': '1rem', 'border': '1px solid transparent', 'border-radius': '8px'
|
|
@@ -1681,7 +2047,7 @@
|
|
|
1681
2047
|
};
|
|
1682
2048
|
rules['.bw-bar'] = {
|
|
1683
2049
|
'width': '100%', 'border-radius': '3px 3px 0 0',
|
|
1684
|
-
'transition': 'height 0.
|
|
2050
|
+
'transition': 'height 0.3s ease-out', 'min-height': '4px'
|
|
1685
2051
|
};
|
|
1686
2052
|
rules['.bw-bar:hover'] = { 'opacity': '0.85' };
|
|
1687
2053
|
rules['.bw-bar-value'] = {
|
|
@@ -1810,6 +2176,16 @@
|
|
|
1810
2176
|
// Responsive grid
|
|
1811
2177
|
Object.assign(rules, defaultStyles.responsive);
|
|
1812
2178
|
|
|
2179
|
+
// Accessibility: reduce motion for users who prefer it
|
|
2180
|
+
rules['@media (prefers-reduced-motion: reduce)'] = {
|
|
2181
|
+
'*, *::before, *::after': {
|
|
2182
|
+
'animation-duration': '0.01ms !important',
|
|
2183
|
+
'animation-iteration-count': '1 !important',
|
|
2184
|
+
'transition-duration': '0.01ms !important',
|
|
2185
|
+
'scroll-behavior': 'auto !important'
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
|
|
1813
2189
|
return addUnderscoreAliases(rules);
|
|
1814
2190
|
}
|
|
1815
2191
|
|
|
@@ -1818,9 +2194,25 @@
|
|
|
1818
2194
|
// =========================================================================
|
|
1819
2195
|
|
|
1820
2196
|
/**
|
|
1821
|
-
* Add underscore aliases for all bw
|
|
2197
|
+
* Add underscore aliases for all `.bw-` selectors.
|
|
2198
|
+
*
|
|
2199
|
+
* CSS CLASS NAMING CONVENTION:
|
|
2200
|
+
*
|
|
2201
|
+
* Canonical form: `.bw-btn`, `.bw-card`, `.bw-table-hover` (hyphens)
|
|
2202
|
+
* Underscore alias: `.bw_btn`, `.bw_card`, `.bw_table_hover` (underscores)
|
|
2203
|
+
*
|
|
2204
|
+
* Both forms are valid in HTML and produce identical results. The hyphen
|
|
2205
|
+
* form is canonical (used in docs, generated CSS, component output).
|
|
2206
|
+
* Underscore aliases exist because:
|
|
2207
|
+
* 1. TACO attribute keys use underscores (`bw_id`, `bw_meta`) — no
|
|
2208
|
+
* quoting needed in JS object literals
|
|
2209
|
+
* 2. Some users prefer underscores for consistency with JS identifiers
|
|
2210
|
+
*
|
|
2211
|
+
* Use `bw.normalizeClass()` to convert underscore classes to canonical
|
|
2212
|
+
* hyphen form at runtime if needed.
|
|
2213
|
+
*
|
|
1822
2214
|
* @param {Object} rules - CSS rules object
|
|
1823
|
-
* @returns {Object}
|
|
2215
|
+
* @returns {Object} Rules with underscore aliases added (both forms work)
|
|
1824
2216
|
*/
|
|
1825
2217
|
function addUnderscoreAliases(rules) {
|
|
1826
2218
|
const result = {};
|
|
@@ -1837,6 +2229,27 @@
|
|
|
1837
2229
|
// =========================================================================
|
|
1838
2230
|
// Theme tokens (backwards compatible)
|
|
1839
2231
|
// =========================================================================
|
|
2232
|
+
//
|
|
2233
|
+
// DESIGN NOTE — Why no CSS custom properties (CSS variables)?
|
|
2234
|
+
//
|
|
2235
|
+
// Bitwrench targets IE11 as Tier 1 (see dev/bw2x-compatibility.md).
|
|
2236
|
+
// CSS custom properties (var(--color-primary)) are not supported in IE11.
|
|
2237
|
+
//
|
|
2238
|
+
// Instead, bitwrench uses class-scoped CSS generation:
|
|
2239
|
+
// 1. `defaultStyles.*` provides hardcoded cosmetic defaults
|
|
2240
|
+
// 2. `generateTheme(name, config)` generates a complete set of
|
|
2241
|
+
// class-scoped CSS rules from 3 seed colors (primary, secondary,
|
|
2242
|
+
// tertiary) — all components are restyled with the new palette
|
|
2243
|
+
// 3. `generateAlternateCSS()` produces the alternate (dark/light)
|
|
2244
|
+
// variant scoped under `.bw-theme-alt`
|
|
2245
|
+
//
|
|
2246
|
+
// This achieves full theme customization without CSS variables:
|
|
2247
|
+
// bw.generateTheme('ocean', { primary: '#006666', secondary: '#cc6633' })
|
|
2248
|
+
// → generates .ocean .bw-btn-primary { background: #006666; } etc.
|
|
2249
|
+
//
|
|
2250
|
+
// When IE11 support is dropped, CSS custom properties can be added as
|
|
2251
|
+
// an optimization (one rule with var() instead of many scoped rules).
|
|
2252
|
+
// The generateTheme() API stays the same — only the output format changes.
|
|
1840
2253
|
|
|
1841
2254
|
let theme = {
|
|
1842
2255
|
colors: {
|
|
@@ -1844,8 +2257,8 @@
|
|
|
1844
2257
|
secondary: '#6c757d',
|
|
1845
2258
|
success: '#198754',
|
|
1846
2259
|
danger: '#dc3545',
|
|
1847
|
-
warning: '#
|
|
1848
|
-
info: '#
|
|
2260
|
+
warning: '#b38600',
|
|
2261
|
+
info: '#0891b2',
|
|
1849
2262
|
light: '#f8f9fa',
|
|
1850
2263
|
dark: '#212529',
|
|
1851
2264
|
white: '#fff',
|
|
@@ -1881,214 +2294,63 @@
|
|
|
1881
2294
|
'5xl': '3rem'
|
|
1882
2295
|
}
|
|
1883
2296
|
},
|
|
1884
|
-
darkMode: false
|
|
1885
2297
|
};
|
|
1886
2298
|
|
|
1887
2299
|
/**
|
|
1888
|
-
* Generate
|
|
1889
|
-
*
|
|
2300
|
+
* Generate alternate-palette CSS scoped under `.bw-theme-alt`.
|
|
2301
|
+
* Uses the same `generateThemedCSS()` pipeline as the primary palette —
|
|
2302
|
+
* both sides go through identical code paths.
|
|
1890
2303
|
*
|
|
1891
|
-
* @param {
|
|
1892
|
-
* @
|
|
2304
|
+
* @param {string} name - Theme scope name (e.g. 'ocean'). '' for global.
|
|
2305
|
+
* @param {Object} altPalette - From derivePalette(deriveAlternateConfig(...))
|
|
2306
|
+
* @param {Object} layout - From resolveLayout()
|
|
2307
|
+
* @returns {Object} CSS rules object scoped under .bw-theme-alt (+ optional .name)
|
|
1893
2308
|
*/
|
|
1894
|
-
function
|
|
1895
|
-
|
|
1896
|
-
var
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
var
|
|
1900
|
-
var
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
'.bw-dark .bw-card': {
|
|
1913
|
-
'background-color': surfaceBg,
|
|
1914
|
-
'border-color': borderColor,
|
|
1915
|
-
'color': textColor
|
|
1916
|
-
},
|
|
1917
|
-
'.bw-dark .bw-card-header': {
|
|
1918
|
-
'background-color': bodyBg,
|
|
1919
|
-
'border-bottom-color': borderColor,
|
|
1920
|
-
'color': textColor
|
|
1921
|
-
},
|
|
1922
|
-
'.bw-dark .bw-card-footer': {
|
|
1923
|
-
'background-color': bodyBg,
|
|
1924
|
-
'border-top-color': borderColor,
|
|
1925
|
-
'color': textColor
|
|
1926
|
-
},
|
|
1927
|
-
'.bw-dark .bw-card-title': {
|
|
1928
|
-
'color': textColor
|
|
1929
|
-
},
|
|
1930
|
-
'.bw-dark .bw-navbar': {
|
|
1931
|
-
'background-color': surfaceBg,
|
|
1932
|
-
'border-bottom-color': borderColor
|
|
1933
|
-
},
|
|
1934
|
-
'.bw-dark .bw-navbar-brand': {
|
|
1935
|
-
'color': textColor
|
|
1936
|
-
},
|
|
1937
|
-
'.bw-dark .bw-navbar-nav .bw-nav-link': {
|
|
1938
|
-
'color': adjustLightness(textColor, -15)
|
|
1939
|
-
},
|
|
1940
|
-
'.bw-dark .bw-navbar-nav .bw-nav-link:hover': {
|
|
1941
|
-
'color': textColor
|
|
1942
|
-
},
|
|
1943
|
-
'.bw-dark .bw-form-control': {
|
|
1944
|
-
'background-color': surfaceBg,
|
|
1945
|
-
'border-color': borderColor,
|
|
1946
|
-
'color': textColor
|
|
1947
|
-
},
|
|
1948
|
-
'.bw-dark .bw-form-label': {
|
|
1949
|
-
'color': textColor
|
|
1950
|
-
},
|
|
1951
|
-
'.bw-dark .bw-form-text': {
|
|
1952
|
-
'color': adjustLightness(textColor, -20)
|
|
1953
|
-
},
|
|
1954
|
-
'.bw-dark .bw-table': {
|
|
1955
|
-
'color': textColor
|
|
1956
|
-
},
|
|
1957
|
-
'.bw-dark .bw-table > :not(caption) > * > *': {
|
|
1958
|
-
'border-bottom-color': borderColor
|
|
1959
|
-
},
|
|
1960
|
-
'.bw-dark .bw-table > thead > tr > *': {
|
|
1961
|
-
'background-color': bodyBg,
|
|
1962
|
-
'color': adjustLightness(textColor, -10),
|
|
1963
|
-
'border-bottom-color': borderColor
|
|
1964
|
-
},
|
|
1965
|
-
'.bw-dark .bw-table-striped > tbody > tr:nth-of-type(odd) > *': {
|
|
1966
|
-
'background-color': 'rgba(255, 255, 255, 0.05)'
|
|
1967
|
-
},
|
|
1968
|
-
'.bw-dark .bw-alert': {
|
|
1969
|
-
'border-color': borderColor
|
|
1970
|
-
},
|
|
1971
|
-
'.bw-dark .bw-list-group-item': {
|
|
1972
|
-
'background-color': surfaceBg,
|
|
1973
|
-
'border-color': borderColor,
|
|
1974
|
-
'color': textColor
|
|
1975
|
-
},
|
|
1976
|
-
'.bw-dark .bw-badge': {
|
|
1977
|
-
'color': textColor
|
|
1978
|
-
},
|
|
1979
|
-
'.bw-dark .bw-nav-tabs': {
|
|
1980
|
-
'border-bottom-color': borderColor
|
|
1981
|
-
},
|
|
1982
|
-
'.bw-dark .bw-nav-link': {
|
|
1983
|
-
'color': adjustLightness(textColor, -15)
|
|
1984
|
-
},
|
|
1985
|
-
'.bw-dark .bw-nav-tabs .bw-nav-link:hover': {
|
|
1986
|
-
'color': textColor,
|
|
1987
|
-
'border-bottom-color': borderColor
|
|
1988
|
-
},
|
|
1989
|
-
'.bw-dark .bw-pagination .bw-page-link': {
|
|
1990
|
-
'background-color': surfaceBg,
|
|
1991
|
-
'border-color': borderColor,
|
|
1992
|
-
'color': textColor
|
|
1993
|
-
},
|
|
1994
|
-
'.bw-dark .bw-breadcrumb-item + .bw-breadcrumb-item::before': {
|
|
1995
|
-
'color': adjustLightness(textColor, -20)
|
|
1996
|
-
},
|
|
1997
|
-
'.bw-dark .bw-breadcrumb-item.active': {
|
|
1998
|
-
'color': adjustLightness(textColor, -10)
|
|
1999
|
-
},
|
|
2000
|
-
'.bw-dark .bw-hero-light': {
|
|
2001
|
-
'background': surfaceBg,
|
|
2002
|
-
'color': textColor
|
|
2003
|
-
},
|
|
2004
|
-
'.bw-dark .bw-progress': {
|
|
2005
|
-
'background-color': surfaceBg
|
|
2006
|
-
},
|
|
2007
|
-
'.bw-dark .bw-section-subtitle': {
|
|
2008
|
-
'color': adjustLightness(textColor, -15)
|
|
2009
|
-
},
|
|
2010
|
-
'.bw-dark .bw-close': {
|
|
2011
|
-
'color': textColor
|
|
2012
|
-
},
|
|
2013
|
-
'.bw-dark .bw-accordion-item': {
|
|
2014
|
-
'background-color': surfaceBg,
|
|
2015
|
-
'border-color': borderColor
|
|
2016
|
-
},
|
|
2017
|
-
'.bw-dark .bw-accordion-button': {
|
|
2018
|
-
'color': textColor
|
|
2019
|
-
},
|
|
2020
|
-
'.bw-dark .bw-accordion-button:not(.bw-collapsed)': {
|
|
2021
|
-
'color': '#7dd3e0',
|
|
2022
|
-
'background-color': 'rgba(125, 211, 224, 0.1)'
|
|
2023
|
-
},
|
|
2024
|
-
'.bw-dark .bw-accordion-button:hover': {
|
|
2025
|
-
'background-color': bodyBg
|
|
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
|
-
},
|
|
2033
|
-
'.bw-dark .bw-accordion-body': {
|
|
2034
|
-
'border-top-color': borderColor
|
|
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
|
-
},
|
|
2045
|
-
'.bw-dark .bw-modal-content': {
|
|
2046
|
-
'background-color': surfaceBg,
|
|
2047
|
-
'border-color': borderColor
|
|
2048
|
-
},
|
|
2049
|
-
'.bw-dark .bw-modal-header': {
|
|
2050
|
-
'border-bottom-color': borderColor
|
|
2051
|
-
},
|
|
2052
|
-
'.bw-dark .bw-modal-footer': {
|
|
2053
|
-
'border-top-color': borderColor
|
|
2054
|
-
},
|
|
2055
|
-
'.bw-dark .bw-modal-title': {
|
|
2056
|
-
'color': textColor
|
|
2057
|
-
},
|
|
2058
|
-
'.bw-dark .bw-toast': {
|
|
2059
|
-
'background-color': surfaceBg,
|
|
2060
|
-
'border-color': borderColor
|
|
2061
|
-
},
|
|
2062
|
-
'.bw-dark .bw-toast-header': {
|
|
2063
|
-
'border-bottom-color': borderColor,
|
|
2064
|
-
'color': textColor
|
|
2065
|
-
},
|
|
2066
|
-
'.bw-dark .bw-dropdown-menu': {
|
|
2067
|
-
'background-color': surfaceBg,
|
|
2068
|
-
'border-color': borderColor
|
|
2069
|
-
},
|
|
2070
|
-
'.bw-dark .bw-dropdown-item': {
|
|
2071
|
-
'color': textColor
|
|
2072
|
-
},
|
|
2073
|
-
'.bw-dark .bw-dropdown-item:hover': {
|
|
2074
|
-
'background-color': bodyBg
|
|
2075
|
-
},
|
|
2076
|
-
'.bw-dark .bw-dropdown-divider': {
|
|
2077
|
-
'border-top-color': borderColor
|
|
2078
|
-
},
|
|
2079
|
-
'.bw-dark .bw-skeleton': {
|
|
2080
|
-
'background': 'linear-gradient(90deg, ' + borderColor + ' 25%, ' + surfaceBg + ' 37%, ' + borderColor + ' 63%)'
|
|
2081
|
-
},
|
|
2082
|
-
'.bw-dark h1, .bw-dark h2, .bw-dark h3, .bw-dark h4, .bw-dark h5, .bw-dark h6': {
|
|
2083
|
-
'color': textColor
|
|
2084
|
-
},
|
|
2085
|
-
'@media (prefers-color-scheme: dark)': {
|
|
2086
|
-
':root.bw-auto-dark body': {
|
|
2087
|
-
'color': textColor,
|
|
2088
|
-
'background-color': bodyBg
|
|
2309
|
+
function generateAlternateCSS(name, altPalette, layout) {
|
|
2310
|
+
// Generate themed CSS using the same pipeline as primary
|
|
2311
|
+
var rawRules = generateThemedCSS('', altPalette, layout);
|
|
2312
|
+
|
|
2313
|
+
// Re-scope every selector under .bw-theme-alt (+ optional theme name)
|
|
2314
|
+
var altPrefix = name ? '.' + name + '.bw-theme-alt' : '.bw-theme-alt';
|
|
2315
|
+
var altRules = {};
|
|
2316
|
+
|
|
2317
|
+
for (var sel in rawRules) {
|
|
2318
|
+
if (!rawRules.hasOwnProperty(sel)) continue;
|
|
2319
|
+
|
|
2320
|
+
if (sel.charAt(0) === '@') {
|
|
2321
|
+
// @media / @keyframes — recurse into the block
|
|
2322
|
+
var innerBlock = rawRules[sel];
|
|
2323
|
+
var altInner = {};
|
|
2324
|
+
for (var innerSel in innerBlock) {
|
|
2325
|
+
if (!innerBlock.hasOwnProperty(innerSel)) continue;
|
|
2326
|
+
altInner[altPrefix + ' ' + innerSel] = innerBlock[innerSel];
|
|
2089
2327
|
}
|
|
2328
|
+
altRules[sel] = altInner;
|
|
2329
|
+
} else {
|
|
2330
|
+
// Regular selector — prefix with alt scope
|
|
2331
|
+
// Handle comma-separated selectors
|
|
2332
|
+
var parts = sel.split(',');
|
|
2333
|
+
var scopedParts = [];
|
|
2334
|
+
for (var i = 0; i < parts.length; i++) {
|
|
2335
|
+
var s = parts[i].trim();
|
|
2336
|
+
// 'body' selector gets special treatment: .bw-theme-alt body
|
|
2337
|
+
if (s === 'body' || s.indexOf('body') === 0) {
|
|
2338
|
+
scopedParts.push(altPrefix + ' ' + s);
|
|
2339
|
+
} else {
|
|
2340
|
+
scopedParts.push(altPrefix + ' ' + s);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
altRules[scopedParts.join(', ')] = rawRules[sel];
|
|
2090
2344
|
}
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// Add body-level overrides for the alternate surface
|
|
2348
|
+
altRules[altPrefix + ' body, :root' + altPrefix + ' body'] = {
|
|
2349
|
+
'color': altPalette.dark.base,
|
|
2350
|
+
'background-color': altPalette.light.base
|
|
2091
2351
|
};
|
|
2352
|
+
|
|
2353
|
+
return altRules;
|
|
2092
2354
|
}
|
|
2093
2355
|
|
|
2094
2356
|
function deepMerge(target, source) {
|
|
@@ -3736,8 +3998,10 @@
|
|
|
3736
3998
|
/**
|
|
3737
3999
|
* Generate responsive CSS with media query breakpoints.
|
|
3738
4000
|
*
|
|
3739
|
-
* Produces a CSS string with `@media` rules for
|
|
3740
|
-
*
|
|
4001
|
+
* Produces a CSS string with `@media (min-width)` rules for standard
|
|
4002
|
+
* breakpoints. These match the grid system and theme.breakpoints:
|
|
4003
|
+
* sm: 576px, md: 768px, lg: 992px, xl: 1200px
|
|
4004
|
+
* Pass the result to `bw.injectCSS()`.
|
|
3741
4005
|
*
|
|
3742
4006
|
* @param {string} selector - CSS selector
|
|
3743
4007
|
* @param {Object} breakpoints - Object with keys: base, sm, md, lg, xl
|
|
@@ -3754,7 +4018,7 @@
|
|
|
3754
4018
|
* bw.injectCSS(css);
|
|
3755
4019
|
*/
|
|
3756
4020
|
bw.responsive = function(selector, breakpoints) {
|
|
3757
|
-
var sizes = { sm: '
|
|
4021
|
+
var sizes = { sm: '576px', md: '768px', lg: '992px', xl: '1200px' };
|
|
3758
4022
|
var parts = [];
|
|
3759
4023
|
Object.keys(breakpoints).forEach(function(key) {
|
|
3760
4024
|
var rules = {};
|
|
@@ -3888,7 +4152,8 @@
|
|
|
3888
4152
|
* @returns {Element|null} Style element if in browser, null in Node.js
|
|
3889
4153
|
* @category CSS & Styling
|
|
3890
4154
|
* @see bw.setTheme
|
|
3891
|
-
* @see bw.
|
|
4155
|
+
* @see bw.applyTheme
|
|
4156
|
+
* @see bw.toggleTheme
|
|
3892
4157
|
* @example
|
|
3893
4158
|
* bw.loadDefaultStyles(); // inject all default CSS
|
|
3894
4159
|
*/
|
|
@@ -3955,53 +4220,6 @@
|
|
|
3955
4220
|
return bw.getTheme();
|
|
3956
4221
|
};
|
|
3957
4222
|
|
|
3958
|
-
/**
|
|
3959
|
-
* Toggle dark mode on/off.
|
|
3960
|
-
*
|
|
3961
|
-
* Adds/removes the `bw-dark` class on `<html>` and injects dark mode CSS
|
|
3962
|
-
* overrides. Pass `true`/`false` to force a mode, or omit to toggle.
|
|
3963
|
-
*
|
|
3964
|
-
* @param {boolean} [force] - Force dark (true) or light (false). Omit to toggle.
|
|
3965
|
-
* @returns {boolean} Whether dark mode is now active
|
|
3966
|
-
* @category CSS & Styling
|
|
3967
|
-
* @see bw.setTheme
|
|
3968
|
-
* @example
|
|
3969
|
-
* bw.toggleDarkMode(); // toggle
|
|
3970
|
-
* bw.toggleDarkMode(true); // force dark
|
|
3971
|
-
* bw.toggleDarkMode(false); // force light
|
|
3972
|
-
*/
|
|
3973
|
-
bw.toggleDarkMode = function(force) {
|
|
3974
|
-
const isDark = force !== undefined ? force : !theme.darkMode;
|
|
3975
|
-
theme.darkMode = isDark;
|
|
3976
|
-
|
|
3977
|
-
if (bw._isBrowser) {
|
|
3978
|
-
const root = document.documentElement;
|
|
3979
|
-
if (isDark) {
|
|
3980
|
-
root.classList.add('bw-dark');
|
|
3981
|
-
// Generate palette-aware dark mode CSS, or fall back to default
|
|
3982
|
-
var palette = bw._activePalette || derivePalette(DEFAULT_PALETTE_CONFIG);
|
|
3983
|
-
var darkRules = generateDarkModeCSS(palette);
|
|
3984
|
-
var darkCSS = bw.css(darkRules);
|
|
3985
|
-
|
|
3986
|
-
// Remove existing dark styles to allow regeneration
|
|
3987
|
-
var existing = document.getElementById('bw-dark-styles');
|
|
3988
|
-
if (existing) existing.remove();
|
|
3989
|
-
|
|
3990
|
-
var styleEl = document.createElement('style');
|
|
3991
|
-
styleEl.id = 'bw-dark-styles';
|
|
3992
|
-
styleEl.textContent = darkCSS;
|
|
3993
|
-
document.head.appendChild(styleEl);
|
|
3994
|
-
} else {
|
|
3995
|
-
root.classList.remove('bw-dark');
|
|
3996
|
-
// Remove dark mode styles when switching to light
|
|
3997
|
-
var darkEl = document.getElementById('bw-dark-styles');
|
|
3998
|
-
if (darkEl) darkEl.remove();
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
|
|
4002
|
-
return isDark;
|
|
4003
|
-
};
|
|
4004
|
-
|
|
4005
4223
|
/**
|
|
4006
4224
|
* Generate a complete, scoped theme from seed colors.
|
|
4007
4225
|
*
|
|
@@ -4024,13 +4242,19 @@
|
|
|
4024
4242
|
* @param {string} [config.spacing='normal'] - 'compact' | 'normal' | 'spacious'
|
|
4025
4243
|
* @param {string} [config.radius='md'] - 'none' | 'sm' | 'md' | 'lg' | 'pill'
|
|
4026
4244
|
* @param {number} [config.fontSize=1.0] - Base font size scale factor
|
|
4245
|
+
* @param {string|number} [config.typeRatio='normal'] - 'tight' | 'normal' | 'relaxed' | 'dramatic' or a number
|
|
4246
|
+
* @param {string} [config.elevation='md'] - 'flat' | 'sm' | 'md' | 'lg'
|
|
4247
|
+
* @param {string} [config.motion='standard'] - 'reduced' | 'standard' | 'expressive'
|
|
4248
|
+
* @param {number} [config.harmonize=0.20] - 0-1, semantic color hue shift toward primary
|
|
4027
4249
|
* @param {boolean} [config.inject=true] - Inject into DOM (browser only)
|
|
4028
|
-
* @returns {Object} { css, palette, name }
|
|
4250
|
+
* @returns {Object} { css, palette, name, isLightPrimary, alternate: { css, palette } }
|
|
4029
4251
|
* @category CSS & Styling
|
|
4252
|
+
* @see bw.applyTheme
|
|
4253
|
+
* @see bw.toggleTheme
|
|
4030
4254
|
* @see bw.loadDefaultStyles
|
|
4031
4255
|
* @example
|
|
4032
|
-
* // Generate and inject an ocean theme
|
|
4033
|
-
* bw.generateTheme('ocean', {
|
|
4256
|
+
* // Generate and inject an ocean theme (primary + alternate)
|
|
4257
|
+
* var theme = bw.generateTheme('ocean', {
|
|
4034
4258
|
* primary: '#0077b6',
|
|
4035
4259
|
* secondary: '#90e0ef',
|
|
4036
4260
|
* tertiary: '#00b4d8'
|
|
@@ -4039,14 +4263,16 @@
|
|
|
4039
4263
|
* // Apply to a container
|
|
4040
4264
|
* document.getElementById('app').classList.add('ocean');
|
|
4041
4265
|
*
|
|
4266
|
+
* // Toggle to alternate palette
|
|
4267
|
+
* bw.toggleTheme();
|
|
4268
|
+
*
|
|
4042
4269
|
* // Generate CSS for static export (Node.js)
|
|
4043
4270
|
* var result = bw.generateTheme('sunset', {
|
|
4044
4271
|
* primary: '#e76f51',
|
|
4045
4272
|
* secondary: '#264653',
|
|
4046
|
-
* tertiary: '#e9c46a',
|
|
4047
4273
|
* inject: false
|
|
4048
4274
|
* });
|
|
4049
|
-
* fs.writeFileSync('sunset.css', result.css);
|
|
4275
|
+
* fs.writeFileSync('sunset.css', result.css + result.alternate.css);
|
|
4050
4276
|
*/
|
|
4051
4277
|
bw.generateTheme = function(name, config) {
|
|
4052
4278
|
if (!config || !config.primary || !config.secondary) {
|
|
@@ -4057,29 +4283,37 @@
|
|
|
4057
4283
|
var fullConfig = Object.assign({}, DEFAULT_PALETTE_CONFIG, config);
|
|
4058
4284
|
if (!config.tertiary) fullConfig.tertiary = fullConfig.primary;
|
|
4059
4285
|
|
|
4060
|
-
// Derive palette
|
|
4286
|
+
// Derive primary palette
|
|
4061
4287
|
var palette = derivePalette(fullConfig);
|
|
4062
4288
|
|
|
4063
|
-
// Store active palette for dark mode
|
|
4064
|
-
bw._activePalette = palette;
|
|
4065
|
-
|
|
4066
4289
|
// Resolve layout
|
|
4067
4290
|
var layout = resolveLayout(fullConfig);
|
|
4068
4291
|
|
|
4069
|
-
// Generate themed CSS rules
|
|
4292
|
+
// Generate primary themed CSS rules
|
|
4070
4293
|
var themedRules = generateThemedCSS(name, palette, layout);
|
|
4071
|
-
|
|
4072
|
-
// Add underscore aliases
|
|
4073
4294
|
var aliasedRules = addUnderscoreAliases(themedRules);
|
|
4074
|
-
|
|
4075
|
-
// Convert to CSS string
|
|
4076
4295
|
var cssStr = bw.css(aliasedRules);
|
|
4077
4296
|
|
|
4078
|
-
//
|
|
4297
|
+
// Derive alternate palette (luminance-inverted)
|
|
4298
|
+
var altConfig = deriveAlternateConfig(fullConfig);
|
|
4299
|
+
var altPalette = derivePalette(altConfig);
|
|
4300
|
+
|
|
4301
|
+
// Generate alternate CSS scoped under .bw-theme-alt
|
|
4302
|
+
var altRules = generateAlternateCSS(name, altPalette, layout);
|
|
4303
|
+
var aliasedAltRules = addUnderscoreAliases(altRules);
|
|
4304
|
+
var altCssStr = bw.css(aliasedAltRules);
|
|
4305
|
+
|
|
4306
|
+
// Determine if primary is light-flavored
|
|
4307
|
+
var lightPrimary = isLightPalette(fullConfig);
|
|
4308
|
+
|
|
4309
|
+
// Inject both CSS sets into DOM if requested
|
|
4079
4310
|
var shouldInject = config.inject !== false;
|
|
4080
4311
|
if (shouldInject && bw._isBrowser) {
|
|
4081
4312
|
var styleId = name ? 'bw-theme-' + name : 'bw-theme-default';
|
|
4082
4313
|
bw.injectCSS(cssStr, { id: styleId, append: false });
|
|
4314
|
+
|
|
4315
|
+
var altStyleId = name ? 'bw-theme-' + name + '-alt' : 'bw-theme-default-alt';
|
|
4316
|
+
bw.injectCSS(altCssStr, { id: altStyleId, append: false });
|
|
4083
4317
|
}
|
|
4084
4318
|
|
|
4085
4319
|
// Update bw.u color entries to reflect the palette
|
|
@@ -4090,7 +4324,72 @@
|
|
|
4090
4324
|
bw.u.textWhite = { color: '#ffffff' };
|
|
4091
4325
|
}
|
|
4092
4326
|
|
|
4093
|
-
|
|
4327
|
+
// Store active theme state
|
|
4328
|
+
var result = {
|
|
4329
|
+
css: cssStr,
|
|
4330
|
+
palette: palette,
|
|
4331
|
+
name: name,
|
|
4332
|
+
isLightPrimary: lightPrimary,
|
|
4333
|
+
alternate: {
|
|
4334
|
+
css: altCssStr,
|
|
4335
|
+
palette: altPalette
|
|
4336
|
+
}
|
|
4337
|
+
};
|
|
4338
|
+
bw._activeTheme = result;
|
|
4339
|
+
bw._activeThemeMode = 'primary';
|
|
4340
|
+
|
|
4341
|
+
return result;
|
|
4342
|
+
};
|
|
4343
|
+
|
|
4344
|
+
/**
|
|
4345
|
+
* Apply a theme mode. Switches between primary and alternate palettes
|
|
4346
|
+
* by adding/removing the `bw-theme-alt` class on `<html>`.
|
|
4347
|
+
*
|
|
4348
|
+
* @param {string} mode - 'primary' | 'alternate' | 'light' | 'dark'
|
|
4349
|
+
* @returns {string} Active mode: 'primary' or 'alternate'
|
|
4350
|
+
* @category CSS & Styling
|
|
4351
|
+
* @see bw.generateTheme
|
|
4352
|
+
* @see bw.toggleTheme
|
|
4353
|
+
* @example
|
|
4354
|
+
* bw.applyTheme('alternate'); // switch to alternate palette
|
|
4355
|
+
* bw.applyTheme('dark'); // switch to whichever palette is darker
|
|
4356
|
+
* bw.applyTheme('primary'); // switch back to primary palette
|
|
4357
|
+
*/
|
|
4358
|
+
bw.applyTheme = function(mode) {
|
|
4359
|
+
if (!bw._isBrowser) return mode || 'primary';
|
|
4360
|
+
var root = document.documentElement;
|
|
4361
|
+
var isLight = bw._activeTheme ? bw._activeTheme.isLightPrimary : true;
|
|
4362
|
+
|
|
4363
|
+
var wantAlt;
|
|
4364
|
+
if (mode === 'primary') wantAlt = false;
|
|
4365
|
+
else if (mode === 'alternate') wantAlt = true;
|
|
4366
|
+
else if (mode === 'light') wantAlt = !isLight;
|
|
4367
|
+
else if (mode === 'dark') wantAlt = isLight;
|
|
4368
|
+
else wantAlt = false;
|
|
4369
|
+
|
|
4370
|
+
if (wantAlt) {
|
|
4371
|
+
root.classList.add('bw-theme-alt');
|
|
4372
|
+
} else {
|
|
4373
|
+
root.classList.remove('bw-theme-alt');
|
|
4374
|
+
}
|
|
4375
|
+
|
|
4376
|
+
bw._activeThemeMode = wantAlt ? 'alternate' : 'primary';
|
|
4377
|
+
return bw._activeThemeMode;
|
|
4378
|
+
};
|
|
4379
|
+
|
|
4380
|
+
/**
|
|
4381
|
+
* Toggle between primary and alternate theme palettes.
|
|
4382
|
+
*
|
|
4383
|
+
* @returns {string} Active mode after toggle: 'primary' or 'alternate'
|
|
4384
|
+
* @category CSS & Styling
|
|
4385
|
+
* @see bw.applyTheme
|
|
4386
|
+
* @see bw.generateTheme
|
|
4387
|
+
* @example
|
|
4388
|
+
* bw.toggleTheme(); // flip between primary and alternate
|
|
4389
|
+
*/
|
|
4390
|
+
bw.toggleTheme = function() {
|
|
4391
|
+
var current = bw._activeThemeMode || 'primary';
|
|
4392
|
+
return bw.applyTheme(current === 'primary' ? 'alternate' : 'primary');
|
|
4094
4393
|
};
|
|
4095
4394
|
|
|
4096
4395
|
// Expose color utility functions on bw namespace
|
|
@@ -4102,10 +4401,18 @@
|
|
|
4102
4401
|
bw.textOnColor = textOnColor;
|
|
4103
4402
|
bw.deriveShades = deriveShades;
|
|
4104
4403
|
bw.derivePalette = derivePalette;
|
|
4404
|
+
bw.harmonize = harmonize;
|
|
4405
|
+
bw.deriveAlternateSeed = deriveAlternateSeed;
|
|
4406
|
+
bw.deriveAlternateConfig = deriveAlternateConfig;
|
|
4407
|
+
bw.isLightPalette = isLightPalette;
|
|
4105
4408
|
|
|
4106
4409
|
// Expose layout and theme presets
|
|
4107
4410
|
bw.SPACING_PRESETS = SPACING_PRESETS;
|
|
4108
4411
|
bw.RADIUS_PRESETS = RADIUS_PRESETS;
|
|
4412
|
+
bw.TYPE_RATIO_PRESETS = TYPE_RATIO_PRESETS;
|
|
4413
|
+
bw.ELEVATION_PRESETS = ELEVATION_PRESETS;
|
|
4414
|
+
bw.MOTION_PRESETS = MOTION_PRESETS;
|
|
4415
|
+
bw.generateTypeScale = generateTypeScale;
|
|
4109
4416
|
bw.DEFAULT_PALETTE_CONFIG = DEFAULT_PALETTE_CONFIG;
|
|
4110
4417
|
bw.THEME_PRESETS = THEME_PRESETS;
|
|
4111
4418
|
|
|
@@ -5147,9 +5454,13 @@
|
|
|
5147
5454
|
/**
|
|
5148
5455
|
* Create a sortable TACO table from an array of row objects.
|
|
5149
5456
|
*
|
|
5457
|
+
* Returns a bare `<table>` TACO — no wrapper, title, or responsive scroll.
|
|
5458
|
+
* Use this when you need full control over table placement, or when embedding
|
|
5459
|
+
* the table inside your own layout. For a ready-to-use table with title,
|
|
5460
|
+
* responsive wrapper, and defaults (striped + hover), use `bw.makeDataTable()`.
|
|
5461
|
+
*
|
|
5150
5462
|
* Auto-detects columns from data keys if not specified. Supports click-to-sort
|
|
5151
|
-
* headers with ascending/descending indicators.
|
|
5152
|
-
* render with `bw.DOM()` or `bw.html()`.
|
|
5463
|
+
* headers with ascending/descending indicators.
|
|
5153
5464
|
*
|
|
5154
5465
|
* @param {Object} config - Table configuration
|
|
5155
5466
|
* @param {Array<Object>} config.data - Array of row objects to display
|
|
@@ -5449,10 +5760,12 @@
|
|
|
5449
5760
|
};
|
|
5450
5761
|
|
|
5451
5762
|
/**
|
|
5452
|
-
* Create a
|
|
5763
|
+
* Create a ready-to-use data table with title and responsive wrapper.
|
|
5453
5764
|
*
|
|
5454
|
-
*
|
|
5455
|
-
*
|
|
5765
|
+
* Convenience wrapper around `bw.makeTable()` that adds a title heading,
|
|
5766
|
+
* responsive horizontal scroll container, and defaults to striped + hover.
|
|
5767
|
+
* Use this for the common case; use `bw.makeTable()` when you need a bare
|
|
5768
|
+
* table element with no wrapper.
|
|
5456
5769
|
*
|
|
5457
5770
|
* @param {Object} config - Table configuration
|
|
5458
5771
|
* @param {string} [config.title] - Table title heading
|