polite-media 0.4.2 → 0.4.3

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/CHANGELOG.md CHANGED
@@ -3,6 +3,27 @@
3
3
  Notable changes, newest first. Versions follow [semver](https://semver.org); while
4
4
  this is `0.x`, a minor bump may still break things and will say so here.
5
5
 
6
+ ## 0.4.3 (2026-09-06)
7
+
8
+ ### Fixed
9
+
10
+ - **An image below the fold arrived without its fade.** The stylesheet's failsafe reveals a marked
11
+ image after `--polite-failsafe` (default `5s`) whatever JavaScript does, and it was firing on
12
+ images `revealImages()` had already claimed. A lazy image that had not loaded yet reached
13
+ `opacity: 1` while still in flight, so when the picture finally arrived there was nothing left to
14
+ fade. Measured on a live page: eleven images revealed that way five seconds in, none of them
15
+ loaded.
16
+
17
+ The library now marks an image it owns with `data-polite-managed` and the stylesheet stands down
18
+ for those. A claimed image is safe without the failsafe because the module always resolves one, on
19
+ `decode()` and failing that on `load` or `error`; if a teardown gives up on a claim, the mark is
20
+ removed and the stylesheet takes the image back.
21
+
22
+ ### Added
23
+
24
+ - **`data-polite-managed` on an `<img>`**, for as long as `revealImages()` owns its reveal. Part of
25
+ the public CSS API like the other attributes the library writes.
26
+
6
27
  ## 0.4.2 (2026-09-06)
7
28
 
8
29
  ### Fixed
package/README.md CHANGED
@@ -143,7 +143,7 @@ warmOnIntent('a[data-hero]', (link) => ({
143
143
 
144
144
  ## The attributes
145
145
 
146
- Seven in total. The distinction that catches people is the middle column: two of
146
+ Eight in total. The distinction that catches people is the middle column: two of
147
147
  the ones you write are live on their own, and one is inert until you call
148
148
  something.
149
149
 
@@ -156,6 +156,7 @@ something.
156
156
  | `data-polite-failed` | the box | written by the library |
157
157
  | `data-polite-paused` | `<html>` | written by the library |
158
158
  | `data-polite-active` | `<html>` | written by the library |
159
+ | `data-polite-managed` | the `<img>` | written by the library |
159
160
 
160
161
  `data-polite-reveal` is the one to be careful with. `image.css` hides a marked
161
162
  image immediately, so marking one you never pass to `revealImages()` leaves it
@@ -176,7 +177,7 @@ stop something that was never registered, and nothing else lets CSS answer that:
176
177
  }
177
178
  ```
178
179
 
179
- The bottom four are yours to style against and never to write yourself. They are
180
+ The bottom five are yours to style against and never to write yourself. They are
180
181
  the public CSS API, along with `--polite-fade` and `--polite-failsafe`.
181
182
 
182
183
  ### Composing your own animation with the reveal
@@ -653,9 +654,23 @@ so with scripting off the photos arrive unfaded instead of never arriving. And
653
654
  because no media query can see a bundle that fails while scripting is on, the
654
655
  stylesheet reveals any marked image after `--polite-failsafe` (default `5s`)
655
656
  regardless. A missed selector or a dead bundle costs you the fade, not the
656
- picture. The failsafe applies to every marked image, including managed ones,
657
- because an earlier design that exempted them could send an already-revealed image
658
- back to hidden.
657
+ picture.
658
+
659
+ **The failsafe is only for images nothing is going to reveal.** It stands down
660
+ for two kinds: one already revealed, and one `revealImages()` has claimed, which
661
+ the library marks with `data-polite-managed`. Both matter.
662
+
663
+ Leaving it on a revealed image let it, not the reveal, decide `opacity`, because
664
+ an animation outranks every normal declaration in the cascade; Firefox then held
665
+ a decoded image at zero for the full five seconds. Leaving it on a claimed one is
666
+ worse and quieter: a lazy image below the fold reaches `opacity: 1` while it is
667
+ still in flight, so when the picture finally arrives there is no fade left to run.
668
+ Measured on a live page, eleven images were revealed that way five seconds in,
669
+ none of them loaded.
670
+
671
+ A claimed image is safe without it because this module always resolves one: on
672
+ `decode()`, and failing that on `load` or `error`. If a teardown gives up on a
673
+ claim, the mark is removed and the stylesheet takes the image back.
659
674
 
660
675
  ## Why it exists
661
676
 
package/dist/image.css CHANGED
@@ -1 +1 @@
1
- @media(scripting:enabled){img[data-polite-reveal]{opacity:0;transition:opacity var(--polite-fade, .35s) ease}img[data-polite-reveal]:not([data-polite-ready]){animation:polite-reveal-failsafe 0s var(--polite-failsafe, 5s) forwards}img[data-polite-reveal][data-polite-ready]{opacity:1}@keyframes polite-reveal-failsafe{to{opacity:1}}}@media(prefers-reduced-motion:reduce){img[data-polite-reveal]{transition:none}}
1
+ @media(scripting:enabled){img[data-polite-reveal]{opacity:0;transition:opacity var(--polite-fade, .35s) ease}img[data-polite-reveal]:not([data-polite-ready]):not([data-polite-managed]){animation:polite-reveal-failsafe 0s var(--polite-failsafe, 5s) forwards}img[data-polite-reveal][data-polite-ready]{opacity:1}@keyframes polite-reveal-failsafe{to{opacity:1}}}@media(prefers-reduced-motion:reduce){img[data-polite-reveal]{transition:none}}
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3D,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA6FD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,IAAI,CA0D/F;AAKD,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3D,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAEnD,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;OAYG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AA+FD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,IAAI,CA2E/F;AAKD,OAAO,EAAE,kBAAkB,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC"}
package/dist/image.js CHANGED
@@ -18,6 +18,8 @@
18
18
  import { POLITE_IMAGE_READY } from './events.js';
19
19
  import { resolveTargets } from './targets.js';
20
20
  const READY = 'data-polite-ready';
21
+ /** Written while this module owns an image's reveal, so image.css can stand down. */
22
+ const MANAGED = 'data-polite-managed';
21
23
  function markReady(image) {
22
24
  image.setAttribute(READY, '');
23
25
  image.dispatchEvent(new CustomEvent(POLITE_IMAGE_READY, {
@@ -104,8 +106,16 @@ export function revealImages(target, options = {}) {
104
106
  const controller = new AbortController();
105
107
  const { signal } = controller;
106
108
  scheduleUnmanagedCheck();
109
+ const claimed = [];
107
110
  for (const image of resolveTargets(target)) {
108
111
  managed.add(image);
112
+ // Tells the stylesheet to leave this one alone. The failsafe there is for images nothing
113
+ // reveals, and firing it on one this module owns is worse than not firing it at all: opacity
114
+ // reaches 1 while the picture is still in flight, so when it arrives there is no fade left to
115
+ // run. Measured on a real page, eleven below-the-fold images were revealed that way five
116
+ // seconds in, none of them loaded.
117
+ image.setAttribute(MANAGED, '');
118
+ claimed.push(image);
109
119
  // Eager images are revealed at once rather than skipped.
110
120
  //
111
121
  // Skipping looks like the cautious choice and is the opposite: image.css
@@ -155,7 +165,16 @@ export function revealImages(target, options = {}) {
155
165
  image.addEventListener('error', () => markReady(image), { once: true, signal });
156
166
  });
157
167
  }
158
- return () => controller.abort();
168
+ // Hands anything still unrevealed back to the stylesheet. A router tearing the page down before
169
+ // the images resolved would otherwise leave them owned by a module that has stopped listening,
170
+ // and with the failsafe suppressed that is hidden forever rather than merely unfaded.
171
+ return () => {
172
+ controller.abort();
173
+ for (const image of claimed) {
174
+ if (!image.hasAttribute(READY))
175
+ image.removeAttribute(MANAGED);
176
+ }
177
+ };
159
178
  }
160
179
  // Re-exported so `polite-media/image` carries the ElementEventMap and
161
180
  // DocumentEventMap augmentation too: it only reaches a consumer whose program
package/dist/image.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,kBAAkB,EAA+B,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAe,MAAM,cAAc,CAAC;AAsB3D,MAAM,KAAK,GAAG,mBAAmB,CAAC;AAElC,SAAS,SAAS,CAAC,KAAuB;IACxC,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC9B,KAAK,CAAC,aAAa,CACjB,IAAI,WAAW,CAAyB,kBAAkB,EAAE;QAC1D,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,EAAE,KAAK,EAAE;KAClB,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,GAAG,IAAI,OAAO,EAAoB,CAAC;AAEhD,IAAI,cAAyD,CAAC;AAE9D;;;;;;;;;;GAUG;AACH,SAAS,sBAAsB;IAC7B,YAAY,CAAC,cAAc,CAAC,CAAC;IAC7B,cAAc,GAAG,UAAU,CAAC,GAAG,EAAE;QAC/B,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,gBAAgB,CAAmB,yBAAyB,CAAC,EAAE,CAAC;YAC3F,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YACjC,OAAO,CAAC,IAAI,CACV,oFAAoF;gBAClF,sEAAsE,EACxE,KAAK,CACN,CAAC;YACF,OAAO;QACT,CAAC;QACD,cAAc,EAAE,CAAC;IACnB,CAAC,EAAE,IAAI,CAAC,CAAC;AACX,CAAC;AAED,IAAI,kBAAkB,GAAG,KAAK,CAAC;AAE/B;;;;;;;;;;;;;;;GAeG;AACH,SAAS,cAAc;IACrB,IAAI,kBAAkB;QAAE,OAAO;IAE/B,+FAA+F;IAC/F,iFAAiF;IACjF,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC5C,KAAK,CAAC,YAAY,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;IAC7C,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;IAChD,KAAK,CAAC,MAAM,EAAE,CAAC;IAEf,kGAAkG;IAClG,gGAAgG;IAChG,+DAA+D;IAC/D,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO;IAE5B,kBAAkB,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,IAAI,CACV,0FAA0F;QACxF,+FAA+F,CAClG,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAmB,EAAE,UAA+B,EAAE;IACjF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IAE9B,sBAAsB,EAAE,CAAC;IAEzB,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,yDAAyD;QACzD,EAAE;QACF,yEAAyE;QACzE,2EAA2E;QAC3E,uEAAuE;QACvE,uEAAuE;QACvE,yEAAyE;QACzE,EAAE;QACF,sEAAsE;QACtE,yEAAyE;QACzE,2EAA2E;QAC3E,kEAAkE;QAClE,yEAAyE;QACzE,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM,EAAE,CAAC;YACpD,SAAS,CAAC,KAAK,CAAC,CAAC;YACjB,SAAS;QACX,CAAC;QAED,0EAA0E;QAC1E,6EAA6E;QAC7E,sEAAsE;QACtE,6CAA6C;QAC7C,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;YAC7C,qBAAqB,CAAC,GAAG,EAAE;gBACzB,IAAI,CAAC,MAAM,CAAC,OAAO;oBAAE,SAAS,CAAC,KAAK,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,KAAK;aACF,MAAM,EAAE;aACR,IAAI,CAAC,GAAG,EAAE;YACT,IAAI,CAAC,MAAM,CAAC,OAAO;gBAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC,CAAC;aACD,KAAK,CAAC,GAAG,EAAE;YACV,qEAAqE;YACrE,sEAAsE;YACtE,uEAAuE;YACvE,yEAAyE;YACzE,IAAI,MAAM,CAAC,OAAO;gBAAE,OAAO;YAC3B,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,SAAS,CAAC,KAAK,CAAC,CAAC;gBACjB,OAAO;YACT,CAAC;YACD,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;YAC/E,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAClF,CAAC,CAAC,CAAC;IACP,CAAC;IAED,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;AAClC,CAAC;AAED,sEAAsE;AACtE,8EAA8E;AAC9E,wCAAwC;AACxC,OAAO,EAAE,kBAAkB,EAA+B,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polite-media",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Background video, image reveals and next-page image warming. Reveals on the frame that actually painted, plays only what is on screen, respects data and motion.",
5
5
  "keywords": [
6
6
  "video",
package/src/image.css CHANGED
@@ -54,7 +54,7 @@
54
54
  * Eager images hid the bug, because they are marked ready inside the same task
55
55
  * that applies the stylesheet, before this can take effect.
56
56
  */
57
- img[data-polite-reveal]:not([data-polite-ready]) {
57
+ img[data-polite-reveal]:not([data-polite-ready]):not([data-polite-managed]) {
58
58
  animation: polite-reveal-failsafe 0s var(--polite-failsafe, 5s) forwards;
59
59
  }
60
60
 
package/src/image.ts CHANGED
@@ -40,6 +40,8 @@ export interface RevealImagesOptions {
40
40
  }
41
41
 
42
42
  const READY = 'data-polite-ready';
43
+ /** Written while this module owns an image's reveal, so image.css can stand down. */
44
+ const MANAGED = 'data-polite-managed';
43
45
 
44
46
  function markReady(image: HTMLImageElement): void {
45
47
  image.setAttribute(READY, '');
@@ -142,8 +144,17 @@ export function revealImages(target: ImageTarget, options: RevealImagesOptions =
142
144
 
143
145
  scheduleUnmanagedCheck();
144
146
 
147
+ const claimed: HTMLImageElement[] = [];
148
+
145
149
  for (const image of resolveTargets(target)) {
146
150
  managed.add(image);
151
+ // Tells the stylesheet to leave this one alone. The failsafe there is for images nothing
152
+ // reveals, and firing it on one this module owns is worse than not firing it at all: opacity
153
+ // reaches 1 while the picture is still in flight, so when it arrives there is no fade left to
154
+ // run. Measured on a real page, eleven below-the-fold images were revealed that way five
155
+ // seconds in, none of them loaded.
156
+ image.setAttribute(MANAGED, '');
157
+ claimed.push(image);
147
158
  // Eager images are revealed at once rather than skipped.
148
159
  //
149
160
  // Skipping looks like the cautious choice and is the opposite: image.css
@@ -193,7 +204,15 @@ export function revealImages(target: ImageTarget, options: RevealImagesOptions =
193
204
  });
194
205
  }
195
206
 
196
- return () => controller.abort();
207
+ // Hands anything still unrevealed back to the stylesheet. A router tearing the page down before
208
+ // the images resolved would otherwise leave them owned by a module that has stopped listening,
209
+ // and with the failsafe suppressed that is hidden forever rather than merely unfaded.
210
+ return () => {
211
+ controller.abort();
212
+ for (const image of claimed) {
213
+ if (!image.hasAttribute(READY)) image.removeAttribute(MANAGED);
214
+ }
215
+ };
197
216
  }
198
217
 
199
218
  // Re-exported so `polite-media/image` carries the ElementEventMap and