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