@jomae/catalog 0.3.0 → 0.6.0

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.
Files changed (257) hide show
  1. package/README.md +21 -3
  2. package/dist/baseline.d.ts +36 -0
  3. package/dist/baseline.d.ts.map +1 -1
  4. package/dist/baseline.js +75 -2
  5. package/dist/baseline.js.map +1 -1
  6. package/dist/categories.d.ts +20 -0
  7. package/dist/categories.d.ts.map +1 -0
  8. package/dist/categories.js +62 -0
  9. package/dist/categories.js.map +1 -0
  10. package/dist/detect.d.ts +2 -1
  11. package/dist/detect.d.ts.map +1 -1
  12. package/dist/detect.js +17 -2
  13. package/dist/detect.js.map +1 -1
  14. package/dist/format.js +1 -1
  15. package/dist/format.js.map +1 -1
  16. package/dist/generated/baseline-history.d.ts +21 -0
  17. package/dist/generated/baseline-history.d.ts.map +1 -0
  18. package/dist/generated/baseline-history.js +20 -0
  19. package/dist/generated/baseline-history.js.map +1 -0
  20. package/dist/generated/baseline.d.ts +16 -0
  21. package/dist/generated/baseline.d.ts.map +1 -1
  22. package/dist/generated/baseline.js +683 -44
  23. package/dist/generated/baseline.js.map +1 -1
  24. package/dist/generated/guides.d.ts +16 -0
  25. package/dist/generated/guides.d.ts.map +1 -0
  26. package/dist/generated/guides.js +153 -0
  27. package/dist/generated/guides.js.map +1 -0
  28. package/dist/generated/sizes.d.ts.map +1 -1
  29. package/dist/generated/sizes.js +683 -48
  30. package/dist/generated/sizes.js.map +1 -1
  31. package/dist/generated/support-claims.d.ts +15 -0
  32. package/dist/generated/support-claims.d.ts.map +1 -0
  33. package/dist/generated/support-claims.js +69 -0
  34. package/dist/generated/support-claims.js.map +1 -0
  35. package/dist/guides.d.ts +46 -0
  36. package/dist/guides.d.ts.map +1 -0
  37. package/dist/guides.js +55 -0
  38. package/dist/guides.js.map +1 -0
  39. package/dist/history.d.ts +11 -0
  40. package/dist/history.d.ts.map +1 -0
  41. package/dist/history.js +14 -0
  42. package/dist/history.js.map +1 -0
  43. package/dist/index.d.ts +5 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +5 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/rules/abort-controller.d.ts +3 -0
  48. package/dist/rules/abort-controller.d.ts.map +1 -0
  49. package/dist/rules/abort-controller.js +26 -0
  50. package/dist/rules/abort-controller.js.map +1 -0
  51. package/dist/rules/accordion.d.ts.map +1 -1
  52. package/dist/rules/accordion.js +3 -0
  53. package/dist/rules/accordion.js.map +1 -1
  54. package/dist/rules/array-grouping.d.ts +3 -0
  55. package/dist/rules/array-grouping.d.ts.map +1 -0
  56. package/dist/rules/array-grouping.js +29 -0
  57. package/dist/rules/array-grouping.js.map +1 -0
  58. package/dist/rules/aspect-ratio.d.ts.map +1 -1
  59. package/dist/rules/aspect-ratio.js +1 -0
  60. package/dist/rules/aspect-ratio.js.map +1 -1
  61. package/dist/rules/base64.d.ts +3 -0
  62. package/dist/rules/base64.d.ts.map +1 -0
  63. package/dist/rules/base64.js +41 -0
  64. package/dist/rules/base64.js.map +1 -0
  65. package/dist/rules/broadcast-channel.d.ts +3 -0
  66. package/dist/rules/broadcast-channel.d.ts.map +1 -0
  67. package/dist/rules/broadcast-channel.js +26 -0
  68. package/dist/rules/broadcast-channel.js.map +1 -0
  69. package/dist/rules/carousel.d.ts.map +1 -1
  70. package/dist/rules/carousel.js +10 -0
  71. package/dist/rules/carousel.js.map +1 -1
  72. package/dist/rules/clipboard.d.ts +3 -0
  73. package/dist/rules/clipboard.d.ts.map +1 -0
  74. package/dist/rules/clipboard.js +35 -0
  75. package/dist/rules/clipboard.js.map +1 -0
  76. package/dist/rules/color-functions.d.ts.map +1 -1
  77. package/dist/rules/color-functions.js +9 -1
  78. package/dist/rules/color-functions.js.map +1 -1
  79. package/dist/rules/compression-streams.d.ts +3 -0
  80. package/dist/rules/compression-streams.d.ts.map +1 -0
  81. package/dist/rules/compression-streams.js +26 -0
  82. package/dist/rules/compression-streams.js.map +1 -0
  83. package/dist/rules/container-queries.d.ts.map +1 -1
  84. package/dist/rules/container-queries.js +3 -0
  85. package/dist/rules/container-queries.js.map +1 -1
  86. package/dist/rules/content-visibility.d.ts.map +1 -1
  87. package/dist/rules/content-visibility.js +11 -2
  88. package/dist/rules/content-visibility.js.map +1 -1
  89. package/dist/rules/custom-highlight.d.ts +3 -0
  90. package/dist/rules/custom-highlight.d.ts.map +1 -0
  91. package/dist/rules/custom-highlight.js +39 -0
  92. package/dist/rules/custom-highlight.js.map +1 -0
  93. package/dist/rules/date-time-input.d.ts.map +1 -1
  94. package/dist/rules/date-time-input.js +4 -0
  95. package/dist/rules/date-time-input.js.map +1 -1
  96. package/dist/rules/dialog.d.ts.map +1 -1
  97. package/dist/rules/dialog.js +11 -0
  98. package/dist/rules/dialog.js.map +1 -1
  99. package/dist/rules/discrete-transitions.d.ts.map +1 -1
  100. package/dist/rules/discrete-transitions.js +6 -0
  101. package/dist/rules/discrete-transitions.js.map +1 -1
  102. package/dist/rules/drag-and-drop.d.ts +3 -0
  103. package/dist/rules/drag-and-drop.d.ts.map +1 -0
  104. package/dist/rules/drag-and-drop.js +47 -0
  105. package/dist/rules/drag-and-drop.js.map +1 -0
  106. package/dist/rules/duration-format.d.ts +3 -0
  107. package/dist/rules/duration-format.d.ts.map +1 -0
  108. package/dist/rules/duration-format.js +37 -0
  109. package/dist/rules/duration-format.js.map +1 -0
  110. package/dist/rules/event-target.d.ts +3 -0
  111. package/dist/rules/event-target.d.ts.map +1 -0
  112. package/dist/rules/event-target.js +51 -0
  113. package/dist/rules/event-target.js.map +1 -0
  114. package/dist/rules/fetch.d.ts +3 -0
  115. package/dist/rules/fetch.d.ts.map +1 -0
  116. package/dist/rules/fetch.js +46 -0
  117. package/dist/rules/fetch.js.map +1 -0
  118. package/dist/rules/field-sizing.d.ts.map +1 -1
  119. package/dist/rules/field-sizing.js +10 -2
  120. package/dist/rules/field-sizing.js.map +1 -1
  121. package/dist/rules/fluid-type.d.ts.map +1 -1
  122. package/dist/rules/fluid-type.js +3 -1
  123. package/dist/rules/fluid-type.js.map +1 -1
  124. package/dist/rules/focus-visible.d.ts +3 -0
  125. package/dist/rules/focus-visible.d.ts.map +1 -0
  126. package/dist/rules/focus-visible.js +27 -0
  127. package/dist/rules/focus-visible.js.map +1 -0
  128. package/dist/rules/fullscreen.d.ts +3 -0
  129. package/dist/rules/fullscreen.d.ts.map +1 -0
  130. package/dist/rules/fullscreen.js +29 -0
  131. package/dist/rules/fullscreen.js.map +1 -0
  132. package/dist/rules/height-animation.d.ts.map +1 -1
  133. package/dist/rules/height-animation.js +2 -0
  134. package/dist/rules/height-animation.js.map +1 -1
  135. package/dist/rules/index.d.ts.map +1 -1
  136. package/dist/rules/index.js +102 -3
  137. package/dist/rules/index.js.map +1 -1
  138. package/dist/rules/inert.d.ts +3 -0
  139. package/dist/rules/inert.d.ts.map +1 -0
  140. package/dist/rules/inert.js +37 -0
  141. package/dist/rules/inert.js.map +1 -0
  142. package/dist/rules/intersection-observer.d.ts +3 -0
  143. package/dist/rules/intersection-observer.d.ts.map +1 -0
  144. package/dist/rules/intersection-observer.js +36 -0
  145. package/dist/rules/intersection-observer.js.map +1 -0
  146. package/dist/rules/lazy-loading.d.ts.map +1 -1
  147. package/dist/rules/lazy-loading.js +4 -0
  148. package/dist/rules/lazy-loading.js.map +1 -1
  149. package/dist/rules/line-clamp.d.ts +3 -0
  150. package/dist/rules/line-clamp.d.ts.map +1 -0
  151. package/dist/rules/line-clamp.js +39 -0
  152. package/dist/rules/line-clamp.js.map +1 -0
  153. package/dist/rules/masonry.d.ts.map +1 -1
  154. package/dist/rules/masonry.js +3 -0
  155. package/dist/rules/masonry.js.map +1 -1
  156. package/dist/rules/natural-sort.d.ts +3 -0
  157. package/dist/rules/natural-sort.d.ts.map +1 -0
  158. package/dist/rules/natural-sort.js +41 -0
  159. package/dist/rules/natural-sort.js.map +1 -0
  160. package/dist/rules/number-format.d.ts +3 -0
  161. package/dist/rules/number-format.d.ts.map +1 -0
  162. package/dist/rules/number-format.js +27 -0
  163. package/dist/rules/number-format.js.map +1 -0
  164. package/dist/rules/page-visibility.d.ts +3 -0
  165. package/dist/rules/page-visibility.d.ts.map +1 -0
  166. package/dist/rules/page-visibility.js +29 -0
  167. package/dist/rules/page-visibility.js.map +1 -0
  168. package/dist/rules/popover.d.ts.map +1 -1
  169. package/dist/rules/popover.js +10 -1
  170. package/dist/rules/popover.js.map +1 -1
  171. package/dist/rules/random-uuid.d.ts +3 -0
  172. package/dist/rules/random-uuid.d.ts.map +1 -0
  173. package/dist/rules/random-uuid.js +31 -0
  174. package/dist/rules/random-uuid.js.map +1 -0
  175. package/dist/rules/relative-time.d.ts +3 -0
  176. package/dist/rules/relative-time.d.ts.map +1 -0
  177. package/dist/rules/relative-time.js +24 -0
  178. package/dist/rules/relative-time.js.map +1 -0
  179. package/dist/rules/resizable-panels.d.ts.map +1 -1
  180. package/dist/rules/resizable-panels.js +4 -2
  181. package/dist/rules/resizable-panels.js.map +1 -1
  182. package/dist/rules/resize-observer.d.ts +3 -0
  183. package/dist/rules/resize-observer.d.ts.map +1 -0
  184. package/dist/rules/resize-observer.js +28 -0
  185. package/dist/rules/resize-observer.js.map +1 -0
  186. package/dist/rules/screen-wake-lock.d.ts +3 -0
  187. package/dist/rules/screen-wake-lock.d.ts.map +1 -0
  188. package/dist/rules/screen-wake-lock.js +25 -0
  189. package/dist/rules/screen-wake-lock.js.map +1 -0
  190. package/dist/rules/scroll-animations.d.ts.map +1 -1
  191. package/dist/rules/scroll-animations.js +9 -1
  192. package/dist/rules/scroll-animations.js.map +1 -1
  193. package/dist/rules/scroll-lock.d.ts.map +1 -1
  194. package/dist/rules/scroll-lock.js +8 -7
  195. package/dist/rules/scroll-lock.js.map +1 -1
  196. package/dist/rules/scrollbars.d.ts.map +1 -1
  197. package/dist/rules/scrollbars.js +8 -0
  198. package/dist/rules/scrollbars.js.map +1 -1
  199. package/dist/rules/select.d.ts.map +1 -1
  200. package/dist/rules/select.js +17 -1
  201. package/dist/rules/select.js.map +1 -1
  202. package/dist/rules/server-sent-events.d.ts +3 -0
  203. package/dist/rules/server-sent-events.d.ts.map +1 -0
  204. package/dist/rules/server-sent-events.js +38 -0
  205. package/dist/rules/server-sent-events.js.map +1 -0
  206. package/dist/rules/smooth-scroll.d.ts.map +1 -1
  207. package/dist/rules/smooth-scroll.js +3 -0
  208. package/dist/rules/smooth-scroll.js.map +1 -1
  209. package/dist/rules/speech-recognition.d.ts +3 -0
  210. package/dist/rules/speech-recognition.d.ts.map +1 -0
  211. package/dist/rules/speech-recognition.js +36 -0
  212. package/dist/rules/speech-recognition.js.map +1 -0
  213. package/dist/rules/speech-synthesis.d.ts +3 -0
  214. package/dist/rules/speech-synthesis.d.ts.map +1 -0
  215. package/dist/rules/speech-synthesis.js +25 -0
  216. package/dist/rules/speech-synthesis.js.map +1 -0
  217. package/dist/rules/sticky.d.ts.map +1 -1
  218. package/dist/rules/sticky.js +4 -0
  219. package/dist/rules/sticky.js.map +1 -1
  220. package/dist/rules/structured-clone.d.ts +3 -0
  221. package/dist/rules/structured-clone.d.ts.map +1 -0
  222. package/dist/rules/structured-clone.js +32 -0
  223. package/dist/rules/structured-clone.js.map +1 -0
  224. package/dist/rules/text-box-trim.d.ts.map +1 -1
  225. package/dist/rules/text-box-trim.js +3 -1
  226. package/dist/rules/text-box-trim.js.map +1 -1
  227. package/dist/rules/text-wrap-balance.d.ts.map +1 -1
  228. package/dist/rules/text-wrap-balance.js +2 -0
  229. package/dist/rules/text-wrap-balance.js.map +1 -1
  230. package/dist/rules/url-search-params.d.ts +3 -0
  231. package/dist/rules/url-search-params.d.ts.map +1 -0
  232. package/dist/rules/url-search-params.js +42 -0
  233. package/dist/rules/url-search-params.js.map +1 -0
  234. package/dist/rules/view-transitions.d.ts.map +1 -1
  235. package/dist/rules/view-transitions.js +9 -0
  236. package/dist/rules/view-transitions.js.map +1 -1
  237. package/dist/rules/web-bluetooth.d.ts +3 -0
  238. package/dist/rules/web-bluetooth.d.ts.map +1 -0
  239. package/dist/rules/web-bluetooth.js +26 -0
  240. package/dist/rules/web-bluetooth.js.map +1 -0
  241. package/dist/rules/web-crypto.d.ts +3 -0
  242. package/dist/rules/web-crypto.d.ts.map +1 -0
  243. package/dist/rules/web-crypto.js +32 -0
  244. package/dist/rules/web-crypto.js.map +1 -0
  245. package/dist/rules/web-share.d.ts +3 -0
  246. package/dist/rules/web-share.d.ts.map +1 -0
  247. package/dist/rules/web-share.js +28 -0
  248. package/dist/rules/web-share.js.map +1 -0
  249. package/dist/schema.d.ts +22 -0
  250. package/dist/schema.d.ts.map +1 -1
  251. package/dist/schema.js +64 -5
  252. package/dist/schema.js.map +1 -1
  253. package/dist/support.d.ts +11 -0
  254. package/dist/support.d.ts.map +1 -0
  255. package/dist/support.js +31 -0
  256. package/dist/support.js.map +1 -0
  257. package/package.json +2 -2
