@pie-players/pie-theme 0.3.65 → 0.3.66
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 +165 -18
- package/dist/color-schemes.css +386 -441
- package/dist/color-schemes.d.ts +12 -21
- package/dist/color-schemes.js +507 -385
- package/dist/components.css +111 -64
- package/dist/contrast.d.ts +67 -0
- package/dist/contrast.js +151 -0
- package/dist/daisyui-mapping.d.ts +58 -0
- package/dist/daisyui-mapping.js +168 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -3
- package/dist/providers.d.ts +16 -0
- package/dist/providers.js +68 -91
- package/dist/scheme-participation.d.ts +88 -0
- package/dist/scheme-participation.js +89 -0
- package/dist/theme-css.d.ts +5 -0
- package/dist/theme-css.js +26 -0
- package/dist/theme-definitions.d.ts +21 -0
- package/dist/theme-definitions.js +1081 -0
- package/dist/theme-element.d.ts +5 -0
- package/dist/theme-element.js +154 -36
- package/dist/theme-types.d.ts +54 -0
- package/dist/token-registry-types.d.ts +57 -0
- package/dist/token-registry-types.js +15 -0
- package/dist/token-registry.json +1311 -0
- package/dist/tokens.css +84 -76
- package/package.json +7 -3
- package/dist/theme-defaults.d.ts +0 -3
- package/dist/theme-defaults.js +0 -100
package/dist/components.css
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
|
+
/* Content classes for authored assessment content, installed as a
|
|
2
|
+
document-level stylesheet by every player (see players-shared
|
|
3
|
+
ui/content-styles.ts).
|
|
4
|
+
|
|
5
|
+
Colour values resolve through the canonical --pie-* tokens with the original
|
|
6
|
+
literal kept as the no-theme last resort. The literals came from content
|
|
7
|
+
authored against a white page: `black` borders, `lightgray` table headers and
|
|
8
|
+
a white loading scrim are invisible or illegible the moment the surface is
|
|
9
|
+
not white, and a host that only swaps the theme cannot reach them. Ink takes
|
|
10
|
+
--pie-text (DaisyUI base-content, the one family guaranteed against the
|
|
11
|
+
surface), page-coloured fills take --pie-white, and one-step-off-the-page
|
|
12
|
+
fills take --pie-background-dark. */
|
|
1
13
|
.pie-loading {
|
|
14
|
+
/* The Figma indigo is --pie-primary at 90%, so the ring follows the theme's
|
|
15
|
+
primary instead of pinning MD indigo on every page. */
|
|
16
|
+
--pie-loading-accent: color-mix(
|
|
17
|
+
in srgb,
|
|
18
|
+
var(--pie-primary, #3f51b5) 90%,
|
|
19
|
+
transparent
|
|
20
|
+
);
|
|
2
21
|
position: absolute;
|
|
3
22
|
height: 100%;
|
|
4
23
|
width: 100%;
|
|
5
|
-
background-color: #fff;
|
|
24
|
+
background-color: var(--pie-white, #fff);
|
|
6
25
|
bottom: 0;
|
|
7
26
|
left: 0;
|
|
8
27
|
right: 0;
|
|
@@ -18,12 +37,12 @@
|
|
|
18
37
|
.pie-loading:before {
|
|
19
38
|
content: "";
|
|
20
39
|
background-color: rgba(0, 0, 0, 0);
|
|
21
|
-
border: 5px solid rgba(63, 81, 181, 0.9);
|
|
40
|
+
border: 5px solid var(--pie-loading-accent, rgba(63, 81, 181, 0.9));
|
|
22
41
|
opacity: 0.9;
|
|
23
42
|
border-right: 5px solid rgba(0, 0, 0, 0);
|
|
24
43
|
border-left: 5px solid rgba(0, 0, 0, 0);
|
|
25
44
|
border-radius: 50px;
|
|
26
|
-
box-shadow: 0 0 35px rgba(63, 81, 181, 0.9);
|
|
45
|
+
box-shadow: 0 0 35px var(--pie-loading-accent, rgba(63, 81, 181, 0.9));
|
|
27
46
|
width: 50px;
|
|
28
47
|
height: 50px;
|
|
29
48
|
-moz-animation: spinPulse 1s infinite ease-in-out;
|
|
@@ -37,12 +56,12 @@
|
|
|
37
56
|
.pie-loading:after {
|
|
38
57
|
content: "";
|
|
39
58
|
background-color: rgba(0, 0, 0, 0);
|
|
40
|
-
border: 5px solid rgba(63, 81, 181, 0.9);
|
|
59
|
+
border: 5px solid var(--pie-loading-accent, rgba(63, 81, 181, 0.9));
|
|
41
60
|
opacity: 0.9;
|
|
42
61
|
border-left: 5px solid rgba(0, 0, 0, 0);
|
|
43
62
|
border-right: 5px solid rgba(0, 0, 0, 0);
|
|
44
63
|
border-radius: 50px;
|
|
45
|
-
box-shadow: 0 0 15px rgba(63, 81, 181, 0.9);
|
|
64
|
+
box-shadow: 0 0 15px var(--pie-loading-accent, rgba(63, 81, 181, 0.9));
|
|
46
65
|
width: 30px;
|
|
47
66
|
height: 30px;
|
|
48
67
|
-moz-animation: spinoffPulse 1s infinite linear;
|
|
@@ -57,7 +76,7 @@
|
|
|
57
76
|
0% {
|
|
58
77
|
transform: rotate(160deg);
|
|
59
78
|
opacity: 0;
|
|
60
|
-
box-shadow: 0 0 1px rgba(63, 81, 181, 0.9);
|
|
79
|
+
box-shadow: 0 0 1px var(--pie-loading-accent, rgba(63, 81, 181, 0.9));
|
|
61
80
|
}
|
|
62
81
|
50% {
|
|
63
82
|
transform: rotate(145deg);
|
|
@@ -159,7 +178,7 @@
|
|
|
159
178
|
margin-top: 5px;
|
|
160
179
|
margin-bottom: 5px;
|
|
161
180
|
width: 60%;
|
|
162
|
-
border: 1px solid black;
|
|
181
|
+
border: 1px solid var(--pie-text, black);
|
|
163
182
|
padding: 2px;
|
|
164
183
|
display: block;
|
|
165
184
|
}
|
|
@@ -171,7 +190,7 @@
|
|
|
171
190
|
margin-top: 5px;
|
|
172
191
|
margin-bottom: 5px;
|
|
173
192
|
width: 60%;
|
|
174
|
-
border: 1px solid black;
|
|
193
|
+
border: 1px solid var(--pie-text, black);
|
|
175
194
|
padding: 2px;
|
|
176
195
|
display: block;
|
|
177
196
|
}
|
|
@@ -196,9 +215,13 @@ span.short-quote:after {
|
|
|
196
215
|
text-transform: uppercase;
|
|
197
216
|
}
|
|
198
217
|
|
|
218
|
+
/* Authored red emphasis. --pie-content-emphasis is mapped from the DaisyUI error
|
|
219
|
+
slot through the legible correction, so it clears 4.5:1 against the page on
|
|
220
|
+
every shipped theme; the `red` literal it replaces measures 4:1 on white and
|
|
221
|
+
fails SC 1.4.3 before any theme is involved. */
|
|
199
222
|
.content-emphasis {
|
|
200
223
|
font-weight: bold;
|
|
201
|
-
color:
|
|
224
|
+
color: var(--pie-content-emphasis, #b00000);
|
|
202
225
|
}
|
|
203
226
|
|
|
204
227
|
span.passage-title:before {
|
|
@@ -232,6 +255,21 @@ span.passage-title:after {
|
|
|
232
255
|
font-style: italic;
|
|
233
256
|
}
|
|
234
257
|
|
|
258
|
+
/*
|
|
259
|
+
* The 20% gutters above are a wide-viewport look. Held at a 320px-equivalent
|
|
260
|
+
* width they leave a ~190px column of very short lines, so they collapse there
|
|
261
|
+
* instead — SC 1.4.10 Reflow.
|
|
262
|
+
*/
|
|
263
|
+
@media (max-width: 30rem) {
|
|
264
|
+
.block-quote,
|
|
265
|
+
.text-block,
|
|
266
|
+
.equation-block {
|
|
267
|
+
margin-left: 0;
|
|
268
|
+
margin-right: 0;
|
|
269
|
+
width: auto;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
235
273
|
/*
|
|
236
274
|
* Presence sentinel. Players install this stylesheet themselves and read this
|
|
237
275
|
* property to detect whether a host that opted out of installation shipped a
|
|
@@ -276,23 +314,23 @@ div.passage-author {
|
|
|
276
314
|
}
|
|
277
315
|
|
|
278
316
|
.kds-absolute {
|
|
279
|
-
border-left: 1px solid black;
|
|
280
|
-
border-right: 1px solid black;
|
|
317
|
+
border-left: 1px solid var(--pie-text, black);
|
|
318
|
+
border-right: 1px solid var(--pie-text, black);
|
|
281
319
|
padding: 0px 0.3em 0px 0.3em;
|
|
282
320
|
}
|
|
283
321
|
|
|
284
322
|
.abs {
|
|
285
|
-
border-left: #000000 1px solid;
|
|
286
|
-
border-right: #000000 1px solid;
|
|
323
|
+
border-left: var(--pie-text, #000000) 1px solid;
|
|
324
|
+
border-right: var(--pie-text, #000000) 1px solid;
|
|
287
325
|
padding: 0px 0.3em 0px 0.3em;
|
|
288
326
|
}
|
|
289
327
|
|
|
290
328
|
.kds-border-1 {
|
|
291
|
-
border: 1px solid black;
|
|
329
|
+
border: 1px solid var(--pie-text, black);
|
|
292
330
|
}
|
|
293
331
|
|
|
294
332
|
.kds-border-2 {
|
|
295
|
-
border: 2px solid black;
|
|
333
|
+
border: 2px solid var(--pie-text, black);
|
|
296
334
|
}
|
|
297
335
|
|
|
298
336
|
.kds-center {
|
|
@@ -320,7 +358,7 @@ sup.frac {
|
|
|
320
358
|
}
|
|
321
359
|
|
|
322
360
|
.kds-fillin {
|
|
323
|
-
border: 1px solid black;
|
|
361
|
+
border: 1px solid var(--pie-text, black);
|
|
324
362
|
display: inline-block;
|
|
325
363
|
margin: 0em 0.2em;
|
|
326
364
|
padding-left: 1em;
|
|
@@ -329,7 +367,7 @@ sup.frac {
|
|
|
329
367
|
.fillin {
|
|
330
368
|
margin: 0em 0.2em;
|
|
331
369
|
padding-left: 1em;
|
|
332
|
-
border: #000000 1px solid;
|
|
370
|
+
border: var(--pie-text, #000000) 1px solid;
|
|
333
371
|
display: inline-block;
|
|
334
372
|
}
|
|
335
373
|
|
|
@@ -351,7 +389,7 @@ table.kds-fraction {
|
|
|
351
389
|
}
|
|
352
390
|
|
|
353
391
|
table.kds-fraction > tbody > tr > td.kds-numerator {
|
|
354
|
-
border-bottom: 1px solid black;
|
|
392
|
+
border-bottom: 1px solid var(--pie-text, black);
|
|
355
393
|
border-left: 0px;
|
|
356
394
|
border-right: 0px;
|
|
357
395
|
border-top: 0px;
|
|
@@ -376,7 +414,7 @@ table.frac {
|
|
|
376
414
|
border-left: 0px;
|
|
377
415
|
border-top: 0px;
|
|
378
416
|
border-right: 0px;
|
|
379
|
-
border-bottom: #000000 1px solid;
|
|
417
|
+
border-bottom: var(--pie-text, #000000) 1px solid;
|
|
380
418
|
text-align: center;
|
|
381
419
|
}
|
|
382
420
|
|
|
@@ -457,7 +495,7 @@ b.frac {
|
|
|
457
495
|
}
|
|
458
496
|
|
|
459
497
|
.newradical td.vinculum {
|
|
460
|
-
border-top: 1px solid black;
|
|
498
|
+
border-top: 1px solid var(--pie-text, black);
|
|
461
499
|
padding: 0px;
|
|
462
500
|
vertical-align: top;
|
|
463
501
|
}
|
|
@@ -478,7 +516,7 @@ b.frac {
|
|
|
478
516
|
}
|
|
479
517
|
|
|
480
518
|
.kds-verdana2t {
|
|
481
|
-
border: 1px solid white;
|
|
519
|
+
border: 1px solid var(--pie-white, white);
|
|
482
520
|
text-align: left;
|
|
483
521
|
text-decoration: none;
|
|
484
522
|
vertical-align: top;
|
|
@@ -488,7 +526,7 @@ b.frac {
|
|
|
488
526
|
vertical-align: top;
|
|
489
527
|
text-align: left;
|
|
490
528
|
text-decoration: none;
|
|
491
|
-
border: 1px solid white;
|
|
529
|
+
border: 1px solid var(--pie-white, white);
|
|
492
530
|
}
|
|
493
531
|
|
|
494
532
|
.kds-whole-number {
|
|
@@ -504,58 +542,65 @@ b.frac {
|
|
|
504
542
|
}
|
|
505
543
|
|
|
506
544
|
table.KdsTable01 {
|
|
507
|
-
border: 1px solid black;
|
|
545
|
+
border: 1px solid var(--pie-text, black);
|
|
508
546
|
border-collapse: collapse;
|
|
509
547
|
border-spacing: 0px;
|
|
510
548
|
}
|
|
511
549
|
|
|
512
550
|
table.KdsTable01 > tbody > tr > th {
|
|
513
|
-
background-color: #d3d3d3;
|
|
514
|
-
border: 1px solid black;
|
|
551
|
+
background-color: var(--pie-background-dark, #d3d3d3);
|
|
552
|
+
border: 1px solid var(--pie-text, black);
|
|
515
553
|
font-weight: normal;
|
|
516
554
|
padding: 2px;
|
|
517
555
|
text-align: center;
|
|
518
556
|
}
|
|
519
557
|
|
|
520
558
|
table.KdsTable01 > tbody > tr > th.bold {
|
|
521
|
-
background-color: #d3d3d3;
|
|
522
|
-
border: 1px solid black;
|
|
559
|
+
background-color: var(--pie-background-dark, #d3d3d3);
|
|
560
|
+
border: 1px solid var(--pie-text, black);
|
|
523
561
|
font-weight: bold;
|
|
524
562
|
padding: 2px;
|
|
525
563
|
text-align: center;
|
|
526
564
|
}
|
|
527
565
|
|
|
528
566
|
table.KdsTable01 > tbody > tr > td {
|
|
529
|
-
border: 1px solid black;
|
|
567
|
+
border: 1px solid var(--pie-text, black);
|
|
530
568
|
padding: 2px;
|
|
531
569
|
text-align: center;
|
|
532
570
|
}
|
|
533
571
|
|
|
534
572
|
table.KdsTable01 > tbody > tr > td.bold {
|
|
535
|
-
border: 1px solid black;
|
|
573
|
+
border: 1px solid var(--pie-text, black);
|
|
536
574
|
font-weight: bold;
|
|
537
575
|
padding: 2px;
|
|
538
576
|
text-align: center;
|
|
539
577
|
}
|
|
540
578
|
|
|
541
579
|
table.KdsTable02 {
|
|
542
|
-
border: 1px solid black;
|
|
580
|
+
border: 1px solid var(--pie-text, black);
|
|
543
581
|
border-collapse: collapse;
|
|
544
582
|
border-spacing: 0px;
|
|
545
583
|
}
|
|
546
584
|
|
|
547
585
|
table.KdsTable02 > tbody > tr > th {
|
|
548
|
-
background-color: #d3d3d3;
|
|
549
|
-
border: 1px solid black;
|
|
586
|
+
background-color: var(--pie-background-dark, #d3d3d3);
|
|
587
|
+
border: 1px solid var(--pie-text, black);
|
|
550
588
|
font-weight: normal;
|
|
551
589
|
padding: 2px;
|
|
552
590
|
}
|
|
553
591
|
|
|
554
592
|
table.KdsTable02 > tbody > tr > td {
|
|
555
|
-
border: 1px solid black;
|
|
593
|
+
border: 1px solid var(--pie-text, black);
|
|
556
594
|
padding: 2px;
|
|
557
595
|
}
|
|
558
596
|
|
|
597
|
+
/* The grid rules below are mixed from the ink instead of taken from
|
|
598
|
+
--pie-border-light, which the DaisyUI mapping fills from base-200 — a surface,
|
|
599
|
+
so a border set from it disappears into the page. The 15% mix keeps the
|
|
600
|
+
near-invisible weight #dee2e6 had on white and inverts to a faint light rule
|
|
601
|
+
on a dark surface. Mixed inline, not hoisted to a custom property: a custom
|
|
602
|
+
property substitutes var(--pie-text) where it is declared, so a scoped
|
|
603
|
+
<pie-theme scope="self"> below :root would not reach it. */
|
|
559
604
|
.table {
|
|
560
605
|
width: 100%;
|
|
561
606
|
margin-bottom: 1em;
|
|
@@ -567,16 +612,19 @@ table.KdsTable02 > tbody > tr > td {
|
|
|
567
612
|
.table td {
|
|
568
613
|
padding: 0.75em;
|
|
569
614
|
vertical-align: top;
|
|
570
|
-
border-top: 1px solid
|
|
615
|
+
border-top: 1px solid
|
|
616
|
+
color-mix(in srgb, var(--pie-text, #000) 15%, transparent);
|
|
571
617
|
}
|
|
572
618
|
|
|
573
619
|
.table tbody + tbody {
|
|
574
|
-
border-top: 2px solid
|
|
620
|
+
border-top: 2px solid
|
|
621
|
+
color-mix(in srgb, var(--pie-text, #000) 15%, transparent);
|
|
575
622
|
}
|
|
576
623
|
|
|
577
624
|
.table thead th {
|
|
578
625
|
vertical-align: bottom;
|
|
579
|
-
border-bottom: 2px solid
|
|
626
|
+
border-bottom: 2px solid
|
|
627
|
+
color-mix(in srgb, var(--pie-text, #000) 15%, transparent);
|
|
580
628
|
}
|
|
581
629
|
|
|
582
630
|
th {
|
|
@@ -586,7 +634,7 @@ th {
|
|
|
586
634
|
.table-bordered,
|
|
587
635
|
.table-bordered th,
|
|
588
636
|
.table-bordered td {
|
|
589
|
-
border: 1px solid #
|
|
637
|
+
border: 1px solid color-mix(in srgb, var(--pie-text, #000) 15%, transparent);
|
|
590
638
|
}
|
|
591
639
|
|
|
592
640
|
.table-bordered thead td,
|
|
@@ -595,7 +643,7 @@ th {
|
|
|
595
643
|
}
|
|
596
644
|
|
|
597
645
|
.table-striped tbody tr:nth-of-type(odd) {
|
|
598
|
-
background-color:
|
|
646
|
+
background-color: color-mix(in srgb, var(--pie-text, #000) 5%, transparent);
|
|
599
647
|
}
|
|
600
648
|
|
|
601
649
|
.text-center {
|
|
@@ -616,6 +664,9 @@ h6 {
|
|
|
616
664
|
margin-top: 0;
|
|
617
665
|
}
|
|
618
666
|
|
|
667
|
+
/* Spacing and inheritance only: `font-weight: 500` used to sit here and flatten
|
|
668
|
+
every authored heading to lighter-than-bold, weakening the visual hierarchy
|
|
669
|
+
a screen-reader user gets from the heading level. */
|
|
619
670
|
.h1,
|
|
620
671
|
.h2,
|
|
621
672
|
.h3,
|
|
@@ -630,31 +681,19 @@ h5,
|
|
|
630
681
|
h6 {
|
|
631
682
|
margin-bottom: 0.5em;
|
|
632
683
|
font-family: inherit;
|
|
633
|
-
font-weight: 500;
|
|
634
684
|
line-height: 1.2;
|
|
635
685
|
color: inherit;
|
|
636
686
|
}
|
|
637
687
|
|
|
638
|
-
/*
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
float: left;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
#item {
|
|
651
|
-
width: 50%;
|
|
652
|
-
float: left;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
.lrn_width_auto.table {
|
|
656
|
-
width: auto;
|
|
657
|
-
}
|
|
688
|
+
/*
|
|
689
|
+
* Removed: a `#stimulus` / `#item` pair of 50% left floats, a `.lrn_feature h3`
|
|
690
|
+
* margin override and `.lrn_width_auto.table`. The float pair hard-coded a
|
|
691
|
+
* two-column layout keyed to two global ids — it cannot reflow at 400% zoom
|
|
692
|
+
* (SC 1.4.10), and it applied to any element in the host document carrying
|
|
693
|
+
* those ids. The other two styled a third-party product's markup that PIE does
|
|
694
|
+
* not render. None of the three could be made correct here, so they are gone
|
|
695
|
+
* rather than patched.
|
|
696
|
+
*/
|
|
658
697
|
|
|
659
698
|
.TEX-S3 {
|
|
660
699
|
font-family: MJXZERO, MJXTEX-S3 !important;
|
|
@@ -688,25 +727,33 @@ table {
|
|
|
688
727
|
.evaluate-bottom-border > :first-child::after {
|
|
689
728
|
content: "";
|
|
690
729
|
display: block;
|
|
691
|
-
border-bottom: 1px solid
|
|
730
|
+
border-bottom: 1px solid
|
|
731
|
+
color-mix(in srgb, var(--pie-text, #000) 15%, transparent);
|
|
692
732
|
}
|
|
693
733
|
|
|
694
734
|
/* ANSWER ELIMINATOR */
|
|
695
735
|
|
|
696
736
|
.pie-answer-eliminator-toggle {
|
|
697
|
-
|
|
698
|
-
|
|
737
|
+
/* em, not px: PNP font scaling raises the surrounding text size without
|
|
738
|
+
touching px boxes, which left a fixed 28px control under grown text. At the
|
|
739
|
+
default 16px base these are the 28px/18px the control used to pin, and the
|
|
740
|
+
box stays past SC 2.5.8's 24px minimum for any base at or above 14px. */
|
|
741
|
+
width: 1.75em;
|
|
742
|
+
height: 1.75em;
|
|
699
743
|
padding: 0;
|
|
700
744
|
border: 1px solid var(--pie-border, #ccc);
|
|
701
745
|
border-radius: 4px;
|
|
702
746
|
background: var(--pie-white, #fff);
|
|
703
747
|
cursor: pointer;
|
|
704
|
-
font-size:
|
|
748
|
+
font-size: 1.125em;
|
|
705
749
|
line-height: 1;
|
|
706
750
|
display: inline-flex;
|
|
707
751
|
align-items: center;
|
|
708
752
|
justify-content: center;
|
|
709
|
-
color: var(
|
|
753
|
+
color: var(
|
|
754
|
+
--pie-answer-eliminator-toggle-color,
|
|
755
|
+
var(--pie-button-color, #8c8c8c)
|
|
756
|
+
);
|
|
710
757
|
transition: all 0.2s ease;
|
|
711
758
|
z-index: 10;
|
|
712
759
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enough colour maths to keep a resolved token legible: WCAG relative luminance,
|
|
3
|
+
* a contrast ratio, and the largest share of a hue that still clears a threshold
|
|
4
|
+
* against the surface it is painted on.
|
|
5
|
+
*
|
|
6
|
+
* Colour parsing is injected rather than implemented here. Provider slots resolve
|
|
7
|
+
* to `oklch()` under DaisyUI 5, so an oklch-to-sRGB implementation in this file
|
|
8
|
+
* would be a second opinion about colours the browser has already decided;
|
|
9
|
+
* `createCanvasColorMeasure` asks the browser instead.
|
|
10
|
+
*/
|
|
11
|
+
export interface Srgb {
|
|
12
|
+
r: number;
|
|
13
|
+
g: number;
|
|
14
|
+
b: number;
|
|
15
|
+
/** 0..1 */
|
|
16
|
+
a: number;
|
|
17
|
+
}
|
|
18
|
+
/** Resolves any CSS colour syntax to 8-bit sRGB, or `null` if not a colour. */
|
|
19
|
+
export type ColorMeasure = (value: string) => Srgb | null;
|
|
20
|
+
/** WCAG 2.2 1.4.3 for text. */
|
|
21
|
+
export declare const LEGIBLE_TEXT_MINIMUM = 4.5;
|
|
22
|
+
/** WCAG 2.2 1.4.11 for component boundaries, states and graphical objects. */
|
|
23
|
+
export declare const LEGIBLE_NON_TEXT_MINIMUM = 3;
|
|
24
|
+
/**
|
|
25
|
+
* The hue share used when contrast cannot be measured. 30% is the largest 5%
|
|
26
|
+
* step that clears 4.5:1 for every success, error and warning slot across
|
|
27
|
+
* DaisyUI's 28 shipped themes — measured across all 84 combinations rather than
|
|
28
|
+
* picked. It is deliberately pessimistic: a theme whose slot needed no
|
|
29
|
+
* correction at all still gets pulled most of the way to the text colour.
|
|
30
|
+
*/
|
|
31
|
+
export declare const UNMEASURED_HUE_WEIGHT = 30;
|
|
32
|
+
/**
|
|
33
|
+
* The same pessimistic fallback for a 3:1 target: 35% is the largest 5% step
|
|
34
|
+
* that clears 3:1 for `--color-base-300` and `--color-neutral` in all 28 themes.
|
|
35
|
+
*/
|
|
36
|
+
export declare const UNMEASURED_NON_TEXT_HUE_WEIGHT = 35;
|
|
37
|
+
/** WCAG relative luminance. Alpha is ignored; composite before calling. */
|
|
38
|
+
export declare function relativeLuminance(color: Srgb): number;
|
|
39
|
+
export declare function contrastRatio(foreground: Srgb, background: Srgb): number;
|
|
40
|
+
export declare function mixTowards(hue: string, target: string, hueWeight: number): string;
|
|
41
|
+
/**
|
|
42
|
+
* Resolves any CSS colour the browser understands to 8-bit sRGB by painting one
|
|
43
|
+
* pixel and reading it back, so `oklch()` and `color-mix()` need no parser here.
|
|
44
|
+
*
|
|
45
|
+
* Returns `null` where there is no canvas to paint on — a server render, or a
|
|
46
|
+
* DOM shim in tests — which callers treat as "contrast is unmeasurable" rather
|
|
47
|
+
* than as an error.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createCanvasColorMeasure(): ColorMeasure | null;
|
|
50
|
+
/**
|
|
51
|
+
* The given hue if it already clears `minimum` against `background`, otherwise
|
|
52
|
+
* the largest share of it that does, mixed toward `text`.
|
|
53
|
+
*
|
|
54
|
+
* Mixing toward the theme's own text colour rather than toward black or white
|
|
55
|
+
* uses the theme's own guarantee: `text` is what that theme chose to be readable
|
|
56
|
+
* on that surface, in a light theme and a dark one alike. Stepping down from the
|
|
57
|
+
* top keeps as much hue as the threshold allows, so a slot that was already fine
|
|
58
|
+
* comes back untouched and a slot that was invisible loses only what it must.
|
|
59
|
+
*/
|
|
60
|
+
export declare function legibleColorAgainst(args: {
|
|
61
|
+
hue?: string;
|
|
62
|
+
text?: string;
|
|
63
|
+
background?: string;
|
|
64
|
+
measure?: ColorMeasure | null;
|
|
65
|
+
minimum?: number;
|
|
66
|
+
unmeasuredHueWeight?: number;
|
|
67
|
+
}): string | undefined;
|
package/dist/contrast.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enough colour maths to keep a resolved token legible: WCAG relative luminance,
|
|
3
|
+
* a contrast ratio, and the largest share of a hue that still clears a threshold
|
|
4
|
+
* against the surface it is painted on.
|
|
5
|
+
*
|
|
6
|
+
* Colour parsing is injected rather than implemented here. Provider slots resolve
|
|
7
|
+
* to `oklch()` under DaisyUI 5, so an oklch-to-sRGB implementation in this file
|
|
8
|
+
* would be a second opinion about colours the browser has already decided;
|
|
9
|
+
* `createCanvasColorMeasure` asks the browser instead.
|
|
10
|
+
*/
|
|
11
|
+
/** WCAG 2.2 1.4.3 for text. */
|
|
12
|
+
export const LEGIBLE_TEXT_MINIMUM = 4.5;
|
|
13
|
+
/** WCAG 2.2 1.4.11 for component boundaries, states and graphical objects. */
|
|
14
|
+
export const LEGIBLE_NON_TEXT_MINIMUM = 3;
|
|
15
|
+
/**
|
|
16
|
+
* The hue share used when contrast cannot be measured. 30% is the largest 5%
|
|
17
|
+
* step that clears 4.5:1 for every success, error and warning slot across
|
|
18
|
+
* DaisyUI's 28 shipped themes — measured across all 84 combinations rather than
|
|
19
|
+
* picked. It is deliberately pessimistic: a theme whose slot needed no
|
|
20
|
+
* correction at all still gets pulled most of the way to the text colour.
|
|
21
|
+
*/
|
|
22
|
+
export const UNMEASURED_HUE_WEIGHT = 30;
|
|
23
|
+
/**
|
|
24
|
+
* The same pessimistic fallback for a 3:1 target: 35% is the largest 5% step
|
|
25
|
+
* that clears 3:1 for `--color-base-300` and `--color-neutral` in all 28 themes.
|
|
26
|
+
*/
|
|
27
|
+
export const UNMEASURED_NON_TEXT_HUE_WEIGHT = 35;
|
|
28
|
+
const HUE_WEIGHT_STEP = 5;
|
|
29
|
+
function channelLuminance(channel) {
|
|
30
|
+
const c = channel / 255;
|
|
31
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
32
|
+
}
|
|
33
|
+
/** WCAG relative luminance. Alpha is ignored; composite before calling. */
|
|
34
|
+
export function relativeLuminance(color) {
|
|
35
|
+
return (0.2126 * channelLuminance(color.r) +
|
|
36
|
+
0.7152 * channelLuminance(color.g) +
|
|
37
|
+
0.0722 * channelLuminance(color.b));
|
|
38
|
+
}
|
|
39
|
+
export function contrastRatio(foreground, background) {
|
|
40
|
+
const a = relativeLuminance(foreground);
|
|
41
|
+
const b = relativeLuminance(background);
|
|
42
|
+
return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
|
|
43
|
+
}
|
|
44
|
+
export function mixTowards(hue, target, hueWeight) {
|
|
45
|
+
return `color-mix(in srgb, ${hue} ${hueWeight}%, ${target})`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolves any CSS colour the browser understands to 8-bit sRGB by painting one
|
|
49
|
+
* pixel and reading it back, so `oklch()` and `color-mix()` need no parser here.
|
|
50
|
+
*
|
|
51
|
+
* Returns `null` where there is no canvas to paint on — a server render, or a
|
|
52
|
+
* DOM shim in tests — which callers treat as "contrast is unmeasurable" rather
|
|
53
|
+
* than as an error.
|
|
54
|
+
*/
|
|
55
|
+
export function createCanvasColorMeasure() {
|
|
56
|
+
if (typeof document === "undefined") {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const canvas = document.createElement("canvas");
|
|
60
|
+
canvas.width = 1;
|
|
61
|
+
canvas.height = 1;
|
|
62
|
+
const context = canvas.getContext?.("2d", { willReadFrequently: true });
|
|
63
|
+
if (!context) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* `fillStyle` silently ignores a value it cannot parse, leaving the previous
|
|
68
|
+
* one in place. Assigning over two different sentinels separates the cases
|
|
69
|
+
* exactly: a parsed colour serializes the same both times, an ignored one
|
|
70
|
+
* keeps whichever sentinel it started from.
|
|
71
|
+
*/
|
|
72
|
+
return (value) => {
|
|
73
|
+
const trimmed = value.trim();
|
|
74
|
+
if (!trimmed) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
context.fillStyle = "#000000";
|
|
79
|
+
context.fillStyle = trimmed;
|
|
80
|
+
const fromBlack = context.fillStyle;
|
|
81
|
+
context.fillStyle = "#ffffff";
|
|
82
|
+
context.fillStyle = trimmed;
|
|
83
|
+
if (context.fillStyle !== fromBlack) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
context.clearRect(0, 0, 1, 1);
|
|
87
|
+
context.fillRect(0, 0, 1, 1);
|
|
88
|
+
const data = context.getImageData(0, 0, 1, 1).data;
|
|
89
|
+
return {
|
|
90
|
+
r: data[0],
|
|
91
|
+
g: data[1],
|
|
92
|
+
b: data[2],
|
|
93
|
+
a: data[3] / 255,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The given hue if it already clears `minimum` against `background`, otherwise
|
|
103
|
+
* the largest share of it that does, mixed toward `text`.
|
|
104
|
+
*
|
|
105
|
+
* Mixing toward the theme's own text colour rather than toward black or white
|
|
106
|
+
* uses the theme's own guarantee: `text` is what that theme chose to be readable
|
|
107
|
+
* on that surface, in a light theme and a dark one alike. Stepping down from the
|
|
108
|
+
* top keeps as much hue as the threshold allows, so a slot that was already fine
|
|
109
|
+
* comes back untouched and a slot that was invisible loses only what it must.
|
|
110
|
+
*/
|
|
111
|
+
export function legibleColorAgainst(args) {
|
|
112
|
+
const { hue, text, background, measure } = args;
|
|
113
|
+
if (!hue) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
if (!text) {
|
|
117
|
+
return hue;
|
|
118
|
+
}
|
|
119
|
+
const minimum = args.minimum ?? LEGIBLE_TEXT_MINIMUM;
|
|
120
|
+
const fallbackWeight = args.unmeasuredHueWeight ?? UNMEASURED_HUE_WEIGHT;
|
|
121
|
+
const unmeasured = () => mixTowards(hue, text, fallbackWeight);
|
|
122
|
+
if (!measure || !background) {
|
|
123
|
+
return unmeasured();
|
|
124
|
+
}
|
|
125
|
+
const surface = measure(background);
|
|
126
|
+
const raw = measure(hue);
|
|
127
|
+
// Compositing a translucent value needs the surface behind the surface, which
|
|
128
|
+
// a provider adapter reading one element does not have. Correcting against a
|
|
129
|
+
// guess is worse than taking the pessimistic weight.
|
|
130
|
+
if (!surface || !raw || surface.a < 1 || raw.a < 1) {
|
|
131
|
+
return unmeasured();
|
|
132
|
+
}
|
|
133
|
+
if (contrastRatio(raw, surface) >= minimum) {
|
|
134
|
+
return hue;
|
|
135
|
+
}
|
|
136
|
+
for (let weight = 100 - HUE_WEIGHT_STEP; weight >= HUE_WEIGHT_STEP; weight -= HUE_WEIGHT_STEP) {
|
|
137
|
+
const candidate = mixTowards(hue, text, weight);
|
|
138
|
+
const resolved = measure(candidate);
|
|
139
|
+
// The measurer cannot resolve `color-mix`, so every further step is
|
|
140
|
+
// unmeasurable too. Stop rather than walking down to the text colour.
|
|
141
|
+
if (!resolved) {
|
|
142
|
+
return unmeasured();
|
|
143
|
+
}
|
|
144
|
+
if (contrastRatio(resolved, surface) >= minimum) {
|
|
145
|
+
return candidate;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Nothing with any hue left in it passes, so fall back to the one colour the
|
|
149
|
+
// theme guarantees against this surface.
|
|
150
|
+
return text;
|
|
151
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one table that says which DaisyUI slot each `--pie-*` token comes from,
|
|
3
|
+
* and one renderer that turns it into variables.
|
|
4
|
+
*
|
|
5
|
+
* It exists because the same 47-row table was written out four times — the
|
|
6
|
+
* provider adapter here, two mappers in `@pie-players/pie-theme-daisyui`, and
|
|
7
|
+
* that package's `bridge.css` — and copies drift. Two defects lived in the drift:
|
|
8
|
+
* `--pie-missing` was corrected to `--color-warning` in one copy while three kept
|
|
9
|
+
* it on `--color-error`, and the parity test that was supposed to catch this
|
|
10
|
+
* compared only the token names, never the slot each one derived from.
|
|
11
|
+
*
|
|
12
|
+
* CSS cannot import a table, so `bridge.css` stays hand-written and is held to
|
|
13
|
+
* this one by `tests/daisyui-mapping-parity.test.mjs` instead.
|
|
14
|
+
*/
|
|
15
|
+
import { type ColorMeasure } from "./contrast.js";
|
|
16
|
+
/** The DaisyUI slots this mapping reads. Not all of DaisyUI's palette. */
|
|
17
|
+
export type DaisySlot = "base100" | "base200" | "base300" | "baseContent" | "primary" | "secondary" | "accent" | "neutral" | "neutralContent" | "success" | "error" | "warning";
|
|
18
|
+
export declare const DAISY_SLOT_CSS_VARIABLES: Record<DaisySlot, string>;
|
|
19
|
+
export type DaisyMappingEntry =
|
|
20
|
+
/** The slot, verbatim. */
|
|
21
|
+
{
|
|
22
|
+
token: string;
|
|
23
|
+
kind: "direct";
|
|
24
|
+
from: DaisySlot;
|
|
25
|
+
}
|
|
26
|
+
/** A fixed blend, for tints and shades that are not contrast-critical. */
|
|
27
|
+
| {
|
|
28
|
+
token: string;
|
|
29
|
+
kind: "mix";
|
|
30
|
+
from: DaisySlot;
|
|
31
|
+
towards: DaisySlot;
|
|
32
|
+
weight: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The slot if it already clears `minimum` against the page, otherwise the
|
|
36
|
+
* largest share of it that does. For the tokens PIE paints as a foreground or
|
|
37
|
+
* as the boundary of a control, where a DaisyUI surface slot taken verbatim is
|
|
38
|
+
* unreadable.
|
|
39
|
+
*/
|
|
40
|
+
| {
|
|
41
|
+
token: string;
|
|
42
|
+
kind: "legible";
|
|
43
|
+
from: DaisySlot;
|
|
44
|
+
minimum: number;
|
|
45
|
+
fallbackWeight: number;
|
|
46
|
+
};
|
|
47
|
+
export declare const DAISYUI_PIE_TOKEN_MAP: readonly DaisyMappingEntry[];
|
|
48
|
+
/**
|
|
49
|
+
* @param read one DaisyUI slot's value, or `undefined` when this source has none
|
|
50
|
+
* @param measure resolves a colour so `legible` entries can be corrected against
|
|
51
|
+
* a measured ratio. Omit it where the values are `var()` references rather than
|
|
52
|
+
* colours: those cannot be measured, and every `legible` entry then takes its
|
|
53
|
+
* pessimistic fixed weight instead.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveDaisyPieVariables(args: {
|
|
56
|
+
read: (slot: DaisySlot) => string | undefined;
|
|
57
|
+
measure?: ColorMeasure | null;
|
|
58
|
+
}): Record<string, string>;
|