@camp.dev/bones 0.3.0 → 0.4.1
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 +22 -16
- package/dist/css/auto.css +41 -27
- package/dist/css/bones.css +76 -28
- package/dist/element/overlay.mjs +5 -5
- package/dist/react/create-bones.mjs +2 -6
- package/dist/react/index.d.mts +1 -2
- package/dist/react/index.mjs +1 -2
- package/package.json +1 -1
- package/src/css/auto.css +41 -27
- package/src/css/bones.css +76 -28
- package/dist/react/bones.d.mts +0 -15
- package/dist/react/bones.mjs +0 -27
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Bones skips the duplication. You write your markup once and it handles both stat
|
|
|
14
14
|
|
|
15
15
|
- Works in Server Components. No hooks, no context, no `'use client'`.
|
|
16
16
|
- One component handles both loading and loaded states.
|
|
17
|
-
- Pass a promise
|
|
17
|
+
- Pass data or a promise. A pending promise suspends to your `<Suspense>` boundary; `forceBones` renders the skeleton.
|
|
18
18
|
- Skeletons are pure CSS, themed with custom properties.
|
|
19
19
|
- Loading elements get `aria-busy="true"` automatically.
|
|
20
20
|
|
|
@@ -34,7 +34,7 @@ import "@camp.dev/bones/css";
|
|
|
34
34
|
|
|
35
35
|
| Import | Contents |
|
|
36
36
|
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
37
|
-
| `@camp.dev/bones/react` | `createBones`, `readPromise`, `forceBones`, `minMax`,
|
|
37
|
+
| `@camp.dev/bones/react` | `createBones`, `readPromise`, `forceBones`, `minMax`, `isMinMax`, `BonesBoundary` |
|
|
38
38
|
| `@camp.dev/bones/css` | The skeleton stylesheet. Import once in your root layout. |
|
|
39
39
|
| `@camp.dev/bones/auto.css` | Skeletonizes unmarked leaves under `aria-busy="true"`. Imports the base stylesheet itself, so a separate `/css` import is optional. |
|
|
40
40
|
| `@camp.dev/bones/element` | `<bones-boundary>`, a custom element that sets `aria-busy` and `inert` on its subtree with `delay`, `min-duration`, and a crossfade. `precision="measured"` draws pixel-accurate per-line bones measured from the content. |
|
|
@@ -65,21 +65,23 @@ function ProfileCard({ user }: { user: Promise<User> | User }) {
|
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
Pass the promise to the component, and reuse the same component with `forceBones` as the fallback:
|
|
69
69
|
|
|
70
70
|
```tsx
|
|
71
|
-
import {
|
|
71
|
+
import { forceBones } from "@camp.dev/bones/react";
|
|
72
|
+
import { Suspense } from "react";
|
|
72
73
|
|
|
73
74
|
export default function Page() {
|
|
75
|
+
const user = fetchUser();
|
|
74
76
|
return (
|
|
75
|
-
<
|
|
76
|
-
<ProfileCard user={
|
|
77
|
-
</
|
|
77
|
+
<Suspense fallback={<ProfileCard user={forceBones} />}>
|
|
78
|
+
<ProfileCard user={user} />
|
|
79
|
+
</Suspense>
|
|
78
80
|
);
|
|
79
81
|
}
|
|
80
82
|
```
|
|
81
83
|
|
|
82
|
-
While the promise is pending,
|
|
84
|
+
While the promise is pending, the fallback renders `<ProfileCard>` with skeletons visible. Once it resolves, the real content swaps in. There is no separate skeleton component to keep in sync — the fallback is the component.
|
|
83
85
|
|
|
84
86
|
## Bone types
|
|
85
87
|
|
|
@@ -99,17 +101,21 @@ import { createBones, forceBones } from "@camp.dev/bones/react";
|
|
|
99
101
|
<ProfileCard user={forceBones} />;
|
|
100
102
|
```
|
|
101
103
|
|
|
102
|
-
To force
|
|
104
|
+
To force a subtree into skeleton mode at once, pass `forceBones` to each component in it:
|
|
103
105
|
|
|
104
106
|
```tsx
|
|
105
|
-
|
|
107
|
+
<ProfileCard user={forceBones} />
|
|
108
|
+
<PostList posts={forceBones} />
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`forceBones` only forces the component it's passed to. A component that derives child props from its own data must forward it itself, such as `PostList` mapping items into cards. `repeat` yields `undefined` for items that don't exist yet:
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
<PostList />
|
|
110
|
-
</BonesForce>;
|
|
113
|
+
```tsx
|
|
114
|
+
<PostCard key={item?.id ?? i} post={item ?? forceBones} />
|
|
111
115
|
```
|
|
112
116
|
|
|
117
|
+
For content that has no bone markup at all, wrap it in [`<bones-boundary force>`](https://github.com/campdotdev/bones/blob/main/apps/docs/content/docs/api/bones-boundary.mdx) and let `auto.css` draw leaf bones.
|
|
118
|
+
|
|
113
119
|
## Automatic skeletons
|
|
114
120
|
|
|
115
121
|
For markup you haven't wired up with `bone()` — third-party components, server-rendered HTML, anything without explicit attributes — import the auto stylesheet. It imports `/css` itself, so this one file is a complete setup:
|
|
@@ -127,9 +133,9 @@ Set `aria-busy="true"` on the loading region and every unmarked leaf inside it b
|
|
|
127
133
|
</section>
|
|
128
134
|
```
|
|
129
135
|
|
|
130
|
-
`[data-bones-auto="off"]` opts a subtree out — useful for a status message you want to stay readable while its container skeletonizes. Explicit `data-bone` markup is left alone; `auto.css` only styles elements neither `bone()` nor a manual `data-bone` attribute has already claimed.
|
|
136
|
+
`[data-bones-auto="off"]` opts a subtree out — useful for a status message you want to stay readable while its container skeletonizes. Images, canvas, and video without native controls get a solid bone even after they have loaded, unless page CSS sets `object-position` on them, which outranks the layered rule the same way `color` does. A loaded `iframe`, `embed`, or `object` still shows its content, because those elements ignore `object-position`. Explicit `data-bone` markup is left alone; `auto.css` only styles elements neither `bone()` nor a manual `data-bone` attribute has already claimed.
|
|
131
137
|
|
|
132
|
-
Auto rules live in `@layer bones-auto`, so any page CSS that sets `color` on an element outranks the bone's transparent text, and that text stays visible over its skeleton bar. `data-bone-animate`
|
|
138
|
+
Auto rules live in `@layer bones-auto`, so any page CSS that sets `color` on an element outranks the bone's transparent text, and that text stays visible over its skeleton bar. `data-bone-animate` works on the `aria-busy` element itself or on any ancestor. The `data-bone-animate` overrides rely on `@scope`. In a browser without `@scope`, every bone shimmers, and `data-bone-animate="pulse"` and `"none"` cannot change that. The `prefers-reduced-motion` fallback to pulse still applies.
|
|
133
139
|
|
|
134
140
|
## Without React
|
|
135
141
|
|
package/dist/css/auto.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
select *,
|
|
45
45
|
object *
|
|
46
46
|
) {
|
|
47
|
-
color:
|
|
47
|
+
color: rgb(from currentcolor r g b / 0);
|
|
48
48
|
position: relative;
|
|
49
49
|
min-width: 4ch;
|
|
50
50
|
min-height: 1lh;
|
|
@@ -144,10 +144,21 @@
|
|
|
144
144
|
progress,
|
|
145
145
|
meter
|
|
146
146
|
):not([data-bone], [data-bone] *, [data-bones-auto="off"], [data-bones-auto="off"] *) {
|
|
147
|
-
color:
|
|
147
|
+
color: rgb(from currentcolor r g b / 0);
|
|
148
148
|
background-color: var(--bone-base);
|
|
149
149
|
border-radius: var(--bone-radius);
|
|
150
150
|
border-color: transparent;
|
|
151
|
+
/* A loaded image paints over its own background, so the bone color alone
|
|
152
|
+
does not cover it. Pushing the picture out of its content box leaves
|
|
153
|
+
the box and its background in place (BON-15). Only img, video, and
|
|
154
|
+
canvas honor this; a loaded iframe, embed, or object still shows, and
|
|
155
|
+
native video controls paint over the bone. object-position is
|
|
156
|
+
interpolable, so a page `transition: all` on images would slide the
|
|
157
|
+
picture out over the transition's duration; transition: none stops
|
|
158
|
+
that on the way in. On the way out the page's own transition applies,
|
|
159
|
+
and the picture reappears at the end of it instead of the start. */
|
|
160
|
+
object-position: 9999px 0;
|
|
161
|
+
transition: none;
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
/* Width variance: text leaves cycle 85 / 100 / 92 / 60 so runs of lines
|
|
@@ -283,10 +294,13 @@
|
|
|
283
294
|
}
|
|
284
295
|
|
|
285
296
|
@layer bones-auto {
|
|
286
|
-
/* Auto bones default to shimmer. bones.css
|
|
287
|
-
[data-bone
|
|
288
|
-
the default
|
|
289
|
-
that
|
|
297
|
+
/* Auto bones default to shimmer. bones.css has its own default, but its
|
|
298
|
+
rules match [data-bone] elements only and never the unmarked leaves
|
|
299
|
+
targeted here, so the default is restated for them, and the scopes below
|
|
300
|
+
mirror bones.css for pages that set the attribute (BON-16). In those
|
|
301
|
+
scopes, :is(:scope, :scope *) lets the attribute
|
|
302
|
+
sit on the aria-busy element itself: a scoped selector's implicit :scope
|
|
303
|
+
prefix matches strict descendants only (BON-17). */
|
|
290
304
|
[aria-busy="true"]
|
|
291
305
|
:not(:has(*)):not(
|
|
292
306
|
[data-bone],
|
|
@@ -374,7 +388,7 @@
|
|
|
374
388
|
}
|
|
375
389
|
|
|
376
390
|
@scope ([data-bone-animate="shimmer"]) to ([data-bone-animate]:not([data-bone-animate="shimmer"])) {
|
|
377
|
-
[aria-busy="true"]
|
|
391
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
378
392
|
:not(:has(*)):not(
|
|
379
393
|
[data-bone],
|
|
380
394
|
[data-bone] *,
|
|
@@ -403,7 +417,7 @@
|
|
|
403
417
|
select *,
|
|
404
418
|
object *
|
|
405
419
|
)::before,
|
|
406
|
-
[aria-busy="true"]
|
|
420
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
407
421
|
:not(:has(*)):not(
|
|
408
422
|
[data-bone],
|
|
409
423
|
[data-bone] *,
|
|
@@ -432,7 +446,7 @@
|
|
|
432
446
|
select *,
|
|
433
447
|
object *
|
|
434
448
|
)::after,
|
|
435
|
-
[aria-busy="true"]
|
|
449
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
436
450
|
:is(
|
|
437
451
|
img,
|
|
438
452
|
svg,
|
|
@@ -461,7 +475,7 @@
|
|
|
461
475
|
}
|
|
462
476
|
|
|
463
477
|
@media (prefers-reduced-motion: reduce) {
|
|
464
|
-
[aria-busy="true"]
|
|
478
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
465
479
|
:not(:has(*)):not(
|
|
466
480
|
[data-bone],
|
|
467
481
|
[data-bone] *,
|
|
@@ -490,7 +504,7 @@
|
|
|
490
504
|
select *,
|
|
491
505
|
object *
|
|
492
506
|
)::before,
|
|
493
|
-
[aria-busy="true"]
|
|
507
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
494
508
|
:not(:has(*)):not(
|
|
495
509
|
[data-bone],
|
|
496
510
|
[data-bone] *,
|
|
@@ -519,7 +533,7 @@
|
|
|
519
533
|
select *,
|
|
520
534
|
object *
|
|
521
535
|
)::after,
|
|
522
|
-
[aria-busy="true"]
|
|
536
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
523
537
|
:is(
|
|
524
538
|
img,
|
|
525
539
|
svg,
|
|
@@ -544,7 +558,7 @@
|
|
|
544
558
|
}
|
|
545
559
|
|
|
546
560
|
@media (forced-colors: active) {
|
|
547
|
-
[aria-busy="true"]
|
|
561
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
548
562
|
:not(:has(*)):not(
|
|
549
563
|
[data-bone],
|
|
550
564
|
[data-bone] *,
|
|
@@ -573,7 +587,7 @@
|
|
|
573
587
|
select *,
|
|
574
588
|
object *
|
|
575
589
|
)::after,
|
|
576
|
-
[aria-busy="true"]
|
|
590
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
577
591
|
:is(
|
|
578
592
|
img,
|
|
579
593
|
svg,
|
|
@@ -598,7 +612,7 @@
|
|
|
598
612
|
}
|
|
599
613
|
|
|
600
614
|
@scope ([data-bone-animate="pulse"]) to ([data-bone-animate]:not([data-bone-animate="pulse"])) {
|
|
601
|
-
[aria-busy="true"]
|
|
615
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
602
616
|
:not(:has(*)):not(
|
|
603
617
|
[data-bone],
|
|
604
618
|
[data-bone] *,
|
|
@@ -627,7 +641,7 @@
|
|
|
627
641
|
select *,
|
|
628
642
|
object *
|
|
629
643
|
)::before,
|
|
630
|
-
[aria-busy="true"]
|
|
644
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
631
645
|
:not(:has(*)):not(
|
|
632
646
|
[data-bone],
|
|
633
647
|
[data-bone] *,
|
|
@@ -656,7 +670,7 @@
|
|
|
656
670
|
select *,
|
|
657
671
|
object *
|
|
658
672
|
)::after,
|
|
659
|
-
[aria-busy="true"]
|
|
673
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
660
674
|
:is(
|
|
661
675
|
img,
|
|
662
676
|
svg,
|
|
@@ -680,7 +694,7 @@
|
|
|
680
694
|
}
|
|
681
695
|
|
|
682
696
|
@media (prefers-reduced-motion: reduce) {
|
|
683
|
-
[aria-busy="true"]
|
|
697
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
684
698
|
:not(:has(*)):not(
|
|
685
699
|
[data-bone],
|
|
686
700
|
[data-bone] *,
|
|
@@ -709,7 +723,7 @@
|
|
|
709
723
|
select *,
|
|
710
724
|
object *
|
|
711
725
|
)::before,
|
|
712
|
-
[aria-busy="true"]
|
|
726
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
713
727
|
:not(:has(*)):not(
|
|
714
728
|
[data-bone],
|
|
715
729
|
[data-bone] *,
|
|
@@ -738,7 +752,7 @@
|
|
|
738
752
|
select *,
|
|
739
753
|
object *
|
|
740
754
|
)::after,
|
|
741
|
-
[aria-busy="true"]
|
|
755
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
742
756
|
:is(
|
|
743
757
|
img,
|
|
744
758
|
svg,
|
|
@@ -763,7 +777,7 @@
|
|
|
763
777
|
}
|
|
764
778
|
|
|
765
779
|
@media (forced-colors: active) {
|
|
766
|
-
[aria-busy="true"]
|
|
780
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
767
781
|
:not(:has(*)):not(
|
|
768
782
|
[data-bone],
|
|
769
783
|
[data-bone] *,
|
|
@@ -792,7 +806,7 @@
|
|
|
792
806
|
select *,
|
|
793
807
|
object *
|
|
794
808
|
)::after,
|
|
795
|
-
[aria-busy="true"]
|
|
809
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
796
810
|
:is(
|
|
797
811
|
img,
|
|
798
812
|
svg,
|
|
@@ -817,7 +831,7 @@
|
|
|
817
831
|
}
|
|
818
832
|
|
|
819
833
|
@scope ([data-bone-animate="none"]) to ([data-bone-animate]:not([data-bone-animate="none"])) {
|
|
820
|
-
[aria-busy="true"]
|
|
834
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
821
835
|
:not(:has(*)):not(
|
|
822
836
|
[data-bone],
|
|
823
837
|
[data-bone] *,
|
|
@@ -846,7 +860,7 @@
|
|
|
846
860
|
select *,
|
|
847
861
|
object *
|
|
848
862
|
)::before,
|
|
849
|
-
[aria-busy="true"]
|
|
863
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
850
864
|
:not(:has(*)):not(
|
|
851
865
|
[data-bone],
|
|
852
866
|
[data-bone] *,
|
|
@@ -875,7 +889,7 @@
|
|
|
875
889
|
select *,
|
|
876
890
|
object *
|
|
877
891
|
)::after,
|
|
878
|
-
[aria-busy="true"]
|
|
892
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
879
893
|
:is(
|
|
880
894
|
img,
|
|
881
895
|
svg,
|
|
@@ -899,7 +913,7 @@
|
|
|
899
913
|
}
|
|
900
914
|
|
|
901
915
|
@media (forced-colors: active) {
|
|
902
|
-
[aria-busy="true"]
|
|
916
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
903
917
|
:not(:has(*)):not(
|
|
904
918
|
[data-bone],
|
|
905
919
|
[data-bone] *,
|
|
@@ -928,7 +942,7 @@
|
|
|
928
942
|
select *,
|
|
929
943
|
object *
|
|
930
944
|
)::after,
|
|
931
|
-
[aria-busy="true"]
|
|
945
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
932
946
|
:is(
|
|
933
947
|
img,
|
|
934
948
|
svg,
|
package/dist/css/bones.css
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/* Bones derive their color from the text color in effect at the bone, so
|
|
3
|
+
they inherit the page's own contrast guarantee on any background in any
|
|
4
|
+
scheme (BON-13). currentColor resolves where var() is used, not here.
|
|
5
|
+
The alpha reset matters: bones hide their content by zeroing the alpha
|
|
6
|
+
of the color property (never with the transparent keyword, which would
|
|
7
|
+
discard the channels), and the mix restores it. */
|
|
8
|
+
--bone-base: color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent);
|
|
9
|
+
--bone-highlight: color-mix(in srgb, rgb(from currentcolor r g b / 1) 6%, transparent);
|
|
4
10
|
--bone-radius: 4px;
|
|
5
11
|
--bone-duration: 1.5s;
|
|
6
12
|
}
|
|
7
13
|
|
|
8
|
-
@media (prefers-color-scheme: dark) {
|
|
9
|
-
:root {
|
|
10
|
-
--bone-base: rgba(255, 255, 255, 0.12);
|
|
11
|
-
--bone-highlight: rgba(255, 255, 255, 0.06);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
14
|
[data-bone="text"] {
|
|
16
|
-
color:
|
|
15
|
+
color: rgb(from currentcolor r g b / 0);
|
|
17
16
|
position: relative;
|
|
18
17
|
min-width: 4ch;
|
|
19
18
|
min-height: 1lh;
|
|
@@ -71,8 +70,15 @@
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
img[data-bone="block"],
|
|
74
|
-
video[data-bone="block"]
|
|
75
|
-
|
|
73
|
+
video[data-bone="block"],
|
|
74
|
+
canvas[data-bone="block"] {
|
|
75
|
+
color: rgb(from currentcolor r g b / 0);
|
|
76
|
+
/* A loaded picture paints over the bone's background-color; pushing it out
|
|
77
|
+
of the content box keeps the box and background visible (BON-15).
|
|
78
|
+
transition: none keeps a page `transition: all` from sliding the picture
|
|
79
|
+
out on the way in; see the matching comment in auto.css. */
|
|
80
|
+
object-position: 9999px 0;
|
|
81
|
+
transition: none;
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
[data-bone="container"] {
|
|
@@ -111,11 +117,43 @@ video[data-bone="block"] {
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
|
|
120
|
+
/* Marked bones shimmer by default, the same as auto bones and the measured
|
|
121
|
+
overlay (BON-16). data-bone-animate="none" is the opt-out; the @scope
|
|
122
|
+
blocks below override this at higher specificity, so their order relative
|
|
123
|
+
to this rule does not matter. */
|
|
124
|
+
[data-bone="text"]::after,
|
|
125
|
+
[data-bone="block"],
|
|
126
|
+
[data-bone="container"]::before {
|
|
127
|
+
animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
|
|
128
|
+
background: linear-gradient(
|
|
129
|
+
90deg,
|
|
130
|
+
var(--bone-base) 25%,
|
|
131
|
+
var(--bone-highlight) 50%,
|
|
132
|
+
var(--bone-base) 75%
|
|
133
|
+
);
|
|
134
|
+
background-size: 200% 100%;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@media (prefers-reduced-motion: reduce) {
|
|
115
138
|
[data-bone="text"]::after,
|
|
116
|
-
[data-bone="text"]::before,
|
|
117
139
|
[data-bone="block"],
|
|
118
140
|
[data-bone="container"]::before {
|
|
141
|
+
animation: bone-pulse 2s ease-in-out infinite;
|
|
142
|
+
background: var(--bone-base);
|
|
143
|
+
background-size: auto;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* :is(:scope, :scope *) lets the attribute work from the bone itself, not
|
|
148
|
+
only from a wrapper: a scoped selector's implicit :scope prefix matches
|
|
149
|
+
strict descendants only, so a plain compound never matches the scope root
|
|
150
|
+
(BON-17). The reduced-motion override sits inside each animated scope so it
|
|
151
|
+
matches at the scope's own specificity; "none" has no override because none
|
|
152
|
+
still means none. */
|
|
153
|
+
@scope ([data-bone-animate="shimmer"]) to ([data-bone-animate]:not([data-bone-animate="shimmer"])) {
|
|
154
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
155
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
156
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
119
157
|
animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
|
|
120
158
|
background: linear-gradient(
|
|
121
159
|
90deg,
|
|
@@ -125,32 +163,42 @@ video[data-bone="block"] {
|
|
|
125
163
|
);
|
|
126
164
|
background-size: 200% 100%;
|
|
127
165
|
}
|
|
166
|
+
|
|
167
|
+
@media (prefers-reduced-motion: reduce) {
|
|
168
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
169
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
170
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
171
|
+
animation: bone-pulse 2s ease-in-out infinite;
|
|
172
|
+
background: var(--bone-base);
|
|
173
|
+
background-size: auto;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
128
176
|
}
|
|
129
177
|
|
|
130
178
|
@scope ([data-bone-animate="pulse"]) to ([data-bone-animate]:not([data-bone-animate="pulse"])) {
|
|
131
|
-
[data-bone="text"]::after,
|
|
132
|
-
[data-bone="
|
|
133
|
-
[data-bone="
|
|
134
|
-
[data-bone="container"]::before {
|
|
179
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
180
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
181
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
135
182
|
animation: bone-pulse var(--bone-duration) ease-in-out infinite;
|
|
136
183
|
background: var(--bone-base);
|
|
137
184
|
background-size: auto;
|
|
138
185
|
}
|
|
186
|
+
|
|
187
|
+
@media (prefers-reduced-motion: reduce) {
|
|
188
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
189
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
190
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
191
|
+
animation: bone-pulse 2s ease-in-out infinite;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
139
194
|
}
|
|
140
195
|
|
|
141
196
|
@scope ([data-bone-animate="none"]) to ([data-bone-animate]:not([data-bone-animate="none"])) {
|
|
142
|
-
[data-bone="text"]::after,
|
|
143
|
-
[data-bone="
|
|
144
|
-
[data-bone="
|
|
145
|
-
[data-bone="container"]::before {
|
|
197
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
198
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
199
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
146
200
|
animation: none;
|
|
147
201
|
background: var(--bone-base);
|
|
148
202
|
background-size: auto;
|
|
149
203
|
}
|
|
150
204
|
}
|
|
151
|
-
|
|
152
|
-
@media (prefers-reduced-motion: reduce) {
|
|
153
|
-
[aria-busy="true"] {
|
|
154
|
-
animation: bone-pulse 2s ease-in-out infinite;
|
|
155
|
-
}
|
|
156
|
-
}
|
package/dist/element/overlay.mjs
CHANGED
|
@@ -19,7 +19,7 @@ const OVERLAY_CSS = `
|
|
|
19
19
|
}
|
|
20
20
|
[part~="bone"] {
|
|
21
21
|
position: absolute;
|
|
22
|
-
background: var(--bone-base,
|
|
22
|
+
background: var(--bone-base, color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent));
|
|
23
23
|
border-radius: var(--bone-radius, 4px);
|
|
24
24
|
}
|
|
25
25
|
@keyframes bone-shimmer {
|
|
@@ -35,9 +35,9 @@ const OVERLAY_CSS = `
|
|
|
35
35
|
animation: bone-shimmer var(--bone-duration, 1.5s) ease-in-out infinite;
|
|
36
36
|
background: linear-gradient(
|
|
37
37
|
90deg,
|
|
38
|
-
var(--bone-base,
|
|
39
|
-
var(--bone-highlight,
|
|
40
|
-
var(--bone-base,
|
|
38
|
+
var(--bone-base, color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent)) 25%,
|
|
39
|
+
var(--bone-highlight, color-mix(in srgb, rgb(from currentcolor r g b / 1) 6%, transparent)) 50%,
|
|
40
|
+
var(--bone-base, color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent)) 75%
|
|
41
41
|
);
|
|
42
42
|
background-size: 200% 100%;
|
|
43
43
|
}
|
|
@@ -55,7 +55,7 @@ const OVERLAY_CSS = `
|
|
|
55
55
|
[part~="overlay"][data-bone-animate="shimmer"] [part~="bone"],
|
|
56
56
|
[part~="overlay"][data-bone-animate="pulse"] [part~="bone"] {
|
|
57
57
|
animation: bone-pulse 2s ease-in-out infinite;
|
|
58
|
-
background: var(--bone-base,
|
|
58
|
+
background: var(--bone-base, color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent));
|
|
59
59
|
background-size: auto;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { boneAttributes } from "../core/attributes.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { cloneElement, createElement, isValidElement } from "react";
|
|
3
3
|
//#region src/react/create-bones.ts
|
|
4
|
-
const getBonesContext = cache(() => ({ loading: false }));
|
|
5
4
|
function withKey(node, key) {
|
|
6
5
|
return isValidElement(node) ? cloneElement(node, { key }) : node;
|
|
7
6
|
}
|
|
@@ -40,9 +39,6 @@ function createBones(dataOrOptions, maybeOptions) {
|
|
|
40
39
|
} else if (data != null && data === forceBones) {
|
|
41
40
|
isLoading = true;
|
|
42
41
|
resolved = void 0;
|
|
43
|
-
} else if (getBonesContext().loading) {
|
|
44
|
-
isLoading = true;
|
|
45
|
-
resolved = void 0;
|
|
46
42
|
} else if (data != null && data instanceof Promise) resolved = readPromise(data);
|
|
47
43
|
else resolved = data;
|
|
48
44
|
let boneCallIndex = 0;
|
|
@@ -70,4 +66,4 @@ function createBones(dataOrOptions, maybeOptions) {
|
|
|
70
66
|
};
|
|
71
67
|
}
|
|
72
68
|
//#endregion
|
|
73
|
-
export { createBones, forceBones,
|
|
69
|
+
export { createBones, forceBones, readPromise };
|
package/dist/react/index.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BoneOptions, BoneType, MinMax, isMinMax, minMax } from "../core/attributes.mjs";
|
|
2
2
|
import { CreateBonesOptions, CreateBonesReturn, createBones, forceBones, readPromise } from "./create-bones.mjs";
|
|
3
|
-
import { Bones, BonesForce } from "./bones.mjs";
|
|
4
3
|
import { BonesBoundary, BonesBoundaryProps } from "./boundary.mjs";
|
|
5
|
-
export { type BoneOptions, type BoneType,
|
|
4
|
+
export { type BoneOptions, type BoneType, BonesBoundary, type BonesBoundaryProps, type CreateBonesOptions, type CreateBonesReturn, type MinMax, createBones, forceBones, isMinMax, minMax, readPromise };
|
package/dist/react/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isMinMax, minMax } from "../core/attributes.mjs";
|
|
2
2
|
import { createBones, forceBones, readPromise } from "./create-bones.mjs";
|
|
3
|
-
import { Bones, BonesForce } from "./bones.mjs";
|
|
4
3
|
import { BonesBoundary } from "./boundary.mjs";
|
|
5
|
-
export {
|
|
4
|
+
export { BonesBoundary, createBones, forceBones, isMinMax, minMax, readPromise };
|
package/package.json
CHANGED
package/src/css/auto.css
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
select *,
|
|
45
45
|
object *
|
|
46
46
|
) {
|
|
47
|
-
color:
|
|
47
|
+
color: rgb(from currentcolor r g b / 0);
|
|
48
48
|
position: relative;
|
|
49
49
|
min-width: 4ch;
|
|
50
50
|
min-height: 1lh;
|
|
@@ -144,10 +144,21 @@
|
|
|
144
144
|
progress,
|
|
145
145
|
meter
|
|
146
146
|
):not([data-bone], [data-bone] *, [data-bones-auto="off"], [data-bones-auto="off"] *) {
|
|
147
|
-
color:
|
|
147
|
+
color: rgb(from currentcolor r g b / 0);
|
|
148
148
|
background-color: var(--bone-base);
|
|
149
149
|
border-radius: var(--bone-radius);
|
|
150
150
|
border-color: transparent;
|
|
151
|
+
/* A loaded image paints over its own background, so the bone color alone
|
|
152
|
+
does not cover it. Pushing the picture out of its content box leaves
|
|
153
|
+
the box and its background in place (BON-15). Only img, video, and
|
|
154
|
+
canvas honor this; a loaded iframe, embed, or object still shows, and
|
|
155
|
+
native video controls paint over the bone. object-position is
|
|
156
|
+
interpolable, so a page `transition: all` on images would slide the
|
|
157
|
+
picture out over the transition's duration; transition: none stops
|
|
158
|
+
that on the way in. On the way out the page's own transition applies,
|
|
159
|
+
and the picture reappears at the end of it instead of the start. */
|
|
160
|
+
object-position: 9999px 0;
|
|
161
|
+
transition: none;
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
/* Width variance: text leaves cycle 85 / 100 / 92 / 60 so runs of lines
|
|
@@ -283,10 +294,13 @@
|
|
|
283
294
|
}
|
|
284
295
|
|
|
285
296
|
@layer bones-auto {
|
|
286
|
-
/* Auto bones default to shimmer. bones.css
|
|
287
|
-
[data-bone
|
|
288
|
-
the default
|
|
289
|
-
that
|
|
297
|
+
/* Auto bones default to shimmer. bones.css has its own default, but its
|
|
298
|
+
rules match [data-bone] elements only and never the unmarked leaves
|
|
299
|
+
targeted here, so the default is restated for them, and the scopes below
|
|
300
|
+
mirror bones.css for pages that set the attribute (BON-16). In those
|
|
301
|
+
scopes, :is(:scope, :scope *) lets the attribute
|
|
302
|
+
sit on the aria-busy element itself: a scoped selector's implicit :scope
|
|
303
|
+
prefix matches strict descendants only (BON-17). */
|
|
290
304
|
[aria-busy="true"]
|
|
291
305
|
:not(:has(*)):not(
|
|
292
306
|
[data-bone],
|
|
@@ -374,7 +388,7 @@
|
|
|
374
388
|
}
|
|
375
389
|
|
|
376
390
|
@scope ([data-bone-animate="shimmer"]) to ([data-bone-animate]:not([data-bone-animate="shimmer"])) {
|
|
377
|
-
[aria-busy="true"]
|
|
391
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
378
392
|
:not(:has(*)):not(
|
|
379
393
|
[data-bone],
|
|
380
394
|
[data-bone] *,
|
|
@@ -403,7 +417,7 @@
|
|
|
403
417
|
select *,
|
|
404
418
|
object *
|
|
405
419
|
)::before,
|
|
406
|
-
[aria-busy="true"]
|
|
420
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
407
421
|
:not(:has(*)):not(
|
|
408
422
|
[data-bone],
|
|
409
423
|
[data-bone] *,
|
|
@@ -432,7 +446,7 @@
|
|
|
432
446
|
select *,
|
|
433
447
|
object *
|
|
434
448
|
)::after,
|
|
435
|
-
[aria-busy="true"]
|
|
449
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
436
450
|
:is(
|
|
437
451
|
img,
|
|
438
452
|
svg,
|
|
@@ -461,7 +475,7 @@
|
|
|
461
475
|
}
|
|
462
476
|
|
|
463
477
|
@media (prefers-reduced-motion: reduce) {
|
|
464
|
-
[aria-busy="true"]
|
|
478
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
465
479
|
:not(:has(*)):not(
|
|
466
480
|
[data-bone],
|
|
467
481
|
[data-bone] *,
|
|
@@ -490,7 +504,7 @@
|
|
|
490
504
|
select *,
|
|
491
505
|
object *
|
|
492
506
|
)::before,
|
|
493
|
-
[aria-busy="true"]
|
|
507
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
494
508
|
:not(:has(*)):not(
|
|
495
509
|
[data-bone],
|
|
496
510
|
[data-bone] *,
|
|
@@ -519,7 +533,7 @@
|
|
|
519
533
|
select *,
|
|
520
534
|
object *
|
|
521
535
|
)::after,
|
|
522
|
-
[aria-busy="true"]
|
|
536
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
523
537
|
:is(
|
|
524
538
|
img,
|
|
525
539
|
svg,
|
|
@@ -544,7 +558,7 @@
|
|
|
544
558
|
}
|
|
545
559
|
|
|
546
560
|
@media (forced-colors: active) {
|
|
547
|
-
[aria-busy="true"]
|
|
561
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
548
562
|
:not(:has(*)):not(
|
|
549
563
|
[data-bone],
|
|
550
564
|
[data-bone] *,
|
|
@@ -573,7 +587,7 @@
|
|
|
573
587
|
select *,
|
|
574
588
|
object *
|
|
575
589
|
)::after,
|
|
576
|
-
[aria-busy="true"]
|
|
590
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
577
591
|
:is(
|
|
578
592
|
img,
|
|
579
593
|
svg,
|
|
@@ -598,7 +612,7 @@
|
|
|
598
612
|
}
|
|
599
613
|
|
|
600
614
|
@scope ([data-bone-animate="pulse"]) to ([data-bone-animate]:not([data-bone-animate="pulse"])) {
|
|
601
|
-
[aria-busy="true"]
|
|
615
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
602
616
|
:not(:has(*)):not(
|
|
603
617
|
[data-bone],
|
|
604
618
|
[data-bone] *,
|
|
@@ -627,7 +641,7 @@
|
|
|
627
641
|
select *,
|
|
628
642
|
object *
|
|
629
643
|
)::before,
|
|
630
|
-
[aria-busy="true"]
|
|
644
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
631
645
|
:not(:has(*)):not(
|
|
632
646
|
[data-bone],
|
|
633
647
|
[data-bone] *,
|
|
@@ -656,7 +670,7 @@
|
|
|
656
670
|
select *,
|
|
657
671
|
object *
|
|
658
672
|
)::after,
|
|
659
|
-
[aria-busy="true"]
|
|
673
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
660
674
|
:is(
|
|
661
675
|
img,
|
|
662
676
|
svg,
|
|
@@ -680,7 +694,7 @@
|
|
|
680
694
|
}
|
|
681
695
|
|
|
682
696
|
@media (prefers-reduced-motion: reduce) {
|
|
683
|
-
[aria-busy="true"]
|
|
697
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
684
698
|
:not(:has(*)):not(
|
|
685
699
|
[data-bone],
|
|
686
700
|
[data-bone] *,
|
|
@@ -709,7 +723,7 @@
|
|
|
709
723
|
select *,
|
|
710
724
|
object *
|
|
711
725
|
)::before,
|
|
712
|
-
[aria-busy="true"]
|
|
726
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
713
727
|
:not(:has(*)):not(
|
|
714
728
|
[data-bone],
|
|
715
729
|
[data-bone] *,
|
|
@@ -738,7 +752,7 @@
|
|
|
738
752
|
select *,
|
|
739
753
|
object *
|
|
740
754
|
)::after,
|
|
741
|
-
[aria-busy="true"]
|
|
755
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
742
756
|
:is(
|
|
743
757
|
img,
|
|
744
758
|
svg,
|
|
@@ -763,7 +777,7 @@
|
|
|
763
777
|
}
|
|
764
778
|
|
|
765
779
|
@media (forced-colors: active) {
|
|
766
|
-
[aria-busy="true"]
|
|
780
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
767
781
|
:not(:has(*)):not(
|
|
768
782
|
[data-bone],
|
|
769
783
|
[data-bone] *,
|
|
@@ -792,7 +806,7 @@
|
|
|
792
806
|
select *,
|
|
793
807
|
object *
|
|
794
808
|
)::after,
|
|
795
|
-
[aria-busy="true"]
|
|
809
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
796
810
|
:is(
|
|
797
811
|
img,
|
|
798
812
|
svg,
|
|
@@ -817,7 +831,7 @@
|
|
|
817
831
|
}
|
|
818
832
|
|
|
819
833
|
@scope ([data-bone-animate="none"]) to ([data-bone-animate]:not([data-bone-animate="none"])) {
|
|
820
|
-
[aria-busy="true"]
|
|
834
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
821
835
|
:not(:has(*)):not(
|
|
822
836
|
[data-bone],
|
|
823
837
|
[data-bone] *,
|
|
@@ -846,7 +860,7 @@
|
|
|
846
860
|
select *,
|
|
847
861
|
object *
|
|
848
862
|
)::before,
|
|
849
|
-
[aria-busy="true"]
|
|
863
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
850
864
|
:not(:has(*)):not(
|
|
851
865
|
[data-bone],
|
|
852
866
|
[data-bone] *,
|
|
@@ -875,7 +889,7 @@
|
|
|
875
889
|
select *,
|
|
876
890
|
object *
|
|
877
891
|
)::after,
|
|
878
|
-
[aria-busy="true"]
|
|
892
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
879
893
|
:is(
|
|
880
894
|
img,
|
|
881
895
|
svg,
|
|
@@ -899,7 +913,7 @@
|
|
|
899
913
|
}
|
|
900
914
|
|
|
901
915
|
@media (forced-colors: active) {
|
|
902
|
-
[aria-busy="true"]
|
|
916
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
903
917
|
:not(:has(*)):not(
|
|
904
918
|
[data-bone],
|
|
905
919
|
[data-bone] *,
|
|
@@ -928,7 +942,7 @@
|
|
|
928
942
|
select *,
|
|
929
943
|
object *
|
|
930
944
|
)::after,
|
|
931
|
-
[aria-busy="true"]
|
|
945
|
+
[aria-busy="true"]:is(:scope, :scope *)
|
|
932
946
|
:is(
|
|
933
947
|
img,
|
|
934
948
|
svg,
|
package/src/css/bones.css
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/* Bones derive their color from the text color in effect at the bone, so
|
|
3
|
+
they inherit the page's own contrast guarantee on any background in any
|
|
4
|
+
scheme (BON-13). currentColor resolves where var() is used, not here.
|
|
5
|
+
The alpha reset matters: bones hide their content by zeroing the alpha
|
|
6
|
+
of the color property (never with the transparent keyword, which would
|
|
7
|
+
discard the channels), and the mix restores it. */
|
|
8
|
+
--bone-base: color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent);
|
|
9
|
+
--bone-highlight: color-mix(in srgb, rgb(from currentcolor r g b / 1) 6%, transparent);
|
|
4
10
|
--bone-radius: 4px;
|
|
5
11
|
--bone-duration: 1.5s;
|
|
6
12
|
}
|
|
7
13
|
|
|
8
|
-
@media (prefers-color-scheme: dark) {
|
|
9
|
-
:root {
|
|
10
|
-
--bone-base: rgba(255, 255, 255, 0.12);
|
|
11
|
-
--bone-highlight: rgba(255, 255, 255, 0.06);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
14
|
[data-bone="text"] {
|
|
16
|
-
color:
|
|
15
|
+
color: rgb(from currentcolor r g b / 0);
|
|
17
16
|
position: relative;
|
|
18
17
|
min-width: 4ch;
|
|
19
18
|
min-height: 1lh;
|
|
@@ -71,8 +70,15 @@
|
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
img[data-bone="block"],
|
|
74
|
-
video[data-bone="block"]
|
|
75
|
-
|
|
73
|
+
video[data-bone="block"],
|
|
74
|
+
canvas[data-bone="block"] {
|
|
75
|
+
color: rgb(from currentcolor r g b / 0);
|
|
76
|
+
/* A loaded picture paints over the bone's background-color; pushing it out
|
|
77
|
+
of the content box keeps the box and background visible (BON-15).
|
|
78
|
+
transition: none keeps a page `transition: all` from sliding the picture
|
|
79
|
+
out on the way in; see the matching comment in auto.css. */
|
|
80
|
+
object-position: 9999px 0;
|
|
81
|
+
transition: none;
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
[data-bone="container"] {
|
|
@@ -111,11 +117,43 @@ video[data-bone="block"] {
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
|
|
114
|
-
|
|
120
|
+
/* Marked bones shimmer by default, the same as auto bones and the measured
|
|
121
|
+
overlay (BON-16). data-bone-animate="none" is the opt-out; the @scope
|
|
122
|
+
blocks below override this at higher specificity, so their order relative
|
|
123
|
+
to this rule does not matter. */
|
|
124
|
+
[data-bone="text"]::after,
|
|
125
|
+
[data-bone="block"],
|
|
126
|
+
[data-bone="container"]::before {
|
|
127
|
+
animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
|
|
128
|
+
background: linear-gradient(
|
|
129
|
+
90deg,
|
|
130
|
+
var(--bone-base) 25%,
|
|
131
|
+
var(--bone-highlight) 50%,
|
|
132
|
+
var(--bone-base) 75%
|
|
133
|
+
);
|
|
134
|
+
background-size: 200% 100%;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@media (prefers-reduced-motion: reduce) {
|
|
115
138
|
[data-bone="text"]::after,
|
|
116
|
-
[data-bone="text"]::before,
|
|
117
139
|
[data-bone="block"],
|
|
118
140
|
[data-bone="container"]::before {
|
|
141
|
+
animation: bone-pulse 2s ease-in-out infinite;
|
|
142
|
+
background: var(--bone-base);
|
|
143
|
+
background-size: auto;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* :is(:scope, :scope *) lets the attribute work from the bone itself, not
|
|
148
|
+
only from a wrapper: a scoped selector's implicit :scope prefix matches
|
|
149
|
+
strict descendants only, so a plain compound never matches the scope root
|
|
150
|
+
(BON-17). The reduced-motion override sits inside each animated scope so it
|
|
151
|
+
matches at the scope's own specificity; "none" has no override because none
|
|
152
|
+
still means none. */
|
|
153
|
+
@scope ([data-bone-animate="shimmer"]) to ([data-bone-animate]:not([data-bone-animate="shimmer"])) {
|
|
154
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
155
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
156
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
119
157
|
animation: bone-shimmer var(--bone-duration) ease-in-out infinite;
|
|
120
158
|
background: linear-gradient(
|
|
121
159
|
90deg,
|
|
@@ -125,32 +163,42 @@ video[data-bone="block"] {
|
|
|
125
163
|
);
|
|
126
164
|
background-size: 200% 100%;
|
|
127
165
|
}
|
|
166
|
+
|
|
167
|
+
@media (prefers-reduced-motion: reduce) {
|
|
168
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
169
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
170
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
171
|
+
animation: bone-pulse 2s ease-in-out infinite;
|
|
172
|
+
background: var(--bone-base);
|
|
173
|
+
background-size: auto;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
128
176
|
}
|
|
129
177
|
|
|
130
178
|
@scope ([data-bone-animate="pulse"]) to ([data-bone-animate]:not([data-bone-animate="pulse"])) {
|
|
131
|
-
[data-bone="text"]::after,
|
|
132
|
-
[data-bone="
|
|
133
|
-
[data-bone="
|
|
134
|
-
[data-bone="container"]::before {
|
|
179
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
180
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
181
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
135
182
|
animation: bone-pulse var(--bone-duration) ease-in-out infinite;
|
|
136
183
|
background: var(--bone-base);
|
|
137
184
|
background-size: auto;
|
|
138
185
|
}
|
|
186
|
+
|
|
187
|
+
@media (prefers-reduced-motion: reduce) {
|
|
188
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
189
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
190
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
191
|
+
animation: bone-pulse 2s ease-in-out infinite;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
139
194
|
}
|
|
140
195
|
|
|
141
196
|
@scope ([data-bone-animate="none"]) to ([data-bone-animate]:not([data-bone-animate="none"])) {
|
|
142
|
-
[data-bone="text"]::after,
|
|
143
|
-
[data-bone="
|
|
144
|
-
[data-bone="
|
|
145
|
-
[data-bone="container"]::before {
|
|
197
|
+
[data-bone="text"]:is(:scope, :scope *)::after,
|
|
198
|
+
[data-bone="block"]:is(:scope, :scope *),
|
|
199
|
+
[data-bone="container"]:is(:scope, :scope *)::before {
|
|
146
200
|
animation: none;
|
|
147
201
|
background: var(--bone-base);
|
|
148
202
|
background-size: auto;
|
|
149
203
|
}
|
|
150
204
|
}
|
|
151
|
-
|
|
152
|
-
@media (prefers-reduced-motion: reduce) {
|
|
153
|
-
[aria-busy="true"] {
|
|
154
|
-
animation: bone-pulse 2s ease-in-out infinite;
|
|
155
|
-
}
|
|
156
|
-
}
|
package/dist/react/bones.d.mts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
2
|
-
|
|
3
|
-
//#region src/react/bones.d.ts
|
|
4
|
-
declare function BonesForce({
|
|
5
|
-
children
|
|
6
|
-
}: {
|
|
7
|
-
children: ReactNode;
|
|
8
|
-
}): ReactNode;
|
|
9
|
-
declare function Bones({
|
|
10
|
-
children
|
|
11
|
-
}: {
|
|
12
|
-
children: ReactNode;
|
|
13
|
-
}): ReactNode;
|
|
14
|
-
//#endregion
|
|
15
|
-
export { Bones, BonesForce };
|
package/dist/react/bones.mjs
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { forceBones, getBonesContext } from "./create-bones.mjs";
|
|
2
|
-
import { Children, Fragment, Suspense, cloneElement, createElement, isValidElement } from "react";
|
|
3
|
-
//#region src/react/bones.ts
|
|
4
|
-
function BonesStart() {
|
|
5
|
-
getBonesContext().loading = true;
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
function BonesEnd() {
|
|
9
|
-
getBonesContext().loading = false;
|
|
10
|
-
return null;
|
|
11
|
-
}
|
|
12
|
-
function swapPromises(children) {
|
|
13
|
-
return Children.map(children, (child) => {
|
|
14
|
-
if (!isValidElement(child)) return child;
|
|
15
|
-
const props = {};
|
|
16
|
-
for (const [key, value] of Object.entries(child.props)) props[key] = value instanceof Promise ? forceBones : value;
|
|
17
|
-
return cloneElement(child, props);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
function BonesForce({ children }) {
|
|
21
|
-
return createElement(Fragment, null, createElement(BonesStart), children, createElement(BonesEnd));
|
|
22
|
-
}
|
|
23
|
-
function Bones({ children }) {
|
|
24
|
-
return createElement(Suspense, { fallback: createElement(Fragment, null, createElement(BonesStart), swapPromises(children), createElement(BonesEnd)) }, children);
|
|
25
|
-
}
|
|
26
|
-
//#endregion
|
|
27
|
-
export { Bones, BonesForce };
|