@@ -0,0 +1,38 @@
1
+ export const serverSentEvents = {
2
+ id: "server-sent-events",
3
+ title: "Server-sent events",
4
+ category: "async-data",
5
+ replaces: ["eventsource", "event-source-polyfill"],
6
+ featureIds: ["server-sent-events"],
7
+ native: "EventSource",
8
+ human: {
9
+ explainer: "EventSource opens a one-way stream from the server and hands you each message as an event. The browser owns the reconnection: if the connection drops it backs off and reconnects on its own, and it replays the last event ID so the server can resume where it left off. Both packages are ports of this API, written for Node or for browsers that predate it.",
10
+ snippet: `const source = new EventSource("/api/updates", {
11
+ withCredentials: true,
12
+ });
13
+
14
+ source.addEventListener("price", (event) => {
15
+ update(JSON.parse(event.data));
16
+ });
17
+
18
+ source.addEventListener("error", () => {
19
+ // The browser is already retrying. Close only to stop for good.
20
+ if (source.readyState === EventSource.CLOSED) reportOffline();
21
+ });`,
22
+ mdnUrl: "https://developer.mozilla.org/en-US/docs/Web/API/EventSource",
23
+ },
24
+ agent: {
25
+ when: "subscribing to a one-way stream of updates from a server",
26
+ unless: [
27
+ "You send an Authorization header on the connection. EventSource cannot set request headers at all, which is the single most common reason projects install the polyfill instead.",
28
+ "The stream needs a POST body or a method other than GET, in which case fetch with a ReadableStream is the platform answer rather than EventSource.",
29
+ "You need control over the reconnection: your own backoff, a retry cap, or a hard failure after N attempts. The browser's loop is not configurable from script.",
30
+ "The code runs in Node rather than a browser. A global EventSource arrived there only recently, and this package is the established client for older runtimes.",
31
+ "You need to inspect response status or headers on connect, for example to tell a 401 apart from a network drop. EventSource surfaces one opaque error event.",
32
+ ],
33
+ snippet: `const source = new EventSource("/api/updates");
34
+ source.addEventListener("price", (e) => update(JSON.parse(e.data)));
35
+ source.close();`,
36
+ },
37
+ };
38
+ //# sourceMappingURL=server-sent-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-sent-events.js","sourceRoot":"","sources":["../../src/rules/server-sent-events.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,oBAAoB;IACxB,KAAK,EAAE,oBAAoB;IAC3B,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAClD,UAAU,EAAE,CAAC,oBAAoB,CAAC;IAClC,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE;QACL,SAAS,EACP,mWAAmW;QACrW,OAAO,EAAE;;;;;;;;;;;IAWT;QACA,MAAM,EAAE,8DAA8D;KACvE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,0DAA0D;QAChE,MAAM,EAAE;YACN,kLAAkL;YAClL,oJAAoJ;YACpJ,gKAAgK;YAChK,+JAA+J;YAC/J,8JAA8J;SAC/J;QACD,OAAO,EAAE;;gBAEG;KACb;CACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"smooth-scroll.d.ts","sourceRoot":"","sources":["../../src/rules/smooth-scroll.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,YAAY,EAAE,IAyC1B,CAAC"}
1
+ {"version":3,"file":"smooth-scroll.d.ts","sourceRoot":"","sources":["../../src/rules/smooth-scroll.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,YAAY,EAAE,IA4C1B,CAAC"}
@@ -1,12 +1,14 @@
1
1
  export const smoothScroll = {
2
2
  id: "smooth-scroll",
3
3
  title: "Smooth scrolling and scroll-to-anchor",
4
+ category: "scrolling",
4
5
  replaces: [
5
6
  "react-scroll",
6
7
  "smoothscroll-polyfill",
7
8
  "scroll-behavior-polyfill",
8
9
  "smooth-scroll",
9
10
  "jump.js",
11
+ "vue-scrollto",
10
12
  ],
11
13
  featureIds: ["scroll-behavior"],
12
14
  native: "scroll-behavior: smooth with scroll-margin-top",
@@ -38,5 +40,6 @@ export const smoothScroll = {
38
40
  }
39
41
  [id] { scroll-margin-top: 5rem; }`,
40
42
  },
43
+ guides: ["scroll-target-on-load"],
41
44
  };
42
45
  //# sourceMappingURL=smooth-scroll.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"smooth-scroll.js","sourceRoot":"","sources":["../../src/rules/smooth-scroll.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,EAAE,EAAE,eAAe;IACnB,KAAK,EAAE,uCAAuC;IAC9C,QAAQ,EAAE;QACR,cAAc;QACd,uBAAuB;QACvB,0BAA0B;QAC1B,eAAe;QACf,SAAS;KACV;IACD,UAAU,EAAE,CAAC,iBAAiB,CAAC;IAC/B,MAAM,EAAE,gDAAgD;IACxD,KAAK,EAAE;QACL,SAAS,EACP,mYAAmY;QACrY,OAAO,EAAE;;;;;;;;;;EAUX;QACE,MAAM,EAAE,kEAAkE;KAC3E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,6DAA6D;QACnE,MAAM,EAAE;YACN,mHAAmH;YACnH,6GAA6G;YAC7G,gFAAgF;YAChF,8KAA8K;SAC/K;QACD,OAAO,EAAE;;;kCAGqB;KAC/B;CACF,CAAC"}
1
+ {"version":3,"file":"smooth-scroll.js","sourceRoot":"","sources":["../../src/rules/smooth-scroll.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,EAAE,EAAE,eAAe;IACnB,KAAK,EAAE,uCAAuC;IAC9C,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE;QACR,cAAc;QACd,uBAAuB;QACvB,0BAA0B;QAC1B,eAAe;QACf,SAAS;QACT,cAAc;KACf;IACD,UAAU,EAAE,CAAC,iBAAiB,CAAC;IAC/B,MAAM,EAAE,gDAAgD;IACxD,KAAK,EAAE;QACL,SAAS,EACP,mYAAmY;QACrY,OAAO,EAAE;;;;;;;;;;EAUX;QACE,MAAM,EAAE,kEAAkE;KAC3E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,6DAA6D;QACnE,MAAM,EAAE;YACN,mHAAmH;YACnH,6GAA6G;YAC7G,gFAAgF;YAChF,8KAA8K;SAC/K;QACD,OAAO,EAAE;;;kCAGqB;KAC/B;IACD,MAAM,EAAE,CAAC,uBAAuB,CAAC;CAClC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../schema.ts";
2
+ export declare const speechRecognition: Rule;
3
+ //# sourceMappingURL=speech-recognition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech-recognition.d.ts","sourceRoot":"","sources":["../../src/rules/speech-recognition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,iBAAiB,EAAE,IAoC/B,CAAC"}
@@ -0,0 +1,36 @@
1
+ export const speechRecognition = {
2
+ id: "speech-recognition",
3
+ title: "Voice input",
4
+ category: "device-apis",
5
+ replaces: ["annyang", "react-speech-recognition"],
6
+ featureIds: ["speech-recognition"],
7
+ native: "SpeechRecognition",
8
+ human: {
9
+ explainer: "annyang and react-speech-recognition both wrap the same underlying API, adding phrase matching or restart-on-silence logic around it. The API itself lets a page listen for speech and hands back a transcript with confidence scores. The constructor is the one part worth copying carefully: the unprefixed name only arrived in Chrome {{chrome:speech-recognition}}, and Safari has exposed it as webkitSpeechRecognition for years, so read both off the window before calling it.",
10
+ snippet: `// Unprefixed is Chrome {{chrome:speech-recognition}} and up. Safari has only
11
+ // the prefixed name,
12
+ // and reading it off window avoids a ReferenceError everywhere else.
13
+ const SpeechRecognitionCtor =
14
+ window.SpeechRecognition ?? window.webkitSpeechRecognition;
15
+
16
+ if (SpeechRecognitionCtor) {
17
+ const recognition = new SpeechRecognitionCtor();
18
+ recognition.onresult = (event) => {
19
+ console.log(event.results[0][0].transcript);
20
+ };
21
+ recognition.start();
22
+ }`,
23
+ mdnUrl: "https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition",
24
+ },
25
+ agent: {
26
+ when: "capturing a speech-to-text transcript directly in the browser",
27
+ unless: [
28
+ "You want one constructor name that works everywhere. The unprefixed SpeechRecognition is Chrome and Edge {{chrome:speech-recognition}} and up, Safari has only webkitSpeechRecognition, and Firefox ships neither, so the feature detection is yours to write.",
29
+ "You need continuous recognition that automatically restarts after the browser's built-in silence timeout. That restart logic is exactly what these libraries still add.",
30
+ "You need recognition to stay on the device. Chrome sends audio to Google's servers by default, and on-device transcription is a separate opt-in rather than the behaviour you get for free.",
31
+ ],
32
+ snippet: `const Ctor = window.SpeechRecognition ?? window.webkitSpeechRecognition;
33
+ if (Ctor) new Ctor().start();`,
34
+ },
35
+ };
36
+ //# sourceMappingURL=speech-recognition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech-recognition.js","sourceRoot":"","sources":["../../src/rules/speech-recognition.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAS;IACrC,EAAE,EAAE,oBAAoB;IACxB,KAAK,EAAE,aAAa;IACpB,QAAQ,EAAE,aAAa;IACvB,QAAQ,EAAE,CAAC,SAAS,EAAE,0BAA0B,CAAC;IACjD,UAAU,EAAE,CAAC,oBAAoB,CAAC;IAClC,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE;QACL,SAAS,EACP,0dAA0d;QAC5d,OAAO,EAAE;;;;;;;;;;;;EAYX;QACE,MAAM,EACJ,oEAAoE;KACvE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,+DAA+D;QACrE,MAAM,EAAE;YACN,gQAAgQ;YAChQ,yKAAyK;YACzK,6LAA6L;SAC9L;QACD,OAAO,EAAE;8BACiB;KAC3B;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../schema.ts";
2
+ export declare const speechSynthesis: Rule;
3
+ //# sourceMappingURL=speech-synthesis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech-synthesis.d.ts","sourceRoot":"","sources":["../../src/rules/speech-synthesis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,eAAe,EAAE,IAwB7B,CAAC"}
@@ -0,0 +1,25 @@
1
+ export const speechSynthesis = {
2
+ id: "speech-synthesis",
3
+ title: "Text to speech",
4
+ category: "device-apis",
5
+ replaces: ["speak-tts", "react-text-to-speech"],
6
+ featureIds: ["speech-synthesis"],
7
+ native: "SpeechSynthesis and SpeechSynthesisUtterance",
8
+ human: {
9
+ explainer: "speak-tts is a queueing and promise layer over the browser's own SpeechSynthesis API. The browser already exposes every installed system voice and a speaking queue; the wrapper mostly makes calling it feel more like a promise.",
10
+ snippet: `const utterance = new SpeechSynthesisUtterance("Hello there");
11
+ speechSynthesis.speak(utterance);`,
12
+ mdnUrl: "https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis",
13
+ },
14
+ agent: {
15
+ when: "reading text aloud with the browser's own text-to-speech engine",
16
+ unless: [
17
+ "You need the voice list on first paint. speechSynthesis.getVoices() returns an empty array until the voiceschanged event fires, so a voice picker has to wait for it, and that timing is most of what speak-tts wraps.",
18
+ "You speak long passages in Chrome, which cuts an utterance off after roughly fifteen seconds unless something keeps resuming it.",
19
+ "You need a specific voice bundled with your app rather than whatever the person's operating system happens to have installed. Voice availability and quality vary by device.",
20
+ "You need the callback-based API wrapped in a promise, or queueing logic across multiple utterances. The library still saves real code there.",
21
+ ],
22
+ snippet: "speechSynthesis.speak(new SpeechSynthesisUtterance(text));",
23
+ },
24
+ };
25
+ //# sourceMappingURL=speech-synthesis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech-synthesis.js","sourceRoot":"","sources":["../../src/rules/speech-synthesis.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,kBAAkB;IACtB,KAAK,EAAE,gBAAgB;IACvB,QAAQ,EAAE,aAAa;IACvB,QAAQ,EAAE,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC/C,UAAU,EAAE,CAAC,kBAAkB,CAAC;IAChC,MAAM,EAAE,8CAA8C;IACtD,KAAK,EAAE;QACL,SAAS,EACP,oOAAoO;QACtO,OAAO,EAAE;kCACqB;QAC9B,MAAM,EAAE,kEAAkE;KAC3E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,iEAAiE;QACvE,MAAM,EAAE;YACN,wNAAwN;YACxN,kIAAkI;YAClI,8KAA8K;YAC9K,8IAA8I;SAC/I;QACD,OAAO,EAAE,4DAA4D;KACtE;CACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"sticky.d.ts","sourceRoot":"","sources":["../../src/rules/sticky.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,MAAM,EAAE,IAuCpB,CAAC"}
1
+ {"version":3,"file":"sticky.d.ts","sourceRoot":"","sources":["../../src/rules/sticky.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,MAAM,EAAE,IA2CpB,CAAC"}
@@ -1,12 +1,15 @@
1
1
  export const sticky = {
2
2
  id: "sticky-positioning",
3
3
  title: "Sticky headers and sidebars",
4
+ category: "layout",
4
5
  replaces: [
5
6
  "sticky-js",
6
7
  "stickyfill",
7
8
  "react-sticky",
8
9
  "sticky-kit",
9
10
  "stickybits",
11
+ "vue-sticky-directive",
12
+ "vue-sticky",
10
13
  ],
11
14
  featureIds: ["sticky-positioning"],
12
15
  native: "position: sticky",
@@ -36,5 +39,6 @@ export const sticky = {
36
39
  snippet: `.header { position: sticky; top: 0; z-index: 10; }
37
40
  .sidebar { position: sticky; top: 5rem; align-self: start; }`,
38
41
  },
42
+ guides: ["state-aware-sticky-headers", "scroll-position-aware-elements"],
39
43
  };
40
44
  //# sourceMappingURL=sticky.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sticky.js","sourceRoot":"","sources":["../../src/rules/sticky.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,MAAM,GAAS;IAC1B,EAAE,EAAE,oBAAoB;IACxB,KAAK,EAAE,6BAA6B;IACpC,QAAQ,EAAE;QACR,WAAW;QACX,YAAY;QACZ,cAAc;QACd,YAAY;QACZ,YAAY;KACb;IACD,UAAU,EAAE,CAAC,oBAAoB,CAAC;IAClC,MAAM,EAAE,kBAAkB;IAC1B,KAAK,EAAE;QACL,SAAS,EACP,iaAAia;QACna,OAAO,EAAE;;;;;;;;;;;EAWX;QACE,MAAM,EAAE,kEAAkE;KAC3E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,0EAA0E;QAChF,MAAM,EAAE;YACN,iLAAiL;YACjL,kLAAkL;YAClL,qFAAqF;SACtF;QACD,OAAO,EAAE;6DACgD;KAC1D;CACF,CAAC"}
1
+ {"version":3,"file":"sticky.js","sourceRoot":"","sources":["../../src/rules/sticky.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,MAAM,GAAS;IAC1B,EAAE,EAAE,oBAAoB;IACxB,KAAK,EAAE,6BAA6B;IACpC,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE;QACR,WAAW;QACX,YAAY;QACZ,cAAc;QACd,YAAY;QACZ,YAAY;QACZ,sBAAsB;QACtB,YAAY;KACb;IACD,UAAU,EAAE,CAAC,oBAAoB,CAAC;IAClC,MAAM,EAAE,kBAAkB;IAC1B,KAAK,EAAE;QACL,SAAS,EACP,iaAAia;QACna,OAAO,EAAE;;;;;;;;;;;EAWX;QACE,MAAM,EAAE,kEAAkE;KAC3E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,0EAA0E;QAChF,MAAM,EAAE;YACN,iLAAiL;YACjL,kLAAkL;YAClL,qFAAqF;SACtF;QACD,OAAO,EAAE;6DACgD;KAC1D;IACD,MAAM,EAAE,CAAC,4BAA4B,EAAE,gCAAgC,CAAC;CACzE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../schema.ts";
2
+ export declare const structuredCloneRule: Rule;
3
+ //# sourceMappingURL=structured-clone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-clone.d.ts","sourceRoot":"","sources":["../../src/rules/structured-clone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,mBAAmB,EAAE,IA+BjC,CAAC"}
@@ -0,0 +1,32 @@
1
+ export const structuredCloneRule = {
2
+ id: "structured-clone",
3
+ title: "Deep cloning",
4
+ category: "async-data",
5
+ replaces: [
6
+ "lodash.clonedeep",
7
+ "rfdc",
8
+ "clone",
9
+ "klona",
10
+ "clone-deep",
11
+ "fast-copy",
12
+ "just-clone",
13
+ ],
14
+ featureIds: ["structured-clone"],
15
+ native: "structuredClone()",
16
+ human: {
17
+ explainer: "These packages walk an object graph by hand, copying nested arrays and objects one property at a time so the copy shares no references with the original. A global function now does the same job: structuredClone() copies arrays, plain objects, Maps, Sets, and dates in one call, no walking required.",
18
+ snippet: "const copy = structuredClone(original);",
19
+ mdnUrl: "https://developer.mozilla.org/en-US/docs/Web/API/structuredClone",
20
+ },
21
+ agent: {
22
+ when: "deep-copying plain data such as arrays, objects, Maps, Sets, and dates",
23
+ unless: [
24
+ "You need to clone functions or DOM nodes. structuredClone throws a DataCloneError on both, where lodash's version drops what it cannot handle and carries on.",
25
+ "You clone class instances and need the prototype back. structuredClone does not throw here, which is the trap: it returns a plain object with the same fields and no methods, so the failure shows up later at the first method call.",
26
+ "You need to clone something containing a value structuredClone doesn't support, such as an Error's custom properties beyond message and name.",
27
+ "You clone large objects on a hot path and measured the difference. Being faster than structuredClone on plain data is the whole pitch of rfdc and fast-copy, and a benchmark on your own shapes is the only way to settle it.",
28
+ ],
29
+ snippet: "const copy = structuredClone(original);",
30
+ },
31
+ };
32
+ //# sourceMappingURL=structured-clone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-clone.js","sourceRoot":"","sources":["../../src/rules/structured-clone.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,mBAAmB,GAAS;IACvC,EAAE,EAAE,kBAAkB;IACtB,KAAK,EAAE,cAAc;IACrB,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE;QACR,kBAAkB;QAClB,MAAM;QACN,OAAO;QACP,OAAO;QACP,YAAY;QACZ,WAAW;QACX,YAAY;KACb;IACD,UAAU,EAAE,CAAC,kBAAkB,CAAC;IAChC,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE;QACL,SAAS,EACP,4SAA4S;QAC9S,OAAO,EAAE,yCAAyC;QAClD,MAAM,EAAE,kEAAkE;KAC3E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,wEAAwE;QAC9E,MAAM,EAAE;YACN,+JAA+J;YAC/J,uOAAuO;YACvO,+IAA+I;YAC/I,+NAA+N;SAChO;QACD,OAAO,EAAE,yCAAyC;KACnD;CACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"text-box-trim.d.ts","sourceRoot":"","sources":["../../src/rules/text-box-trim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,WAAW,EAAE,IAgCzB,CAAC"}
1
+ {"version":3,"file":"text-box-trim.d.ts","sourceRoot":"","sources":["../../src/rules/text-box-trim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,WAAW,EAAE,IAkCzB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  export const textBoxTrim = {
2
2
  id: "text-box-trim",
3
3
  title: "Trimming font leading",
4
+ category: "typography",
4
5
  replaces: ["capsize", "@capsizecss/core", "@capsizecss/metrics"],
5
6
  featureIds: ["text-box"],
6
7
  native: "text-box-trim and text-box-edge",
@@ -23,11 +24,12 @@ export const textBoxTrim = {
23
24
  agent: {
24
25
  when: "removing a font's built-in leading so text sits flush in its box",
25
26
  unless: [
26
- "You are shipping to production outside Chromium. text-box is limited availability, and the fallback is the untrimmed spacing you started with, which shifts your vertical rhythm between browsers.",
27
+ "Your support target reaches below Chrome {{chrome:css.properties.text-box}}, Safari {{safari:css.properties.text-box}} or Firefox {{firefox:css.properties.text-box}}. Firefox was last to ship it, so on an older target the fallback is the untrimmed spacing you started with and your vertical rhythm shifts between browsers.",
27
28
  "You need the computed metrics as numbers at build time, for example to generate spacing tokens or to lay out text in a canvas or a PDF. Capsize gives you values; CSS only affects rendering.",
28
29
  "You need to support a font whose metrics are wrong or missing, which Capsize lets you override by hand.",
29
30
  ],
30
31
  snippet: "h1 { text-box: trim-both cap alphabetic; }",
31
32
  },
33
+ guides: ["precise-text-alignment"],
32
34
  };
33
35
  //# sourceMappingURL=text-box-trim.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-box-trim.js","sourceRoot":"","sources":["../../src/rules/text-box-trim.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAS;IAC/B,EAAE,EAAE,eAAe;IACnB,KAAK,EAAE,uBAAuB;IAC9B,QAAQ,EAAE,CAAC,SAAS,EAAE,kBAAkB,EAAE,qBAAqB,CAAC;IAChE,UAAU,EAAE,CAAC,UAAU,CAAC;IACxB,MAAM,EAAE,iCAAiC;IACzC,KAAK,EAAE;QACL,SAAS,EACP,+bAA+b;QACjc,OAAO,EAAE;;;;;;;;;;;EAWX;QACE,MAAM,EAAE,gEAAgE;KACzE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,kEAAkE;QACxE,MAAM,EAAE;YACN,oMAAoM;YACpM,+LAA+L;YAC/L,yGAAyG;SAC1G;QACD,OAAO,EAAE,4CAA4C;KACtD;CACF,CAAC"}
1
+ {"version":3,"file":"text-box-trim.js","sourceRoot":"","sources":["../../src/rules/text-box-trim.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAS;IAC/B,EAAE,EAAE,eAAe;IACnB,KAAK,EAAE,uBAAuB;IAC9B,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE,CAAC,SAAS,EAAE,kBAAkB,EAAE,qBAAqB,CAAC;IAChE,UAAU,EAAE,CAAC,UAAU,CAAC;IACxB,MAAM,EAAE,iCAAiC;IACzC,KAAK,EAAE;QACL,SAAS,EACP,+bAA+b;QACjc,OAAO,EAAE;;;;;;;;;;;EAWX;QACE,MAAM,EAAE,gEAAgE;KACzE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,kEAAkE;QACxE,MAAM,EAAE;YACN,oUAAoU;YACpU,+LAA+L;YAC/L,yGAAyG;SAC1G;QACD,OAAO,EAAE,4CAA4C;KACtD;IACD,MAAM,EAAE,CAAC,wBAAwB,CAAC;CACnC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"text-wrap-balance.d.ts","sourceRoot":"","sources":["../../src/rules/text-wrap-balance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,eAAe,EAAE,IAkC7B,CAAC"}
1
+ {"version":3,"file":"text-wrap-balance.d.ts","sourceRoot":"","sources":["../../src/rules/text-wrap-balance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,eAAe,EAAE,IAoC7B,CAAC"}
@@ -1,6 +1,7 @@
1
1
  export const textWrapBalance = {
2
2
  id: "text-wrap-balance",
3
3
  title: "Balanced headings",
4
+ category: "typography",
4
5
  replaces: ["react-wrap-balancer", "balance-text"],
5
6
  featureIds: ["text-wrap-balance"],
6
7
  native: "text-wrap: balance",
@@ -31,5 +32,6 @@ p {
31
32
  snippet: `h1, h2, h3 { text-wrap: balance; }
32
33
  p { text-wrap: pretty; }`,
33
34
  },
35
+ guides: ["improve-text-layout-and-legibility"],
34
36
  };
35
37
  //# sourceMappingURL=text-wrap-balance.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"text-wrap-balance.js","sourceRoot":"","sources":["../../src/rules/text-wrap-balance.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,mBAAmB;IACvB,KAAK,EAAE,mBAAmB;IAC1B,QAAQ,EAAE,CAAC,qBAAqB,EAAE,cAAc,CAAC;IACjD,UAAU,EAAE,CAAC,mBAAmB,CAAC;IACjC,MAAM,EAAE,oBAAoB;IAC5B,KAAK,EAAE;QACL,SAAS,EACP,qbAAqb;QACvb,OAAO,EAAE;;;;;;;;;;;EAWX;QACE,MAAM,EAAE,4DAA4D;KACrE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,gFAAgF;QACtF,MAAM,EAAE;YACN,4JAA4J;YAC5J,mKAAmK;YACnK,gIAAgI;YAChI,gJAAgJ;SACjJ;QACD,OAAO,EAAE;yBACY;KACtB;CACF,CAAC"}
1
+ {"version":3,"file":"text-wrap-balance.js","sourceRoot":"","sources":["../../src/rules/text-wrap-balance.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,mBAAmB;IACvB,KAAK,EAAE,mBAAmB;IAC1B,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE,CAAC,qBAAqB,EAAE,cAAc,CAAC;IACjD,UAAU,EAAE,CAAC,mBAAmB,CAAC;IACjC,MAAM,EAAE,oBAAoB;IAC5B,KAAK,EAAE;QACL,SAAS,EACP,qbAAqb;QACvb,OAAO,EAAE;;;;;;;;;;;EAWX;QACE,MAAM,EAAE,4DAA4D;KACrE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,gFAAgF;QACtF,MAAM,EAAE;YACN,4JAA4J;YAC5J,mKAAmK;YACnK,gIAAgI;YAChI,gJAAgJ;SACjJ;QACD,OAAO,EAAE;yBACY;KACtB;IACD,MAAM,EAAE,CAAC,oCAAoC,CAAC;CAC/C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../schema.ts";
2
+ export declare const urlSearchParams: Rule;
3
+ //# sourceMappingURL=url-search-params.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-search-params.d.ts","sourceRoot":"","sources":["../../src/rules/url-search-params.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,eAAe,EAAE,IAyC7B,CAAC"}
@@ -0,0 +1,42 @@
1
+ export const urlSearchParams = {
2
+ id: "url-search-params",
3
+ title: "Query string parsing",
4
+ category: "async-data",
5
+ replaces: ["query-string", "qs", "querystringify", "url-parse"],
6
+ featureIds: [],
7
+ manualBaseline: {
8
+ status: "widely",
9
+ verifiedOn: "2026-09-06",
10
+ note: "web-features folds URLSearchParams into the 'url' feature, which reports Chrome {{chrome:url}} and Safari {{safari:url}} because the URL constructor landed first. URLSearchParams itself is Chrome {{chrome:api.URLSearchParams}}, Firefox {{firefox:api.URLSearchParams}} and Safari {{safari:api.URLSearchParams}}. Deriving from 'url' would understate the floor by three Safari majors. Widely available on its own dates since 2017.",
11
+ },
12
+ native: "URLSearchParams and the URL constructor",
13
+ human: {
14
+ explainer: "URLSearchParams reads and writes query strings, handles percent-encoding in both directions, and gives you get, getAll, set, append and delete. new URL() does the same job for a whole address, so the hostname, the pathname and the search params come apart without a regular expression. Both have been in every browser since 2017 and in Node for as long, and both are what these libraries call underneath once you strip the option handling.",
15
+ snippet: `const url = new URL("/search?tag=css&tag=html&page=2", location.origin);
16
+
17
+ url.searchParams.getAll("tag"); // ["css", "html"]
18
+ url.searchParams.get("page"); // "2", a string
19
+ url.searchParams.set("page", "3");
20
+
21
+ // Build one from scratch.
22
+ new URLSearchParams({ q: "scroll snap", page: "1" }).toString();
23
+ // "q=scroll+snap&page=1"`,
24
+ mdnUrl: "https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams",
25
+ },
26
+ agent: {
27
+ when: "reading or building a query string, or pulling a URL apart",
28
+ unless: [
29
+ "You parse nested objects or arrays from bracket syntax such as filter[status][]=open. That is the reason qs exists, and URLSearchParams has no equivalent.",
30
+ "You round-trip typed data through the query string. Every value comes back as a string, so numbers, booleans and null need converting at each read site.",
31
+ "You depend on the library's options: a custom array format, a configurable delimiter, sorted output, or comma-separated values parsed into arrays.",
32
+ "You parse untrusted input on a server and rely on qs's depth and parameterLimit guards against prototype pollution. URLSearchParams applies no limits of its own.",
33
+ "You need Node's legacy querystring semantics, where a repeated key gives an array rather than needing getAll().",
34
+ "Your support target reaches below Chrome {{chrome:api.URLSearchParams}}, Firefox {{firefox:api.URLSearchParams}} or Safari {{safari:api.URLSearchParams}}. The URL constructor arrived earlier than URLSearchParams, so a browser having one is not proof it has the other.",
35
+ ],
36
+ snippet: `const params = new URLSearchParams(location.search);
37
+ params.get("page");
38
+ params.getAll("tag");
39
+ new URLSearchParams({ q: "css" }).toString();`,
40
+ },
41
+ };
42
+ //# sourceMappingURL=url-search-params.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-search-params.js","sourceRoot":"","sources":["../../src/rules/url-search-params.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,mBAAmB;IACvB,KAAK,EAAE,sBAAsB;IAC7B,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,gBAAgB,EAAE,WAAW,CAAC;IAC/D,UAAU,EAAE,EAAE;IACd,cAAc,EAAE;QACd,MAAM,EAAE,QAAQ;QAChB,UAAU,EAAE,YAAY;QACxB,IAAI,EAAE,6aAA6a;KACpb;IACD,MAAM,EAAE,yCAAyC;IACjD,KAAK,EAAE;QACL,SAAS,EACP,ybAAyb;QAC3b,OAAO,EAAE;;;;;;;;0BAQa;QACtB,MAAM,EAAE,kEAAkE;KAC3E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,4DAA4D;QAClE,MAAM,EAAE;YACN,4JAA4J;YAC5J,0JAA0J;YAC1J,oJAAoJ;YACpJ,mKAAmK;YACnK,iHAAiH;YACjH,6QAA6Q;SAC9Q;QACD,OAAO,EAAE;;;8CAGiC;KAC3C;CACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"view-transitions.d.ts","sourceRoot":"","sources":["../../src/rules/view-transitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,eAAe,EAAE,IAsD7B,CAAC"}
1
+ {"version":3,"file":"view-transitions.d.ts","sourceRoot":"","sources":["../../src/rules/view-transitions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,eAAe,EAAE,IA+D7B,CAAC"}
@@ -1,6 +1,7 @@
1
1
  export const viewTransitions = {
2
2
  id: "view-transitions",
3
3
  title: "Page and state transitions",
4
+ category: "animation",
4
5
  replaces: [
5
6
  "next-view-transitions",
6
7
  "react-page-transition",
@@ -50,5 +51,13 @@ document.startViewTransition(() => {
50
51
  ::view-transition-group(*) { animation: none; }
51
52
  }`,
52
53
  },
54
+ guides: [
55
+ "same-document-transitions",
56
+ "cross-document-transitions",
57
+ "faster-spa-view-transitions",
58
+ "consistent-cross-document-transitions",
59
+ "directional-navigation-transitions",
60
+ "group-element-transitions",
61
+ ],
53
62
  };
54
63
  //# sourceMappingURL=view-transitions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-transitions.js","sourceRoot":"","sources":["../../src/rules/view-transitions.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,kBAAkB;IACtB,KAAK,EAAE,4BAA4B;IACnC,QAAQ,EAAE;QACR,uBAAuB;QACvB,uBAAuB;QACvB,UAAU;QACV,MAAM;KACP;IACD,UAAU,EAAE,CAAC,kBAAkB,CAAC;IAChC,MAAM,EAAE,0BAA0B;IAClC,KAAK,EAAE;QACL,SAAS,EACP,gcAAgc;QAClc,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;EAoBX;QACE,MAAM,EACJ,uEAAuE;KAC1E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,+FAA+F;QACrG,MAAM,EAAE;YACN,+LAA+L;YAC/L,4JAA4J;YAC5J,6HAA6H;YAC7H,oGAAoG;YACpG,2FAA2F;SAC5F;QACD,OAAO,EAAE;;;;;EAKX;KACC;CACF,CAAC"}
1
+ {"version":3,"file":"view-transitions.js","sourceRoot":"","sources":["../../src/rules/view-transitions.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAS;IACnC,EAAE,EAAE,kBAAkB;IACtB,KAAK,EAAE,4BAA4B;IACnC,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE;QACR,uBAAuB;QACvB,uBAAuB;QACvB,UAAU;QACV,MAAM;KACP;IACD,UAAU,EAAE,CAAC,kBAAkB,CAAC;IAChC,MAAM,EAAE,0BAA0B;IAClC,KAAK,EAAE;QACL,SAAS,EACP,gcAAgc;QAClc,OAAO,EAAE;;;;;;;;;;;;;;;;;;;;EAoBX;QACE,MAAM,EACJ,uEAAuE;KAC1E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,+FAA+F;QACrG,MAAM,EAAE;YACN,+LAA+L;YAC/L,4JAA4J;YAC5J,6HAA6H;YAC7H,oGAAoG;YACpG,2FAA2F;SAC5F;QACD,OAAO,EAAE;;;;;EAKX;KACC;IACD,MAAM,EAAE;QACN,2BAA2B;QAC3B,4BAA4B;QAC5B,6BAA6B;QAC7B,uCAAuC;QACvC,oCAAoC;QACpC,2BAA2B;KAC5B;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../schema.ts";
2
+ export declare const webBluetooth: Rule;
3
+ //# sourceMappingURL=web-bluetooth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-bluetooth.d.ts","sourceRoot":"","sources":["../../src/rules/web-bluetooth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,YAAY,EAAE,IA2B1B,CAAC"}
@@ -0,0 +1,26 @@
1
+ export const webBluetooth = {
2
+ id: "web-bluetooth",
3
+ title: "Bluetooth device access",
4
+ category: "device-apis",
5
+ replaces: ["@capacitor-community/bluetooth-le", "cordova-plugin-ble-central"],
6
+ featureIds: ["web-bluetooth"],
7
+ native: "navigator.bluetooth.requestDevice()",
8
+ human: {
9
+ explainer: "Talking to a Bluetooth peripheral from a web app used to mean wrapping the page in a native shell such as Cordova or Capacitor and reaching for a plugin that called into the operating system's own Bluetooth stack. A page running directly in a supporting browser can request a device, connect to its GATT services, and read or write characteristics with no native wrapper at all. This is a narrower case than most of this catalog. It replaces the native-app packaging step these plugins exist for, rather than a JavaScript library doing work the browser could already do.",
10
+ snippet: `const device = await navigator.bluetooth.requestDevice({
11
+ acceptAllDevices: true,
12
+ });
13
+ const server = await device.gatt.connect();`,
14
+ mdnUrl: "https://developer.mozilla.org/en-US/docs/Web/API/Web_Bluetooth_API",
15
+ },
16
+ agent: {
17
+ when: "connecting to a Bluetooth Low Energy device directly from a web page",
18
+ unless: [
19
+ "You need to support iOS in any browser. Apple has stated it has no plans to implement Web Bluetooth. That gap is permanent for iPhone and iPad users.",
20
+ "You need background scanning or a connection that survives the tab closing. The API only works while the page is open.",
21
+ "You are already shipping a native app shell for other reasons. The plugin ecosystem is more mature for background operation and OS-level pairing UI.",
22
+ ],
23
+ snippet: "await navigator.bluetooth.requestDevice({ acceptAllDevices: true });",
24
+ },
25
+ };
26
+ //# sourceMappingURL=web-bluetooth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-bluetooth.js","sourceRoot":"","sources":["../../src/rules/web-bluetooth.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAS;IAChC,EAAE,EAAE,eAAe;IACnB,KAAK,EAAE,yBAAyB;IAChC,QAAQ,EAAE,aAAa;IACvB,QAAQ,EAAE,CAAC,mCAAmC,EAAE,4BAA4B,CAAC;IAC7E,UAAU,EAAE,CAAC,eAAe,CAAC;IAC7B,MAAM,EAAE,qCAAqC;IAC7C,KAAK,EAAE;QACL,SAAS,EACP,4jBAA4jB;QAC9jB,OAAO,EAAE;;;4CAG+B;QACxC,MAAM,EACJ,oEAAoE;KACvE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,sEAAsE;QAC5E,MAAM,EAAE;YACN,uJAAuJ;YACvJ,wHAAwH;YACxH,sJAAsJ;SACvJ;QACD,OAAO,EACL,sEAAsE;KACzE;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../schema.ts";
2
+ export declare const webCrypto: Rule;
3
+ //# sourceMappingURL=web-crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-crypto.d.ts","sourceRoot":"","sources":["../../src/rules/web-crypto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,SAAS,EAAE,IA+BvB,CAAC"}
@@ -0,0 +1,32 @@
1
+ export const webCrypto = {
2
+ id: "web-crypto",
3
+ title: "Hashing and encryption",
4
+ category: "async-data",
5
+ replaces: ["crypto-js", "js-sha256", "crypto-hash"],
6
+ featureIds: ["web-cryptography"],
7
+ native: "crypto.subtle",
8
+ human: {
9
+ explainer: "crypto-js ships its own implementations of SHA-256, AES and HMAC as JavaScript, which is both a large download and slower than the same primitives compiled into the browser. crypto.subtle exposes those primitives natively, with key material held as CryptoKey objects the page cannot read out unless you mark them extractable. The API is asynchronous and takes bytes rather than strings, so a call site grows a TextEncoder and an await.",
10
+ snippet: `async function sha256(text) {
11
+ const bytes = new TextEncoder().encode(text);
12
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
13
+ return [...new Uint8Array(digest)]
14
+ .map((b) => b.toString(16).padStart(2, "0"))
15
+ .join("");
16
+ }`,
17
+ mdnUrl: "https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto",
18
+ },
19
+ agent: {
20
+ when: "hashing, signing, or encrypting with SHA-2, HMAC, AES or RSA",
21
+ unless: [
22
+ "You need MD5, SHA-1 signing, or another legacy algorithm. crypto.subtle leaves those out deliberately, so a format that requires one keeps its library.",
23
+ "The call has to be synchronous. Everything on crypto.subtle returns a promise, which changes any function that hashes inside a render pass or a sort comparator.",
24
+ "The code runs on plain http somewhere other than localhost. crypto.subtle is undefined outside a secure context.",
25
+ "You interoperate with crypto-js output that uses its own defaults, such as its OpenSSL-style key derivation for AES, where matching the format by hand is the harder job.",
26
+ "You need password hashing. Neither the library nor crypto.subtle gives you bcrypt, scrypt or Argon2, and PBKDF2 is the only stretching primitive here.",
27
+ ],
28
+ snippet: `const bytes = new TextEncoder().encode(text);
29
+ const digest = await crypto.subtle.digest("SHA-256", bytes);`,
30
+ },
31
+ };
32
+ //# sourceMappingURL=web-crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-crypto.js","sourceRoot":"","sources":["../../src/rules/web-crypto.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,SAAS,GAAS;IAC7B,EAAE,EAAE,YAAY;IAChB,KAAK,EAAE,wBAAwB;IAC/B,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE,CAAC,WAAW,EAAE,WAAW,EAAE,aAAa,CAAC;IACnD,UAAU,EAAE,CAAC,kBAAkB,CAAC;IAChC,MAAM,EAAE,eAAe;IACvB,KAAK,EAAE;QACL,SAAS,EACP,qbAAqb;QACvb,OAAO,EAAE;;;;;;EAMX;QACE,MAAM,EAAE,+DAA+D;KACxE;IACD,KAAK,EAAE;QACL,IAAI,EAAE,8DAA8D;QACpE,MAAM,EAAE;YACN,yJAAyJ;YACzJ,kKAAkK;YAClK,kHAAkH;YAClH,2KAA2K;YAC3K,wJAAwJ;SACzJ;QACD,OAAO,EAAE;6DACgD;KAC1D;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../schema.ts";
2
+ export declare const webShare: Rule;
3
+ //# sourceMappingURL=web-share.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-share.d.ts","sourceRoot":"","sources":["../../src/rules/web-share.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,eAAO,MAAM,QAAQ,EAAE,IA4BtB,CAAC"}
@@ -0,0 +1,28 @@
1
+ export const webShare = {
2
+ id: "web-share",
3
+ title: "Sharing to other apps",
4
+ category: "device-apis",
5
+ replaces: ["react-share", "vue-social-sharing", "ngx-sharebuttons"],
6
+ featureIds: ["share"],
7
+ native: "navigator.share()",
8
+ human: {
9
+ explainer: "A share library renders a row of buttons, each pointing at one network's own share-intent URL, so the page decides where the content can go. navigator.share() opens the operating system's own share sheet instead, the same one every native app uses, and lets the person pick where it goes.",
10
+ snippet: `async function share() {
11
+ await navigator.share({
12
+ title: document.title,
13
+ url: location.href,
14
+ });
15
+ }`,
16
+ mdnUrl: "https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share",
17
+ },
18
+ agent: {
19
+ when: "letting someone share the current page or a piece of content to whatever app they choose",
20
+ unless: [
21
+ "You need a button for one specific network, such as a fixed Twitter/X or LinkedIn share link. The OS share sheet lists whatever apps the person has installed, not a chosen set.",
22
+ "You support Firefox on desktop. It has never shipped the Web Share API outside Android.",
23
+ "You need to share a file the person doesn't already have a URL for. That needs the file-sharing variant of the API, which has narrower browser support again.",
24
+ ],
25
+ snippet: "await navigator.share({ title: document.title, url: location.href });",
26
+ },
27
+ };
28
+ //# sourceMappingURL=web-share.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-share.js","sourceRoot":"","sources":["../../src/rules/web-share.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,QAAQ,GAAS;IAC5B,EAAE,EAAE,WAAW;IACf,KAAK,EAAE,uBAAuB;IAC9B,QAAQ,EAAE,aAAa;IACvB,QAAQ,EAAE,CAAC,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,CAAC;IACnE,UAAU,EAAE,CAAC,OAAO,CAAC;IACrB,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE;QACL,SAAS,EACP,kSAAkS;QACpS,OAAO,EAAE;;;;;EAKX;QACE,MAAM,EAAE,kEAAkE;KAC3E;IACD,KAAK,EAAE;QACL,IAAI,EAAE,0FAA0F;QAChG,MAAM,EAAE;YACN,kLAAkL;YAClL,yFAAyF;YACzF,+JAA+J;SAChK;QACD,OAAO,EACL,uEAAuE;KAC1E;CACF,CAAC"}
package/dist/schema.d.ts CHANGED
@@ -36,6 +36,16 @@ export type ManualBaseline = z.infer<typeof manualBaselineSchema>;
36
36
  export declare const ruleSchema: z.ZodObject<{
37
37
  id: z.ZodString;
38
38
  title: z.ZodString;
39
+ category: z.ZodEnum<{
40
+ animation: "animation";
41
+ "async-data": "async-data";
42
+ "device-apis": "device-apis";
43
+ formatting: "formatting";
44
+ forms: "forms";
45
+ layout: "layout";
46
+ scrolling: "scrolling";
47
+ typography: "typography";
48
+ }>;
39
49
  replaces: z.ZodArray<z.ZodString>;
40
50
  featureIds: z.ZodArray<z.ZodString>;
41
51
  native: z.ZodString;
@@ -50,6 +60,7 @@ export declare const ruleSchema: z.ZodObject<{
50
60
  unless: z.ZodArray<z.ZodString>;
51
61
  snippet: z.ZodString;
52
62
  }, z.core.$strip>;
63
+ guides: z.ZodOptional<z.ZodArray<z.ZodString>>;
53
64
  manualBaseline: z.ZodOptional<z.ZodObject<{
54
65
  status: z.ZodEnum<{
55
66
  limited: "limited";
@@ -64,6 +75,16 @@ export type Rule = z.infer<typeof ruleSchema>;
64
75
  export declare const catalogSchema: z.ZodArray<z.ZodObject<{
65
76
  id: z.ZodString;
66
77
  title: z.ZodString;
78
+ category: z.ZodEnum<{
79
+ animation: "animation";
80
+ "async-data": "async-data";
81
+ "device-apis": "device-apis";
82
+ formatting: "formatting";
83
+ forms: "forms";
84
+ layout: "layout";
85
+ scrolling: "scrolling";
86
+ typography: "typography";
87
+ }>;
67
88
  replaces: z.ZodArray<z.ZodString>;
68
89
  featureIds: z.ZodArray<z.ZodString>;
69
90
  native: z.ZodString;
@@ -78,6 +99,7 @@ export declare const catalogSchema: z.ZodArray<z.ZodObject<{
78
99
  unless: z.ZodArray<z.ZodString>;
79
100
  snippet: z.ZodString;
80
101
  }, z.core.$strip>;
102
+ guides: z.ZodOptional<z.ZodArray<z.ZodString>>;
81
103
  manualBaseline: z.ZodOptional<z.ZodObject<{
82
104
  status: z.ZodEnum<{
83
105
  limited: "limited";
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;EAK/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAkBlE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;iBAK/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4DnB,CAAC;AAEL,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2BxB,CAAC;AAEH,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,CAEnD"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;GAIG;AACH,eAAO,MAAM,cAAc,IAAI,CAAC;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB;;;;;EAK/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAmDlE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;iBAK/B,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+FnB,CAAC;AAEL,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBA2BxB,CAAC;AAEH,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,CAEnD"}