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