@threadlabs/looma 0.39.0 → 0.39.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.
@@ -1,4 +1,4 @@
1
- <template component="ui-skeleton">
1
+ <template component="ui-skeleton" module="./ui-skeleton.js">
2
2
  <defs>
3
3
  <prop name="shape" type="keyword" values="text, circle, rectangle" default="text"
4
4
  >The content's silhouette. text reserves one inherited line box with a shorter bar inside it; circle matches a medium Avatar; rectangle reserves a media
@@ -8,8 +8,12 @@
8
8
  Avatar's outer size for circles.</prop>
9
9
  <prop name="height" type="string" default=""
10
10
  >Block size as a CSS length. Empty reserves one line for text, a square for circles, or 8rem for rectangles. Set only width to keep a circle square.</prop>
11
- <prop name="animated" type="boolean" default="false"
12
- >Gently pulses the placeholder. Off by default, and always still when the reader requests reduced motion.</prop>
11
+ <prop
12
+ name="animated"
13
+ type="boolean"
14
+ default="true"
15
+ default-true-reason="A travelling highlight identifies pending content by default; reduced-motion preferences always keep it static."
16
+ >Moves a subtle highlight from left to right. On by default; pass false to keep it static. Reduced-motion preferences always disable the shimmer.</prop>
13
17
  </defs>
14
18
  <span aria-hidden="true" style:--ui-skeleton-width="$width" style:--ui-skeleton-height="$height"></span>
15
19
  <style>
@@ -22,8 +26,20 @@
22
26
  syntax: "*";
23
27
  inherits: false;
24
28
  }
29
+ @property --_ui-skeleton-viewport-x {
30
+ syntax: "<length>";
31
+ inherits: false;
32
+ initial-value: 0px;
33
+ }
34
+ @property --_ui-skeleton-viewport-scale {
35
+ syntax: "<number>";
36
+ inherits: false;
37
+ initial-value: 1;
38
+ }
25
39
 
26
40
  :host {
41
+ --_ui-skeleton-paint-x: var(--_ui-skeleton-viewport-x);
42
+ --_ui-skeleton-paint-scale: var(--_ui-skeleton-viewport-scale);
27
43
  display: block !important;
28
44
  position: relative;
29
45
  box-sizing: border-box !important;
@@ -65,23 +81,36 @@
65
81
  }
66
82
 
67
83
  :host([animated])::before {
68
- animation: ui-skeleton-pulse 1.6s ease-in-out infinite;
84
+ background-image: linear-gradient(
85
+ 90deg,
86
+ transparent,
87
+ light-dark(rgb(255 255 255 / 0.6), rgb(255 255 255 / 0.14)) 50%,
88
+ transparent
89
+ );
90
+ background-size: calc(100vw / var(--_ui-skeleton-paint-scale)) 100%;
91
+ background-position: calc(0vw - var(--_ui-skeleton-paint-x)) 0;
92
+ background-repeat: repeat-x;
93
+ animation: ui-skeleton-shimmer 3s linear infinite;
69
94
  }
70
- @keyframes ui-skeleton-pulse {
71
- 50% {
72
- opacity: 0.45;
95
+ @keyframes ui-skeleton-shimmer {
96
+ to {
97
+ background-position: calc(100vw / var(--_ui-skeleton-paint-scale) - var(--_ui-skeleton-paint-x)) 0;
73
98
  }
74
99
  }
75
100
  @media (prefers-reduced-motion: reduce) {
76
101
  :host([animated])::before {
77
102
  animation: none;
103
+ background-image: none;
78
104
  }
79
105
  }
80
106
  @media (forced-colors: active) {
81
107
  :host::before {
82
108
  background: CanvasText;
83
109
  opacity: 0.2;
110
+ }
111
+ :host([animated])::before {
84
112
  animation: none;
113
+ background-image: none;
85
114
  }
86
115
  }
87
116
  :host[hidden] {
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Keep one viewport gradient aligned through mounting, scrolling and transformed surfaces.
3
+ * @lifecycle CSS owns motion preferences; geometry tracking stops with the animation or disconnect.
4
+ */
5
+ export function connect(host) {
6
+ const element = host.element;
7
+ const view = element.ownerDocument.defaultView;
8
+ let motion;
9
+ let frame = 0;
10
+ let previousPosition = "";
11
+ let previousScale = "";
12
+ const trackPosition = () => {
13
+ frame = 0;
14
+ if (motion?.playState !== "running") return;
15
+ const bounds = element.getBoundingClientRect();
16
+ const width = parseFloat(view.getComputedStyle(element).width);
17
+ const scale = bounds.width > 0 && width > 0 ? bounds.width / width : 1;
18
+ const position = `${bounds.left / scale}px`;
19
+ const scaleValue = String(scale);
20
+ if (position !== previousPosition) {
21
+ element.style.setProperty("--_ui-skeleton-viewport-x", position);
22
+ previousPosition = position;
23
+ }
24
+ if (scaleValue !== previousScale) {
25
+ element.style.setProperty("--_ui-skeleton-viewport-scale", scaleValue);
26
+ previousScale = scaleValue;
27
+ }
28
+ frame = view.requestAnimationFrame(trackPosition);
29
+ };
30
+ const synchronize = () => {
31
+ const names = view.getComputedStyle(element, "::before").animationName.split(",").map(name => name.trim());
32
+ for (const animation of element.getAnimations({ subtree: true })) {
33
+ if (names.includes(animation.animationName)) {
34
+ motion = animation;
35
+ motion.startTime = 0;
36
+ }
37
+ }
38
+ if (!frame) trackPosition();
39
+ };
40
+ element.addEventListener("animationstart", synchronize);
41
+ synchronize();
42
+ return () => {
43
+ element.removeEventListener("animationstart", synchronize);
44
+ view.cancelAnimationFrame(frame);
45
+ };
46
+ }