tempest-react-sdk 0.77.0 → 0.79.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 (1623) hide show
  1. package/README.md +6 -6
  2. package/bin/lib/alias/collect.mjs +20 -3
  3. package/bin/lib/alias/index.mjs +12 -1
  4. package/bin/lib/alias/index.test.mjs +24 -0
  5. package/bin/lib/css/analyze.test.mjs +21 -1
  6. package/bin/lib/css/collect.mjs +14 -0
  7. package/bin/lib/css/extract.mjs +63 -5
  8. package/bin/lib/css/extract.test.mjs +110 -1
  9. package/bin/lib/css/fix.e2e.test.mjs +50 -0
  10. package/bin/lib/css/index.mjs +6 -1
  11. package/bin/lib/fs/contain.mjs +50 -0
  12. package/bin/lib/icons/generate.mjs +28 -3
  13. package/bin/lib/icons/generate.test.mjs +57 -1
  14. package/bin/lib/openapi/escape.mjs +20 -6
  15. package/bin/lib/openapi/generate.encode.test.mjs +120 -0
  16. package/bin/lib/openapi/generate.hostile.test.mjs +38 -9
  17. package/bin/lib/openapi/generate.mjs +203 -64
  18. package/bin/lib/openapi/generate.pagination.test.mjs +3 -2
  19. package/bin/lib/openapi/generate.test.mjs +270 -3
  20. package/bin/lib/openapi/schema-to-zod.mjs +5 -3
  21. package/bin/tempest-gen.test.mjs +188 -0
  22. package/bin/tempest.mjs +120 -44
  23. package/dist/access/Can.cjs.map +1 -1
  24. package/dist/access/Can.js.map +1 -1
  25. package/dist/access/access-control-context.cjs.map +1 -1
  26. package/dist/access/access-control-context.js.map +1 -1
  27. package/dist/access/create-role-access-control.cjs.map +1 -1
  28. package/dist/access/create-role-access-control.js.map +1 -1
  29. package/dist/access/permissions-from-token.cjs.map +1 -1
  30. package/dist/access/permissions-from-token.js.map +1 -1
  31. package/dist/access/use-can.cjs.map +1 -1
  32. package/dist/access/use-can.js.map +1 -1
  33. package/dist/app/AppProviders.cjs.map +1 -1
  34. package/dist/app/AppProviders.js.map +1 -1
  35. package/dist/audio/audio-bus.cjs.map +1 -1
  36. package/dist/audio/audio-bus.js.map +1 -1
  37. package/dist/audio/audio-output.cjs.map +1 -1
  38. package/dist/audio/audio-output.js.map +1 -1
  39. package/dist/audio/audio-player.cjs.map +1 -1
  40. package/dist/audio/audio-player.js.map +1 -1
  41. package/dist/audio/audio-recorder.cjs.map +1 -1
  42. package/dist/audio/audio-recorder.js.map +1 -1
  43. package/dist/audio/duration.cjs.map +1 -1
  44. package/dist/audio/duration.js.map +1 -1
  45. package/dist/audio/level-meter.cjs.map +1 -1
  46. package/dist/audio/level-meter.js.map +1 -1
  47. package/dist/audio/media-access.cjs.map +1 -1
  48. package/dist/audio/media-access.js.map +1 -1
  49. package/dist/audio/noise-floor.cjs.map +1 -1
  50. package/dist/audio/noise-floor.js.map +1 -1
  51. package/dist/audio/sfx-pool.cjs.map +1 -1
  52. package/dist/audio/sfx-pool.js.map +1 -1
  53. package/dist/audio/use-audio-bus.cjs.map +1 -1
  54. package/dist/audio/use-audio-bus.js.map +1 -1
  55. package/dist/audio/use-audio-recorder.cjs.map +1 -1
  56. package/dist/audio/use-audio-recorder.js.map +1 -1
  57. package/dist/audio/use-audio.cjs.map +1 -1
  58. package/dist/audio/use-audio.js.map +1 -1
  59. package/dist/audio/use-microphone.cjs +1 -1
  60. package/dist/audio/use-microphone.cjs.map +1 -1
  61. package/dist/audio/use-microphone.js +1 -5
  62. package/dist/audio/use-microphone.js.map +1 -1
  63. package/dist/audio/use-sfx-pool.cjs.map +1 -1
  64. package/dist/audio/use-sfx-pool.js.map +1 -1
  65. package/dist/audio/voice-activity.cjs.map +1 -1
  66. package/dist/audio/voice-activity.js.map +1 -1
  67. package/dist/audio/voice-chain-stages.cjs.map +1 -1
  68. package/dist/audio/voice-chain-stages.js.map +1 -1
  69. package/dist/audio/voice-chain.cjs.map +1 -1
  70. package/dist/audio/voice-chain.js.map +1 -1
  71. package/dist/audio/wav.cjs.map +1 -1
  72. package/dist/audio/wav.js.map +1 -1
  73. package/dist/auth/AuthGuard.cjs.map +1 -1
  74. package/dist/auth/AuthGuard.js.map +1 -1
  75. package/dist/auth/create-auth-store.cjs.map +1 -1
  76. package/dist/auth/create-auth-store.js.map +1 -1
  77. package/dist/auth/create-tempest-auth.cjs +1 -1
  78. package/dist/auth/create-tempest-auth.cjs.map +1 -1
  79. package/dist/auth/create-tempest-auth.js +185 -114
  80. package/dist/auth/create-tempest-auth.js.map +1 -1
  81. package/dist/auth/jwt.cjs.map +1 -1
  82. package/dist/auth/jwt.js.map +1 -1
  83. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  84. package/dist/auth/lazy-with-retry.js.map +1 -1
  85. package/dist/auth/passkey.cjs.map +1 -1
  86. package/dist/auth/passkey.js.map +1 -1
  87. package/dist/auth/refresh-queue.cjs.map +1 -1
  88. package/dist/auth/refresh-queue.js.map +1 -1
  89. package/dist/auth/use-passkey.cjs +1 -1
  90. package/dist/auth/use-passkey.cjs.map +1 -1
  91. package/dist/auth/use-passkey.js +1 -1
  92. package/dist/auth/use-passkey.js.map +1 -1
  93. package/dist/br/BrazilMap.cjs.map +1 -1
  94. package/dist/br/BrazilMap.js.map +1 -1
  95. package/dist/br/BrazilMap.module.cjs.map +1 -1
  96. package/dist/br/BrazilMap.module.js.map +1 -1
  97. package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
  98. package/dist/br/BrazilStateCitySelect.js.map +1 -1
  99. package/dist/br/BrazilStateMap.cjs.map +1 -1
  100. package/dist/br/BrazilStateMap.js.map +1 -1
  101. package/dist/br/MapLegend.cjs.map +1 -1
  102. package/dist/br/MapLegend.js.map +1 -1
  103. package/dist/br/MapLegend.module.cjs.map +1 -1
  104. package/dist/br/MapLegend.module.js.map +1 -1
  105. package/dist/br/MapMarkers.cjs.map +1 -1
  106. package/dist/br/MapMarkers.js.map +1 -1
  107. package/dist/br/MapTooltip.cjs.map +1 -1
  108. package/dist/br/MapTooltip.js.map +1 -1
  109. package/dist/br/MunicipalitySearch.cjs +1 -1
  110. package/dist/br/MunicipalitySearch.cjs.map +1 -1
  111. package/dist/br/MunicipalitySearch.js +2 -5
  112. package/dist/br/MunicipalitySearch.js.map +1 -1
  113. package/dist/br/MunicipalitySearch.module.cjs.map +1 -1
  114. package/dist/br/MunicipalitySearch.module.js.map +1 -1
  115. package/dist/br/PixQRCode.cjs.map +1 -1
  116. package/dist/br/PixQRCode.js.map +1 -1
  117. package/dist/br/PixQRCode.module.cjs.map +1 -1
  118. package/dist/br/PixQRCode.module.js.map +1 -1
  119. package/dist/br/boleto.cjs.map +1 -1
  120. package/dist/br/boleto.js.map +1 -1
  121. package/dist/br/br-geo.cjs.map +1 -1
  122. package/dist/br/br-geo.js.map +1 -1
  123. package/dist/br/data/br-centroids.cjs.map +1 -1
  124. package/dist/br/data/br-centroids.js.map +1 -1
  125. package/dist/br/data/br-locations.cjs.map +1 -1
  126. package/dist/br/data/br-locations.js.map +1 -1
  127. package/dist/br/data/br-uf-geo.cjs.map +1 -1
  128. package/dist/br/data/br-uf-geo.js.map +1 -1
  129. package/dist/br/data/mun/AC.cjs.map +1 -1
  130. package/dist/br/data/mun/AC.js.map +1 -1
  131. package/dist/br/data/mun/AL.cjs.map +1 -1
  132. package/dist/br/data/mun/AL.js.map +1 -1
  133. package/dist/br/data/mun/AM.cjs.map +1 -1
  134. package/dist/br/data/mun/AM.js.map +1 -1
  135. package/dist/br/data/mun/AP.cjs.map +1 -1
  136. package/dist/br/data/mun/AP.js.map +1 -1
  137. package/dist/br/data/mun/BA.cjs.map +1 -1
  138. package/dist/br/data/mun/BA.js.map +1 -1
  139. package/dist/br/data/mun/CE.cjs.map +1 -1
  140. package/dist/br/data/mun/CE.js.map +1 -1
  141. package/dist/br/data/mun/DF.cjs.map +1 -1
  142. package/dist/br/data/mun/DF.js.map +1 -1
  143. package/dist/br/data/mun/ES.cjs.map +1 -1
  144. package/dist/br/data/mun/ES.js.map +1 -1
  145. package/dist/br/data/mun/GO.cjs.map +1 -1
  146. package/dist/br/data/mun/GO.js.map +1 -1
  147. package/dist/br/data/mun/MA.cjs.map +1 -1
  148. package/dist/br/data/mun/MA.js.map +1 -1
  149. package/dist/br/data/mun/MG.cjs.map +1 -1
  150. package/dist/br/data/mun/MG.js.map +1 -1
  151. package/dist/br/data/mun/MS.cjs.map +1 -1
  152. package/dist/br/data/mun/MS.js.map +1 -1
  153. package/dist/br/data/mun/MT.cjs.map +1 -1
  154. package/dist/br/data/mun/MT.js.map +1 -1
  155. package/dist/br/data/mun/PA.cjs.map +1 -1
  156. package/dist/br/data/mun/PA.js.map +1 -1
  157. package/dist/br/data/mun/PB.cjs.map +1 -1
  158. package/dist/br/data/mun/PB.js.map +1 -1
  159. package/dist/br/data/mun/PE.cjs.map +1 -1
  160. package/dist/br/data/mun/PE.js.map +1 -1
  161. package/dist/br/data/mun/PI.cjs.map +1 -1
  162. package/dist/br/data/mun/PI.js.map +1 -1
  163. package/dist/br/data/mun/PR.cjs.map +1 -1
  164. package/dist/br/data/mun/PR.js.map +1 -1
  165. package/dist/br/data/mun/RJ.cjs.map +1 -1
  166. package/dist/br/data/mun/RJ.js.map +1 -1
  167. package/dist/br/data/mun/RN.cjs.map +1 -1
  168. package/dist/br/data/mun/RN.js.map +1 -1
  169. package/dist/br/data/mun/RO.cjs.map +1 -1
  170. package/dist/br/data/mun/RO.js.map +1 -1
  171. package/dist/br/data/mun/RR.cjs.map +1 -1
  172. package/dist/br/data/mun/RR.js.map +1 -1
  173. package/dist/br/data/mun/RS.cjs.map +1 -1
  174. package/dist/br/data/mun/RS.js.map +1 -1
  175. package/dist/br/data/mun/SC.cjs.map +1 -1
  176. package/dist/br/data/mun/SC.js.map +1 -1
  177. package/dist/br/data/mun/SE.cjs.map +1 -1
  178. package/dist/br/data/mun/SE.js.map +1 -1
  179. package/dist/br/data/mun/SP.cjs.map +1 -1
  180. package/dist/br/data/mun/SP.js.map +1 -1
  181. package/dist/br/data/mun/TO.cjs.map +1 -1
  182. package/dist/br/data/mun/TO.js.map +1 -1
  183. package/dist/br/geocode.cjs.map +1 -1
  184. package/dist/br/geocode.js.map +1 -1
  185. package/dist/br/holidays.cjs.map +1 -1
  186. package/dist/br/holidays.js.map +1 -1
  187. package/dist/br/locations.cjs.map +1 -1
  188. package/dist/br/locations.js.map +1 -1
  189. package/dist/br/nfe.cjs.map +1 -1
  190. package/dist/br/nfe.js.map +1 -1
  191. package/dist/br/pix.cjs +1 -1
  192. package/dist/br/pix.cjs.map +1 -1
  193. package/dist/br/pix.js +108 -71
  194. package/dist/br/pix.js.map +1 -1
  195. package/dist/br/regions.cjs.map +1 -1
  196. package/dist/br/regions.js.map +1 -1
  197. package/dist/br/scales.cjs.map +1 -1
  198. package/dist/br/scales.js.map +1 -1
  199. package/dist/br/state-geo.cjs.map +1 -1
  200. package/dist/br/state-geo.js.map +1 -1
  201. package/dist/br/svg-utils.cjs.map +1 -1
  202. package/dist/br/svg-utils.js.map +1 -1
  203. package/dist/br/use-map-hover.cjs.map +1 -1
  204. package/dist/br/use-map-hover.js.map +1 -1
  205. package/dist/br/use-map-zoom.cjs.map +1 -1
  206. package/dist/br/use-map-zoom.js.map +1 -1
  207. package/dist/br/whatsapp.cjs.map +1 -1
  208. package/dist/br/whatsapp.js.map +1 -1
  209. package/dist/br.cjs.map +1 -1
  210. package/dist/br.d.ts +16 -5
  211. package/dist/br.js.map +1 -1
  212. package/dist/capture/barcode.cjs.map +1 -1
  213. package/dist/capture/barcode.js.map +1 -1
  214. package/dist/capture/media-recorder.cjs.map +1 -1
  215. package/dist/capture/media-recorder.js.map +1 -1
  216. package/dist/capture/use-barcode-scanner.cjs.map +1 -1
  217. package/dist/capture/use-barcode-scanner.js.map +1 -1
  218. package/dist/capture/use-screen-capture.cjs +1 -1
  219. package/dist/capture/use-screen-capture.cjs.map +1 -1
  220. package/dist/capture/use-screen-capture.js +1 -7
  221. package/dist/capture/use-screen-capture.js.map +1 -1
  222. package/dist/capture/use-speech-recognition.cjs +1 -1
  223. package/dist/capture/use-speech-recognition.cjs.map +1 -1
  224. package/dist/capture/use-speech-recognition.js +35 -36
  225. package/dist/capture/use-speech-recognition.js.map +1 -1
  226. package/dist/capture/use-torch.cjs.map +1 -1
  227. package/dist/capture/use-torch.js.map +1 -1
  228. package/dist/capture/use-video-recorder.cjs.map +1 -1
  229. package/dist/capture/use-video-recorder.js.map +1 -1
  230. package/dist/capture/video-recorder.cjs.map +1 -1
  231. package/dist/capture/video-recorder.js.map +1 -1
  232. package/dist/charts/AreaChart.cjs.map +1 -1
  233. package/dist/charts/AreaChart.js.map +1 -1
  234. package/dist/charts/BarChart.cjs.map +1 -1
  235. package/dist/charts/BarChart.js.map +1 -1
  236. package/dist/charts/LineChart.cjs.map +1 -1
  237. package/dist/charts/LineChart.js.map +1 -1
  238. package/dist/charts/PieChart.cjs.map +1 -1
  239. package/dist/charts/PieChart.js.map +1 -1
  240. package/dist/charts/RadarChart.cjs.map +1 -1
  241. package/dist/charts/RadarChart.js.map +1 -1
  242. package/dist/charts/palette.cjs.map +1 -1
  243. package/dist/charts/palette.js.map +1 -1
  244. package/dist/charts/scales.cjs.map +1 -1
  245. package/dist/charts/scales.js.map +1 -1
  246. package/dist/charts/types.cjs.map +1 -1
  247. package/dist/charts/types.js.map +1 -1
  248. package/dist/charts/use-chart-colors.cjs.map +1 -1
  249. package/dist/charts/use-chart-colors.js.map +1 -1
  250. package/dist/components/AIChat/AIChat.cjs +1 -1
  251. package/dist/components/AIChat/AIChat.cjs.map +1 -1
  252. package/dist/components/AIChat/AIChat.js +1 -5
  253. package/dist/components/AIChat/AIChat.js.map +1 -1
  254. package/dist/components/AIChat/AIChat.module.cjs.map +1 -1
  255. package/dist/components/AIChat/AIChat.module.js.map +1 -1
  256. package/dist/components/AIChat/AIChatComposer.cjs +1 -1
  257. package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
  258. package/dist/components/AIChat/AIChatComposer.js +1 -7
  259. package/dist/components/AIChat/AIChatComposer.js.map +1 -1
  260. package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
  261. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  262. package/dist/components/AIChat/ai-chat-turns.cjs.map +1 -1
  263. package/dist/components/AIChat/ai-chat-turns.js.map +1 -1
  264. package/dist/components/Accordion/Accordion.cjs.map +1 -1
  265. package/dist/components/Accordion/Accordion.js.map +1 -1
  266. package/dist/components/Accordion/Accordion.module.cjs.map +1 -1
  267. package/dist/components/Accordion/Accordion.module.js.map +1 -1
  268. package/dist/components/Alert/Alert.cjs.map +1 -1
  269. package/dist/components/Alert/Alert.js.map +1 -1
  270. package/dist/components/Alert/Alert.module.cjs.map +1 -1
  271. package/dist/components/Alert/Alert.module.js.map +1 -1
  272. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  273. package/dist/components/AppBar/AppBar.js.map +1 -1
  274. package/dist/components/AppBar/AppBar.module.cjs.map +1 -1
  275. package/dist/components/AppBar/AppBar.module.js.map +1 -1
  276. package/dist/components/AppBar/use-sticky-body-warning.cjs +1 -1
  277. package/dist/components/AppBar/use-sticky-body-warning.cjs.map +1 -1
  278. package/dist/components/AppBar/use-sticky-body-warning.js +1 -1
  279. package/dist/components/AppBar/use-sticky-body-warning.js.map +1 -1
  280. package/dist/components/AppShell/AppShell.cjs.map +1 -1
  281. package/dist/components/AppShell/AppShell.js.map +1 -1
  282. package/dist/components/AppShell/AppShell.module.cjs.map +1 -1
  283. package/dist/components/AppShell/AppShell.module.js.map +1 -1
  284. package/dist/components/AspectRatio/AspectRatio.cjs.map +1 -1
  285. package/dist/components/AspectRatio/AspectRatio.js.map +1 -1
  286. package/dist/components/AspectRatio/AspectRatio.module.cjs.map +1 -1
  287. package/dist/components/AspectRatio/AspectRatio.module.js.map +1 -1
  288. package/dist/components/AudioPlayer/AudioPlayer.cjs +1 -1
  289. package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
  290. package/dist/components/AudioPlayer/AudioPlayer.js +1 -7
  291. package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
  292. package/dist/components/AudioPlayer/AudioPlayer.module.cjs.map +1 -1
  293. package/dist/components/AudioPlayer/AudioPlayer.module.js.map +1 -1
  294. package/dist/components/AudioRecorder/AudioRecorder.cjs +1 -1
  295. package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
  296. package/dist/components/AudioRecorder/AudioRecorder.js +16 -21
  297. package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
  298. package/dist/components/AudioRecorder/AudioRecorder.module.cjs.map +1 -1
  299. package/dist/components/AudioRecorder/AudioRecorder.module.js.map +1 -1
  300. package/dist/components/Avatar/Avatar.cjs.map +1 -1
  301. package/dist/components/Avatar/Avatar.js.map +1 -1
  302. package/dist/components/Avatar/Avatar.module.cjs.map +1 -1
  303. package/dist/components/Avatar/Avatar.module.js.map +1 -1
  304. package/dist/components/AvatarGroup/AvatarGroup.cjs.map +1 -1
  305. package/dist/components/AvatarGroup/AvatarGroup.js.map +1 -1
  306. package/dist/components/AvatarGroup/AvatarGroup.module.cjs.map +1 -1
  307. package/dist/components/AvatarGroup/AvatarGroup.module.js.map +1 -1
  308. package/dist/components/Badge/Badge.cjs.map +1 -1
  309. package/dist/components/Badge/Badge.js.map +1 -1
  310. package/dist/components/Badge/Badge.module.cjs.map +1 -1
  311. package/dist/components/Badge/Badge.module.js.map +1 -1
  312. package/dist/components/Banner/Banner.cjs.map +1 -1
  313. package/dist/components/Banner/Banner.js.map +1 -1
  314. package/dist/components/Banner/Banner.module.cjs.map +1 -1
  315. package/dist/components/Banner/Banner.module.js.map +1 -1
  316. package/dist/components/BarList/BarList.cjs.map +1 -1
  317. package/dist/components/BarList/BarList.js.map +1 -1
  318. package/dist/components/BarList/BarList.module.cjs.map +1 -1
  319. package/dist/components/BarList/BarList.module.js.map +1 -1
  320. package/dist/components/BarList/bar-list-model.cjs.map +1 -1
  321. package/dist/components/BarList/bar-list-model.js.map +1 -1
  322. package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
  323. package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
  324. package/dist/components/BarcodeScanner/BarcodeScanner.module.cjs.map +1 -1
  325. package/dist/components/BarcodeScanner/BarcodeScanner.module.js.map +1 -1
  326. package/dist/components/BottomNavigation/BottomNavigation.cjs.map +1 -1
  327. package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -1
  328. package/dist/components/BottomNavigation/BottomNavigation.module.cjs.map +1 -1
  329. package/dist/components/BottomNavigation/BottomNavigation.module.js.map +1 -1
  330. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  331. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  332. package/dist/components/BottomSheet/BottomSheet.module.cjs.map +1 -1
  333. package/dist/components/BottomSheet/BottomSheet.module.js.map +1 -1
  334. package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
  335. package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  336. package/dist/components/Breadcrumbs/Breadcrumbs.module.cjs.map +1 -1
  337. package/dist/components/Breadcrumbs/Breadcrumbs.module.js.map +1 -1
  338. package/dist/components/Button/Button.cjs.map +1 -1
  339. package/dist/components/Button/Button.js.map +1 -1
  340. package/dist/components/Button/Button.module.cjs.map +1 -1
  341. package/dist/components/Button/Button.module.js.map +1 -1
  342. package/dist/components/Button/ButtonContent.cjs.map +1 -1
  343. package/dist/components/Button/ButtonContent.js.map +1 -1
  344. package/dist/components/Button/ButtonSlot.cjs.map +1 -1
  345. package/dist/components/Button/ButtonSlot.js.map +1 -1
  346. package/dist/components/Button/button-class.cjs.map +1 -1
  347. package/dist/components/Button/button-class.js.map +1 -1
  348. package/dist/components/Button/link-rel.cjs.map +1 -1
  349. package/dist/components/Button/link-rel.js.map +1 -1
  350. package/dist/components/Button/slot.cjs.map +1 -1
  351. package/dist/components/Button/slot.js.map +1 -1
  352. package/dist/components/Calendar/Calendar.cjs.map +1 -1
  353. package/dist/components/Calendar/Calendar.js.map +1 -1
  354. package/dist/components/Calendar/Calendar.module.cjs.map +1 -1
  355. package/dist/components/Calendar/Calendar.module.js.map +1 -1
  356. package/dist/components/Card/Card.cjs.map +1 -1
  357. package/dist/components/Card/Card.js.map +1 -1
  358. package/dist/components/Card/Card.module.cjs.map +1 -1
  359. package/dist/components/Card/Card.module.js.map +1 -1
  360. package/dist/components/Carousel/Carousel.cjs.map +1 -1
  361. package/dist/components/Carousel/Carousel.js.map +1 -1
  362. package/dist/components/Carousel/Carousel.module.cjs.map +1 -1
  363. package/dist/components/Carousel/Carousel.module.js.map +1 -1
  364. package/dist/components/Center/Center.cjs.map +1 -1
  365. package/dist/components/Center/Center.js.map +1 -1
  366. package/dist/components/Center/Center.module.cjs.map +1 -1
  367. package/dist/components/Center/Center.module.js.map +1 -1
  368. package/dist/components/Chat/Chat.cjs.map +1 -1
  369. package/dist/components/Chat/Chat.js.map +1 -1
  370. package/dist/components/Chat/Chat.module.cjs.map +1 -1
  371. package/dist/components/Chat/Chat.module.js.map +1 -1
  372. package/dist/components/Chat/ChatActionsMenu.cjs.map +1 -1
  373. package/dist/components/Chat/ChatActionsMenu.js.map +1 -1
  374. package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
  375. package/dist/components/Chat/ChatAttachments.js.map +1 -1
  376. package/dist/components/Chat/ChatBubble.cjs.map +1 -1
  377. package/dist/components/Chat/ChatBubble.js.map +1 -1
  378. package/dist/components/Chat/ChatComposer.cjs.map +1 -1
  379. package/dist/components/Chat/ChatComposer.js.map +1 -1
  380. package/dist/components/Chat/chat-groups.cjs +1 -1
  381. package/dist/components/Chat/chat-groups.cjs.map +1 -1
  382. package/dist/components/Chat/chat-groups.js +2 -6
  383. package/dist/components/Chat/chat-groups.js.map +1 -1
  384. package/dist/components/Chat/chat-receipt.cjs.map +1 -1
  385. package/dist/components/Chat/chat-receipt.js.map +1 -1
  386. package/dist/components/Checkbox/Checkbox.cjs.map +1 -1
  387. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  388. package/dist/components/Checkbox/Checkbox.module.cjs.map +1 -1
  389. package/dist/components/Checkbox/Checkbox.module.js.map +1 -1
  390. package/dist/components/ChipInput/ChipInput.cjs +1 -1
  391. package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
  392. package/dist/components/ChipInput/ChipInput.js +2 -12
  393. package/dist/components/ChipInput/ChipInput.js.map +1 -1
  394. package/dist/components/ChipInput/ChipInput.module.cjs.map +1 -1
  395. package/dist/components/ChipInput/ChipInput.module.js.map +1 -1
  396. package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
  397. package/dist/components/ClickOutside/ClickOutside.js.map +1 -1
  398. package/dist/components/CodeBlock/CodeBlock.cjs +1 -1
  399. package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
  400. package/dist/components/CodeBlock/CodeBlock.js +1 -1
  401. package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
  402. package/dist/components/CodeBlock/CodeBlock.module.cjs.map +1 -1
  403. package/dist/components/CodeBlock/CodeBlock.module.js.map +1 -1
  404. package/dist/components/CodeBlock/tokenize.cjs +1 -1
  405. package/dist/components/CodeBlock/tokenize.cjs.map +1 -1
  406. package/dist/components/CodeBlock/tokenize.js +54 -25
  407. package/dist/components/CodeBlock/tokenize.js.map +1 -1
  408. package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
  409. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  410. package/dist/components/Collapsible/Collapsible.module.cjs.map +1 -1
  411. package/dist/components/Collapsible/Collapsible.module.js.map +1 -1
  412. package/dist/components/Combobox/Combobox.cjs.map +1 -1
  413. package/dist/components/Combobox/Combobox.js.map +1 -1
  414. package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
  415. package/dist/components/Combobox/Combobox.module.js.map +1 -1
  416. package/dist/components/Combobox/use-listbox-dismiss.cjs.map +1 -1
  417. package/dist/components/Combobox/use-listbox-dismiss.js.map +1 -1
  418. package/dist/components/Command/Command.cjs +1 -1
  419. package/dist/components/Command/Command.cjs.map +1 -1
  420. package/dist/components/Command/Command.js +4 -11
  421. package/dist/components/Command/Command.js.map +1 -1
  422. package/dist/components/Command/Command.module.cjs.map +1 -1
  423. package/dist/components/Command/Command.module.js.map +1 -1
  424. package/dist/components/ConditionalWrapper/ConditionalWrapper.cjs.map +1 -1
  425. package/dist/components/ConditionalWrapper/ConditionalWrapper.js.map +1 -1
  426. package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
  427. package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
  428. package/dist/components/ContextMenu/ContextMenu.cjs +1 -1
  429. package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
  430. package/dist/components/ContextMenu/ContextMenu.js +1 -1
  431. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  432. package/dist/components/ContextMenu/ContextMenu.module.cjs.map +1 -1
  433. package/dist/components/ContextMenu/ContextMenu.module.js.map +1 -1
  434. package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
  435. package/dist/components/CopyButton/CopyButton.js.map +1 -1
  436. package/dist/components/CopyButton/CopyButton.module.cjs.map +1 -1
  437. package/dist/components/CopyButton/CopyButton.module.js.map +1 -1
  438. package/dist/components/DataList/DataList.cjs.map +1 -1
  439. package/dist/components/DataList/DataList.js.map +1 -1
  440. package/dist/components/DataList/DataList.module.cjs.map +1 -1
  441. package/dist/components/DataList/DataList.module.js.map +1 -1
  442. package/dist/components/DataTable/DataTable.cjs +1 -1
  443. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  444. package/dist/components/DataTable/DataTable.js +6 -14
  445. package/dist/components/DataTable/DataTable.js.map +1 -1
  446. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  447. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  448. package/dist/components/DataTable/EditableCell.cjs +1 -1
  449. package/dist/components/DataTable/EditableCell.cjs.map +1 -1
  450. package/dist/components/DataTable/EditableCell.js +1 -9
  451. package/dist/components/DataTable/EditableCell.js.map +1 -1
  452. package/dist/components/DataTable/LoadingRows.cjs.map +1 -1
  453. package/dist/components/DataTable/LoadingRows.js.map +1 -1
  454. package/dist/components/DataTable/edit-labels.cjs.map +1 -1
  455. package/dist/components/DataTable/edit-labels.js.map +1 -1
  456. package/dist/components/DataTable/use-dev-warnings.cjs.map +1 -1
  457. package/dist/components/DataTable/use-dev-warnings.js.map +1 -1
  458. package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
  459. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  460. package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
  461. package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
  462. package/dist/components/DateRangePicker/DateRangePicker.module.cjs.map +1 -1
  463. package/dist/components/DateRangePicker/DateRangePicker.module.js.map +1 -1
  464. package/dist/components/DescriptionList/DescriptionList.cjs.map +1 -1
  465. package/dist/components/DescriptionList/DescriptionList.js.map +1 -1
  466. package/dist/components/DescriptionList/DescriptionList.module.cjs.map +1 -1
  467. package/dist/components/DescriptionList/DescriptionList.module.js.map +1 -1
  468. package/dist/components/Divider/Divider.cjs.map +1 -1
  469. package/dist/components/Divider/Divider.js.map +1 -1
  470. package/dist/components/Divider/Divider.module.cjs.map +1 -1
  471. package/dist/components/Divider/Divider.module.js.map +1 -1
  472. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  473. package/dist/components/Drawer/Drawer.js.map +1 -1
  474. package/dist/components/Drawer/Drawer.module.cjs.map +1 -1
  475. package/dist/components/Drawer/Drawer.module.js.map +1 -1
  476. package/dist/components/DropdownMenu/DropdownMenu.cjs +1 -1
  477. package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  478. package/dist/components/DropdownMenu/DropdownMenu.js +2 -14
  479. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  480. package/dist/components/DropdownMenu/DropdownMenu.module.cjs.map +1 -1
  481. package/dist/components/DropdownMenu/DropdownMenu.module.js.map +1 -1
  482. package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
  483. package/dist/components/Dropzone/Dropzone.js.map +1 -1
  484. package/dist/components/Dropzone/Dropzone.module.cjs.map +1 -1
  485. package/dist/components/Dropzone/Dropzone.module.js.map +1 -1
  486. package/dist/components/EmptyState/EmptyState.cjs.map +1 -1
  487. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  488. package/dist/components/EmptyState/EmptyState.module.cjs.map +1 -1
  489. package/dist/components/EmptyState/EmptyState.module.js.map +1 -1
  490. package/dist/components/ErrorState/ErrorState.cjs.map +1 -1
  491. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  492. package/dist/components/ErrorState/ErrorState.module.cjs.map +1 -1
  493. package/dist/components/ErrorState/ErrorState.module.js.map +1 -1
  494. package/dist/components/ErrorText/ErrorText.cjs.map +1 -1
  495. package/dist/components/ErrorText/ErrorText.js.map +1 -1
  496. package/dist/components/ErrorText/ErrorText.module.cjs.map +1 -1
  497. package/dist/components/ErrorText/ErrorText.module.js.map +1 -1
  498. package/dist/components/FileUpload/FileUpload.cjs +1 -1
  499. package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
  500. package/dist/components/FileUpload/FileUpload.js +7 -17
  501. package/dist/components/FileUpload/FileUpload.js.map +1 -1
  502. package/dist/components/FileUpload/FileUpload.module.cjs.map +1 -1
  503. package/dist/components/FileUpload/FileUpload.module.js.map +1 -1
  504. package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
  505. package/dist/components/FilterBar/FilterBar.js.map +1 -1
  506. package/dist/components/FilterBar/FilterBar.module.cjs.map +1 -1
  507. package/dist/components/FilterBar/FilterBar.module.js.map +1 -1
  508. package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
  509. package/dist/components/FilterBar/filter-apply.js.map +1 -1
  510. package/dist/components/FilterBar/filter-model.cjs.map +1 -1
  511. package/dist/components/FilterBar/filter-model.js.map +1 -1
  512. package/dist/components/FilterBar/filter-query.cjs +1 -1
  513. package/dist/components/FilterBar/filter-query.cjs.map +1 -1
  514. package/dist/components/FilterBar/filter-query.js +2 -5
  515. package/dist/components/FilterBar/filter-query.js.map +1 -1
  516. package/dist/components/FilterPanel/FilterPanel.cjs.map +1 -1
  517. package/dist/components/FilterPanel/FilterPanel.js.map +1 -1
  518. package/dist/components/FilterPanel/FilterPanel.module.cjs.map +1 -1
  519. package/dist/components/FilterPanel/FilterPanel.module.js.map +1 -1
  520. package/dist/components/FloatingActionButton/FloatingActionButton.cjs.map +1 -1
  521. package/dist/components/FloatingActionButton/FloatingActionButton.js.map +1 -1
  522. package/dist/components/FloatingActionButton/FloatingActionButton.module.cjs.map +1 -1
  523. package/dist/components/FloatingActionButton/FloatingActionButton.module.js.map +1 -1
  524. package/dist/components/For/For.cjs.map +1 -1
  525. package/dist/components/For/For.js.map +1 -1
  526. package/dist/components/Form/Form.cjs.map +1 -1
  527. package/dist/components/Form/Form.js.map +1 -1
  528. package/dist/components/Form/Form.module.cjs.map +1 -1
  529. package/dist/components/Form/Form.module.js.map +1 -1
  530. package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
  531. package/dist/components/HoverCard/HoverCard.js.map +1 -1
  532. package/dist/components/HoverCard/HoverCard.module.cjs.map +1 -1
  533. package/dist/components/HoverCard/HoverCard.module.js.map +1 -1
  534. package/dist/components/Image/Image.cjs.map +1 -1
  535. package/dist/components/Image/Image.js.map +1 -1
  536. package/dist/components/Image/Image.module.cjs.map +1 -1
  537. package/dist/components/Image/Image.module.js.map +1 -1
  538. package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
  539. package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
  540. package/dist/components/ImageCropper/ImageCropper.js +4 -8
  541. package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
  542. package/dist/components/ImageCropper/ImageCropper.module.cjs.map +1 -1
  543. package/dist/components/ImageCropper/ImageCropper.module.js.map +1 -1
  544. package/dist/components/ImageCropper/crop-geometry.cjs.map +1 -1
  545. package/dist/components/ImageCropper/crop-geometry.js.map +1 -1
  546. package/dist/components/Input/Input.cjs.map +1 -1
  547. package/dist/components/Input/Input.js.map +1 -1
  548. package/dist/components/Input/Input.module.cjs.map +1 -1
  549. package/dist/components/Input/Input.module.js.map +1 -1
  550. package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
  551. package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
  552. package/dist/components/InstallBanner/InstallBanner.module.cjs.map +1 -1
  553. package/dist/components/InstallBanner/InstallBanner.module.js.map +1 -1
  554. package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
  555. package/dist/components/InstallButton/InstallButton.js.map +1 -1
  556. package/dist/components/InstallButton/InstallButton.module.cjs.map +1 -1
  557. package/dist/components/InstallButton/InstallButton.module.js.map +1 -1
  558. package/dist/components/InstallButton/install-hints.cjs.map +1 -1
  559. package/dist/components/InstallButton/install-hints.js.map +1 -1
  560. package/dist/components/Kanban/Kanban.cjs.map +1 -1
  561. package/dist/components/Kanban/Kanban.js.map +1 -1
  562. package/dist/components/Kanban/Kanban.module.cjs.map +1 -1
  563. package/dist/components/Kanban/Kanban.module.js.map +1 -1
  564. package/dist/components/Kbd/Kbd.cjs.map +1 -1
  565. package/dist/components/Kbd/Kbd.js.map +1 -1
  566. package/dist/components/Kbd/Kbd.module.cjs.map +1 -1
  567. package/dist/components/Kbd/Kbd.module.js.map +1 -1
  568. package/dist/components/Label/Label.cjs.map +1 -1
  569. package/dist/components/Label/Label.js.map +1 -1
  570. package/dist/components/Label/Label.module.cjs.map +1 -1
  571. package/dist/components/Label/Label.module.js.map +1 -1
  572. package/dist/components/Layout/Layout.cjs.map +1 -1
  573. package/dist/components/Layout/Layout.js.map +1 -1
  574. package/dist/components/Layout/Layout.module.cjs.map +1 -1
  575. package/dist/components/Layout/Layout.module.js.map +1 -1
  576. package/dist/components/Lightbox/Lightbox.cjs +1 -1
  577. package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
  578. package/dist/components/Lightbox/Lightbox.js +1 -1
  579. package/dist/components/Lightbox/Lightbox.js.map +1 -1
  580. package/dist/components/Lightbox/Lightbox.module.cjs.map +1 -1
  581. package/dist/components/Lightbox/Lightbox.module.js.map +1 -1
  582. package/dist/components/ListTile/ListTile.cjs.map +1 -1
  583. package/dist/components/ListTile/ListTile.js.map +1 -1
  584. package/dist/components/ListTile/ListTile.module.cjs.map +1 -1
  585. package/dist/components/ListTile/ListTile.module.js.map +1 -1
  586. package/dist/components/Markdown/Markdown.cjs.map +1 -1
  587. package/dist/components/Markdown/Markdown.js.map +1 -1
  588. package/dist/components/Markdown/Markdown.module.cjs.map +1 -1
  589. package/dist/components/Markdown/Markdown.module.js.map +1 -1
  590. package/dist/components/Markdown/markdown-parse.cjs +6 -6
  591. package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
  592. package/dist/components/Markdown/markdown-parse.js +94 -96
  593. package/dist/components/Markdown/markdown-parse.js.map +1 -1
  594. package/dist/components/Masonry/Masonry.cjs.map +1 -1
  595. package/dist/components/Masonry/Masonry.js.map +1 -1
  596. package/dist/components/Masonry/Masonry.module.cjs.map +1 -1
  597. package/dist/components/Masonry/Masonry.module.js.map +1 -1
  598. package/dist/components/Masonry/masonry-layout.cjs.map +1 -1
  599. package/dist/components/Masonry/masonry-layout.js.map +1 -1
  600. package/dist/components/Menubar/Menubar.cjs.map +1 -1
  601. package/dist/components/Menubar/Menubar.js.map +1 -1
  602. package/dist/components/Menubar/Menubar.module.cjs.map +1 -1
  603. package/dist/components/Menubar/Menubar.module.js.map +1 -1
  604. package/dist/components/Modal/Modal.cjs.map +1 -1
  605. package/dist/components/Modal/Modal.js.map +1 -1
  606. package/dist/components/Modal/Modal.module.cjs.map +1 -1
  607. package/dist/components/Modal/Modal.module.js.map +1 -1
  608. package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
  609. package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
  610. package/dist/components/ModalsManager/ModalsManager.module.cjs.map +1 -1
  611. package/dist/components/ModalsManager/ModalsManager.module.js.map +1 -1
  612. package/dist/components/Money/Money.cjs.map +1 -1
  613. package/dist/components/Money/Money.js.map +1 -1
  614. package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
  615. package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
  616. package/dist/components/MultiSelect/MultiSelect.module.cjs.map +1 -1
  617. package/dist/components/MultiSelect/MultiSelect.module.js.map +1 -1
  618. package/dist/components/NProgress/NProgress.cjs.map +1 -1
  619. package/dist/components/NProgress/NProgress.js.map +1 -1
  620. package/dist/components/NProgress/NProgress.module.cjs.map +1 -1
  621. package/dist/components/NProgress/NProgress.module.js.map +1 -1
  622. package/dist/components/Navbar/Navbar.cjs.map +1 -1
  623. package/dist/components/Navbar/Navbar.js.map +1 -1
  624. package/dist/components/Navbar/Navbar.module.cjs.map +1 -1
  625. package/dist/components/Navbar/Navbar.module.js.map +1 -1
  626. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  627. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  628. package/dist/components/NavigationMenu/NavigationMenu.module.cjs.map +1 -1
  629. package/dist/components/NavigationMenu/NavigationMenu.module.js.map +1 -1
  630. package/dist/components/NavigationRail/NavigationRail.cjs.map +1 -1
  631. package/dist/components/NavigationRail/NavigationRail.js.map +1 -1
  632. package/dist/components/NavigationRail/NavigationRail.module.cjs.map +1 -1
  633. package/dist/components/NavigationRail/NavigationRail.module.js.map +1 -1
  634. package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
  635. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
  636. package/dist/components/NotificationCenter/NotificationCenter.module.cjs.map +1 -1
  637. package/dist/components/NotificationCenter/NotificationCenter.module.js.map +1 -1
  638. package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -1
  639. package/dist/components/NotificationCenter/use-notification-inbox.js.map +1 -1
  640. package/dist/components/OfflineIndicator/OfflineIndicator.cjs.map +1 -1
  641. package/dist/components/OfflineIndicator/OfflineIndicator.js.map +1 -1
  642. package/dist/components/OfflineIndicator/OfflineIndicator.module.cjs.map +1 -1
  643. package/dist/components/OfflineIndicator/OfflineIndicator.module.js.map +1 -1
  644. package/dist/components/Page/Page.cjs.map +1 -1
  645. package/dist/components/Page/Page.js.map +1 -1
  646. package/dist/components/Page/Page.module.cjs.map +1 -1
  647. package/dist/components/Page/Page.module.js.map +1 -1
  648. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  649. package/dist/components/Pagination/Pagination.js.map +1 -1
  650. package/dist/components/Pagination/Pagination.module.cjs.map +1 -1
  651. package/dist/components/Pagination/Pagination.module.js.map +1 -1
  652. package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
  653. package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
  654. package/dist/components/PasswordInput/PasswordInput.module.cjs.map +1 -1
  655. package/dist/components/PasswordInput/PasswordInput.module.js.map +1 -1
  656. package/dist/components/PinInput/PinInput.cjs.map +1 -1
  657. package/dist/components/PinInput/PinInput.js.map +1 -1
  658. package/dist/components/PinInput/PinInput.module.cjs.map +1 -1
  659. package/dist/components/PinInput/PinInput.module.js.map +1 -1
  660. package/dist/components/Popover/Popover.cjs.map +1 -1
  661. package/dist/components/Popover/Popover.js.map +1 -1
  662. package/dist/components/Popover/Popover.module.cjs.map +1 -1
  663. package/dist/components/Popover/Popover.module.js.map +1 -1
  664. package/dist/components/Portal/Portal.cjs.map +1 -1
  665. package/dist/components/Portal/Portal.js.map +1 -1
  666. package/dist/components/Portal/anchor-position.cjs.map +1 -1
  667. package/dist/components/Portal/anchor-position.js.map +1 -1
  668. package/dist/components/Portal/escape-layer.cjs.map +1 -1
  669. package/dist/components/Portal/escape-layer.js.map +1 -1
  670. package/dist/components/Portal/portal-host.cjs.map +1 -1
  671. package/dist/components/Portal/portal-host.js.map +1 -1
  672. package/dist/components/Portal/tab-bridge.cjs +1 -1
  673. package/dist/components/Portal/tab-bridge.cjs.map +1 -1
  674. package/dist/components/Portal/tab-bridge.js +1 -5
  675. package/dist/components/Portal/tab-bridge.js.map +1 -1
  676. package/dist/components/Progress/Progress.cjs.map +1 -1
  677. package/dist/components/Progress/Progress.js.map +1 -1
  678. package/dist/components/Progress/Progress.module.cjs.map +1 -1
  679. package/dist/components/Progress/Progress.module.js.map +1 -1
  680. package/dist/components/QRCode/QRCode.cjs.map +1 -1
  681. package/dist/components/QRCode/QRCode.js.map +1 -1
  682. package/dist/components/QRCode/QRCode.module.cjs.map +1 -1
  683. package/dist/components/QRCode/QRCode.module.js.map +1 -1
  684. package/dist/components/QRCode/qr-encode.cjs +1 -1
  685. package/dist/components/QRCode/qr-encode.cjs.map +1 -1
  686. package/dist/components/QRCode/qr-encode.js +7 -13
  687. package/dist/components/QRCode/qr-encode.js.map +1 -1
  688. package/dist/components/QRCode/qr-tables.cjs.map +1 -1
  689. package/dist/components/QRCode/qr-tables.js.map +1 -1
  690. package/dist/components/Radio/Radio.cjs.map +1 -1
  691. package/dist/components/Radio/Radio.js.map +1 -1
  692. package/dist/components/Radio/Radio.module.cjs.map +1 -1
  693. package/dist/components/Radio/Radio.module.js.map +1 -1
  694. package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
  695. package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
  696. package/dist/components/RangeSlider/RangeSlider.module.cjs.map +1 -1
  697. package/dist/components/RangeSlider/RangeSlider.module.js.map +1 -1
  698. package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
  699. package/dist/components/RatingStars/RatingStars.js.map +1 -1
  700. package/dist/components/RatingStars/RatingStars.module.cjs.map +1 -1
  701. package/dist/components/RatingStars/RatingStars.module.js.map +1 -1
  702. package/dist/components/RefreshIndicator/RefreshIndicator.cjs +1 -1
  703. package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
  704. package/dist/components/RefreshIndicator/RefreshIndicator.js +10 -18
  705. package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
  706. package/dist/components/RefreshIndicator/RefreshIndicator.module.cjs.map +1 -1
  707. package/dist/components/RefreshIndicator/RefreshIndicator.module.js.map +1 -1
  708. package/dist/components/RelativeTime/RelativeTime.cjs.map +1 -1
  709. package/dist/components/RelativeTime/RelativeTime.js.map +1 -1
  710. package/dist/components/Resizable/Resizable.cjs +1 -1
  711. package/dist/components/Resizable/Resizable.cjs.map +1 -1
  712. package/dist/components/Resizable/Resizable.js +1 -9
  713. package/dist/components/Resizable/Resizable.js.map +1 -1
  714. package/dist/components/Resizable/Resizable.module.cjs.map +1 -1
  715. package/dist/components/Resizable/Resizable.module.js.map +1 -1
  716. package/dist/components/Responsive/Responsive.cjs.map +1 -1
  717. package/dist/components/Responsive/Responsive.js.map +1 -1
  718. package/dist/components/Reveal/Reveal.cjs.map +1 -1
  719. package/dist/components/Reveal/Reveal.js.map +1 -1
  720. package/dist/components/Reveal/Reveal.module.cjs.map +1 -1
  721. package/dist/components/Reveal/Reveal.module.js.map +1 -1
  722. package/dist/components/Reveal/reveal-threshold.cjs.map +1 -1
  723. package/dist/components/Reveal/reveal-threshold.js.map +1 -1
  724. package/dist/components/SafeArea/SafeArea.cjs.map +1 -1
  725. package/dist/components/SafeArea/SafeArea.js.map +1 -1
  726. package/dist/components/SafeArea/SafeArea.module.cjs.map +1 -1
  727. package/dist/components/SafeArea/SafeArea.module.js.map +1 -1
  728. package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
  729. package/dist/components/Scheduler/Scheduler.js.map +1 -1
  730. package/dist/components/Scheduler/Scheduler.module.cjs.map +1 -1
  731. package/dist/components/Scheduler/Scheduler.module.js.map +1 -1
  732. package/dist/components/Scheduler/scheduler-layout.cjs.map +1 -1
  733. package/dist/components/Scheduler/scheduler-layout.js.map +1 -1
  734. package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
  735. package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
  736. package/dist/components/ScrollArea/ScrollArea.module.cjs.map +1 -1
  737. package/dist/components/ScrollArea/ScrollArea.module.js.map +1 -1
  738. package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
  739. package/dist/components/SearchBar/SearchBar.js.map +1 -1
  740. package/dist/components/SearchBar/SearchBar.module.cjs.map +1 -1
  741. package/dist/components/SearchBar/SearchBar.module.js.map +1 -1
  742. package/dist/components/SectionHeader/SectionHeader.cjs.map +1 -1
  743. package/dist/components/SectionHeader/SectionHeader.js.map +1 -1
  744. package/dist/components/SectionHeader/SectionHeader.module.cjs.map +1 -1
  745. package/dist/components/SectionHeader/SectionHeader.module.js.map +1 -1
  746. package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
  747. package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
  748. package/dist/components/SegmentedControl/SegmentedControl.module.cjs.map +1 -1
  749. package/dist/components/SegmentedControl/SegmentedControl.module.js.map +1 -1
  750. package/dist/components/Select/CaretIcon.cjs.map +1 -1
  751. package/dist/components/Select/CaretIcon.js.map +1 -1
  752. package/dist/components/Select/Select.cjs.map +1 -1
  753. package/dist/components/Select/Select.js.map +1 -1
  754. package/dist/components/Select/Select.module.cjs.map +1 -1
  755. package/dist/components/Select/Select.module.js.map +1 -1
  756. package/dist/components/Select/options.cjs.map +1 -1
  757. package/dist/components/Select/options.js.map +1 -1
  758. package/dist/components/Sidebar/Sidebar.cjs +1 -1
  759. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  760. package/dist/components/Sidebar/Sidebar.js +13 -23
  761. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  762. package/dist/components/Sidebar/Sidebar.module.cjs.map +1 -1
  763. package/dist/components/Sidebar/Sidebar.module.js.map +1 -1
  764. package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
  765. package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
  766. package/dist/components/Sidebar/active-nav-key.cjs.map +1 -1
  767. package/dist/components/Sidebar/active-nav-key.js.map +1 -1
  768. package/dist/components/SignaturePad/SignaturePad.cjs +1 -1
  769. package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
  770. package/dist/components/SignaturePad/SignaturePad.js +1 -5
  771. package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
  772. package/dist/components/SignaturePad/SignaturePad.module.cjs.map +1 -1
  773. package/dist/components/SignaturePad/SignaturePad.module.js.map +1 -1
  774. package/dist/components/Skeleton/Skeleton.cjs.map +1 -1
  775. package/dist/components/Skeleton/Skeleton.js.map +1 -1
  776. package/dist/components/Skeleton/Skeleton.module.cjs.map +1 -1
  777. package/dist/components/Skeleton/Skeleton.module.js.map +1 -1
  778. package/dist/components/Slider/Slider.cjs.map +1 -1
  779. package/dist/components/Slider/Slider.js.map +1 -1
  780. package/dist/components/Slider/Slider.module.cjs.map +1 -1
  781. package/dist/components/Slider/Slider.module.js.map +1 -1
  782. package/dist/components/Spacer/Spacer.cjs.map +1 -1
  783. package/dist/components/Spacer/Spacer.js.map +1 -1
  784. package/dist/components/Spacer/Spacer.module.cjs.map +1 -1
  785. package/dist/components/Spacer/Spacer.module.js.map +1 -1
  786. package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
  787. package/dist/components/Sparkline/Sparkline.js.map +1 -1
  788. package/dist/components/Sparkline/Sparkline.module.cjs.map +1 -1
  789. package/dist/components/Sparkline/Sparkline.module.js.map +1 -1
  790. package/dist/components/Sparkline/sparkline-geometry.cjs.map +1 -1
  791. package/dist/components/Sparkline/sparkline-geometry.js.map +1 -1
  792. package/dist/components/Spinner/Spinner.cjs.map +1 -1
  793. package/dist/components/Spinner/Spinner.js.map +1 -1
  794. package/dist/components/Spinner/Spinner.module.cjs.map +1 -1
  795. package/dist/components/Spinner/Spinner.module.js.map +1 -1
  796. package/dist/components/Stat/Stat.cjs.map +1 -1
  797. package/dist/components/Stat/Stat.js.map +1 -1
  798. package/dist/components/Stat/Stat.module.cjs.map +1 -1
  799. package/dist/components/Stat/Stat.module.js.map +1 -1
  800. package/dist/components/Stepper/Stepper.cjs.map +1 -1
  801. package/dist/components/Stepper/Stepper.js.map +1 -1
  802. package/dist/components/Stepper/Stepper.module.cjs.map +1 -1
  803. package/dist/components/Stepper/Stepper.module.js.map +1 -1
  804. package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
  805. package/dist/components/StepperInput/StepperInput.js.map +1 -1
  806. package/dist/components/StepperInput/StepperInput.module.cjs.map +1 -1
  807. package/dist/components/StepperInput/StepperInput.module.js.map +1 -1
  808. package/dist/components/Switch/Switch.cjs.map +1 -1
  809. package/dist/components/Switch/Switch.js.map +1 -1
  810. package/dist/components/Switch/Switch.module.cjs.map +1 -1
  811. package/dist/components/Switch/Switch.module.js.map +1 -1
  812. package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
  813. package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
  814. package/dist/components/SyncStatusBadge/SyncStatusBadge.module.cjs.map +1 -1
  815. package/dist/components/SyncStatusBadge/SyncStatusBadge.module.js.map +1 -1
  816. package/dist/components/Table/Table.cjs.map +1 -1
  817. package/dist/components/Table/Table.js.map +1 -1
  818. package/dist/components/Table/Table.module.cjs.map +1 -1
  819. package/dist/components/Table/Table.module.js.map +1 -1
  820. package/dist/components/Tabs/Tabs.cjs.map +1 -1
  821. package/dist/components/Tabs/Tabs.js.map +1 -1
  822. package/dist/components/Tabs/Tabs.module.cjs.map +1 -1
  823. package/dist/components/Tabs/Tabs.module.js.map +1 -1
  824. package/dist/components/Tag/Tag.cjs.map +1 -1
  825. package/dist/components/Tag/Tag.js.map +1 -1
  826. package/dist/components/Tag/Tag.module.cjs.map +1 -1
  827. package/dist/components/Tag/Tag.module.js.map +1 -1
  828. package/dist/components/Textarea/Textarea.cjs.map +1 -1
  829. package/dist/components/Textarea/Textarea.js.map +1 -1
  830. package/dist/components/Textarea/Textarea.module.cjs.map +1 -1
  831. package/dist/components/Textarea/Textarea.module.js.map +1 -1
  832. package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
  833. package/dist/components/TimePicker/TimePicker.js.map +1 -1
  834. package/dist/components/TimePicker/TimePicker.module.cjs.map +1 -1
  835. package/dist/components/TimePicker/TimePicker.module.js.map +1 -1
  836. package/dist/components/Timeline/Timeline.cjs.map +1 -1
  837. package/dist/components/Timeline/Timeline.js.map +1 -1
  838. package/dist/components/Timeline/Timeline.module.cjs.map +1 -1
  839. package/dist/components/Timeline/Timeline.module.js.map +1 -1
  840. package/dist/components/Toast/Toast.module.cjs.map +1 -1
  841. package/dist/components/Toast/Toast.module.js.map +1 -1
  842. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  843. package/dist/components/Toast/ToastProvider.js.map +1 -1
  844. package/dist/components/Toggle/Toggle.cjs.map +1 -1
  845. package/dist/components/Toggle/Toggle.js.map +1 -1
  846. package/dist/components/Toggle/Toggle.module.cjs.map +1 -1
  847. package/dist/components/Toggle/Toggle.module.js.map +1 -1
  848. package/dist/components/ToggleGroup/ToggleGroup.cjs.map +1 -1
  849. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  850. package/dist/components/ToggleGroup/ToggleGroup.module.cjs.map +1 -1
  851. package/dist/components/ToggleGroup/ToggleGroup.module.js.map +1 -1
  852. package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
  853. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  854. package/dist/components/Tooltip/Tooltip.module.cjs.map +1 -1
  855. package/dist/components/Tooltip/Tooltip.module.js.map +1 -1
  856. package/dist/components/Tour/Tour.cjs +1 -1
  857. package/dist/components/Tour/Tour.cjs.map +1 -1
  858. package/dist/components/Tour/Tour.js +1 -5
  859. package/dist/components/Tour/Tour.js.map +1 -1
  860. package/dist/components/Tour/Tour.module.cjs.map +1 -1
  861. package/dist/components/Tour/Tour.module.js.map +1 -1
  862. package/dist/components/Tour/tour-position.cjs.map +1 -1
  863. package/dist/components/Tour/tour-position.js.map +1 -1
  864. package/dist/components/Transfer/Transfer.cjs.map +1 -1
  865. package/dist/components/Transfer/Transfer.js.map +1 -1
  866. package/dist/components/Transfer/Transfer.module.cjs.map +1 -1
  867. package/dist/components/Transfer/Transfer.module.js.map +1 -1
  868. package/dist/components/Transfer/transfer-state.cjs.map +1 -1
  869. package/dist/components/Transfer/transfer-state.js.map +1 -1
  870. package/dist/components/TreeView/TreeView.cjs.map +1 -1
  871. package/dist/components/TreeView/TreeView.js.map +1 -1
  872. package/dist/components/TreeView/TreeView.module.cjs.map +1 -1
  873. package/dist/components/TreeView/TreeView.module.js.map +1 -1
  874. package/dist/components/TruncateText/TruncateText.cjs.map +1 -1
  875. package/dist/components/TruncateText/TruncateText.js.map +1 -1
  876. package/dist/components/TruncateText/TruncateText.module.cjs.map +1 -1
  877. package/dist/components/TruncateText/TruncateText.module.js.map +1 -1
  878. package/dist/components/UpdatePrompt/UpdatePrompt.cjs.map +1 -1
  879. package/dist/components/UpdatePrompt/UpdatePrompt.js.map +1 -1
  880. package/dist/components/UpdatePrompt/UpdatePrompt.module.cjs.map +1 -1
  881. package/dist/components/UpdatePrompt/UpdatePrompt.module.js.map +1 -1
  882. package/dist/components/VideoPlayer/VideoPlayer.cjs +1 -1
  883. package/dist/components/VideoPlayer/VideoPlayer.cjs.map +1 -1
  884. package/dist/components/VideoPlayer/VideoPlayer.js +1 -7
  885. package/dist/components/VideoPlayer/VideoPlayer.js.map +1 -1
  886. package/dist/components/VideoPlayer/VideoPlayer.module.cjs.map +1 -1
  887. package/dist/components/VideoPlayer/VideoPlayer.module.js.map +1 -1
  888. package/dist/components/VideoPlayer/playback-rates.cjs.map +1 -1
  889. package/dist/components/VideoPlayer/playback-rates.js.map +1 -1
  890. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  891. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  892. package/dist/components/VirtualList/VirtualList.module.cjs.map +1 -1
  893. package/dist/components/VirtualList/VirtualList.module.js.map +1 -1
  894. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
  895. package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
  896. package/dist/components/VirtualTable/VirtualTable.module.cjs.map +1 -1
  897. package/dist/components/VirtualTable/VirtualTable.module.js.map +1 -1
  898. package/dist/components/VisuallyHidden/VisuallyHidden.cjs.map +1 -1
  899. package/dist/components/VisuallyHidden/VisuallyHidden.js.map +1 -1
  900. package/dist/components/VisuallyHidden/VisuallyHidden.module.cjs.map +1 -1
  901. package/dist/components/VisuallyHidden/VisuallyHidden.module.js.map +1 -1
  902. package/dist/components/Wizard/Wizard.cjs +1 -1
  903. package/dist/components/Wizard/Wizard.cjs.map +1 -1
  904. package/dist/components/Wizard/Wizard.js +4 -11
  905. package/dist/components/Wizard/Wizard.js.map +1 -1
  906. package/dist/components/Wizard/Wizard.module.cjs.map +1 -1
  907. package/dist/components/Wizard/Wizard.module.js.map +1 -1
  908. package/dist/data/create-data-provider.cjs.map +1 -1
  909. package/dist/data/create-data-provider.js.map +1 -1
  910. package/dist/data/data-provider-context.cjs.map +1 -1
  911. package/dist/data/data-provider-context.js.map +1 -1
  912. package/dist/data/use-resource.cjs.map +1 -1
  913. package/dist/data/use-resource.js.map +1 -1
  914. package/dist/editor/RichTextEditor.cjs.map +1 -1
  915. package/dist/editor/RichTextEditor.js.map +1 -1
  916. package/dist/editor/RichTextEditor.module.cjs.map +1 -1
  917. package/dist/editor/RichTextEditor.module.js.map +1 -1
  918. package/dist/editor.js +1 -2
  919. package/dist/error-boundary/ErrorBoundary.cjs.map +1 -1
  920. package/dist/error-boundary/ErrorBoundary.js.map +1 -1
  921. package/dist/error-boundary/use-error-handler.cjs.map +1 -1
  922. package/dist/error-boundary/use-error-handler.js.map +1 -1
  923. package/dist/feature-flags/FeatureFlagsProvider.cjs.map +1 -1
  924. package/dist/feature-flags/FeatureFlagsProvider.js.map +1 -1
  925. package/dist/feature-flags/growthbook-adapter.cjs.map +1 -1
  926. package/dist/feature-flags/growthbook-adapter.js.map +1 -1
  927. package/dist/feature-flags/in-memory-adapter.cjs.map +1 -1
  928. package/dist/feature-flags/in-memory-adapter.js.map +1 -1
  929. package/dist/feature-flags/launchdarkly-adapter.cjs.map +1 -1
  930. package/dist/feature-flags/launchdarkly-adapter.js.map +1 -1
  931. package/dist/forms/FormField.cjs.map +1 -1
  932. package/dist/forms/FormField.js.map +1 -1
  933. package/dist/forms/br-validators.cjs.map +1 -1
  934. package/dist/forms/br-validators.js.map +1 -1
  935. package/dist/forms/masked-inputs.cjs.map +1 -1
  936. package/dist/forms/masked-inputs.js.map +1 -1
  937. package/dist/forms/use-viacep.cjs.map +1 -1
  938. package/dist/forms/use-viacep.js.map +1 -1
  939. package/dist/forms/use-zod-form.cjs.map +1 -1
  940. package/dist/forms/use-zod-form.js.map +1 -1
  941. package/dist/forms/validate-form.cjs.map +1 -1
  942. package/dist/forms/validate-form.js.map +1 -1
  943. package/dist/forms/zod-resolver.cjs.map +1 -1
  944. package/dist/forms/zod-resolver.js.map +1 -1
  945. package/dist/geo/TrajectoryMap.cjs.map +1 -1
  946. package/dist/geo/TrajectoryMap.js.map +1 -1
  947. package/dist/geo/TrajectoryMap.module.cjs.map +1 -1
  948. package/dist/geo/TrajectoryMap.module.js.map +1 -1
  949. package/dist/geo/bounds.cjs.map +1 -1
  950. package/dist/geo/bounds.js.map +1 -1
  951. package/dist/geo/create-position-tracker.cjs +1 -1
  952. package/dist/geo/create-position-tracker.cjs.map +1 -1
  953. package/dist/geo/create-position-tracker.js +1 -7
  954. package/dist/geo/create-position-tracker.js.map +1 -1
  955. package/dist/geo/distance.cjs.map +1 -1
  956. package/dist/geo/distance.js.map +1 -1
  957. package/dist/geo/estimate.cjs.map +1 -1
  958. package/dist/geo/estimate.js.map +1 -1
  959. package/dist/geo/leaflet-map.cjs.map +1 -1
  960. package/dist/geo/leaflet-map.js.map +1 -1
  961. package/dist/geo/projection.cjs.map +1 -1
  962. package/dist/geo/projection.js.map +1 -1
  963. package/dist/geo/routing.cjs.map +1 -1
  964. package/dist/geo/routing.js.map +1 -1
  965. package/dist/geo/types.cjs.map +1 -1
  966. package/dist/geo/types.js.map +1 -1
  967. package/dist/geo/use-position-tracker.cjs.map +1 -1
  968. package/dist/geo/use-position-tracker.js.map +1 -1
  969. package/dist/hooks/pwa-env.cjs.map +1 -1
  970. package/dist/hooks/pwa-env.js.map +1 -1
  971. package/dist/hooks/tab-sequence.cjs.map +1 -1
  972. package/dist/hooks/tab-sequence.js.map +1 -1
  973. package/dist/hooks/use-announce.cjs +1 -1
  974. package/dist/hooks/use-announce.cjs.map +1 -1
  975. package/dist/hooks/use-announce.js +2 -2
  976. package/dist/hooks/use-announce.js.map +1 -1
  977. package/dist/hooks/use-async.cjs.map +1 -1
  978. package/dist/hooks/use-async.js.map +1 -1
  979. package/dist/hooks/use-before-install-prompt.cjs.map +1 -1
  980. package/dist/hooks/use-before-install-prompt.js.map +1 -1
  981. package/dist/hooks/use-breakpoint.cjs.map +1 -1
  982. package/dist/hooks/use-breakpoint.js.map +1 -1
  983. package/dist/hooks/use-click-outside.cjs.map +1 -1
  984. package/dist/hooks/use-click-outside.js.map +1 -1
  985. package/dist/hooks/use-client-filter.cjs.map +1 -1
  986. package/dist/hooks/use-client-filter.js.map +1 -1
  987. package/dist/hooks/use-clipboard.cjs.map +1 -1
  988. package/dist/hooks/use-clipboard.js.map +1 -1
  989. package/dist/hooks/use-countdown.cjs.map +1 -1
  990. package/dist/hooks/use-countdown.js.map +1 -1
  991. package/dist/hooks/use-counter.cjs.map +1 -1
  992. package/dist/hooks/use-counter.js.map +1 -1
  993. package/dist/hooks/use-debounce.cjs.map +1 -1
  994. package/dist/hooks/use-debounce.js.map +1 -1
  995. package/dist/hooks/use-deep-memo.cjs.map +1 -1
  996. package/dist/hooks/use-deep-memo.js.map +1 -1
  997. package/dist/hooks/use-disclosure.cjs.map +1 -1
  998. package/dist/hooks/use-disclosure.js.map +1 -1
  999. package/dist/hooks/use-document-title.cjs.map +1 -1
  1000. package/dist/hooks/use-document-title.js.map +1 -1
  1001. package/dist/hooks/use-document-visibility.cjs.map +1 -1
  1002. package/dist/hooks/use-document-visibility.js.map +1 -1
  1003. package/dist/hooks/use-draft-filters.cjs.map +1 -1
  1004. package/dist/hooks/use-draft-filters.js.map +1 -1
  1005. package/dist/hooks/use-event-listener.cjs.map +1 -1
  1006. package/dist/hooks/use-event-listener.js.map +1 -1
  1007. package/dist/hooks/use-favicon.cjs.map +1 -1
  1008. package/dist/hooks/use-favicon.js.map +1 -1
  1009. package/dist/hooks/use-focus-trap.cjs.map +1 -1
  1010. package/dist/hooks/use-focus-trap.js.map +1 -1
  1011. package/dist/hooks/use-fullscreen-element.cjs.map +1 -1
  1012. package/dist/hooks/use-fullscreen-element.js.map +1 -1
  1013. package/dist/hooks/use-fullscreen.cjs +1 -1
  1014. package/dist/hooks/use-fullscreen.cjs.map +1 -1
  1015. package/dist/hooks/use-fullscreen.js +7 -23
  1016. package/dist/hooks/use-fullscreen.js.map +1 -1
  1017. package/dist/hooks/use-geolocation.cjs.map +1 -1
  1018. package/dist/hooks/use-geolocation.js.map +1 -1
  1019. package/dist/hooks/use-hover.cjs.map +1 -1
  1020. package/dist/hooks/use-hover.js.map +1 -1
  1021. package/dist/hooks/use-idle.cjs.map +1 -1
  1022. package/dist/hooks/use-idle.js.map +1 -1
  1023. package/dist/hooks/use-install-prompt.cjs +1 -1
  1024. package/dist/hooks/use-install-prompt.cjs.map +1 -1
  1025. package/dist/hooks/use-install-prompt.js +1 -1
  1026. package/dist/hooks/use-install-prompt.js.map +1 -1
  1027. package/dist/hooks/use-intersection-observer.cjs.map +1 -1
  1028. package/dist/hooks/use-intersection-observer.js.map +1 -1
  1029. package/dist/hooks/use-interval.cjs.map +1 -1
  1030. package/dist/hooks/use-interval.js.map +1 -1
  1031. package/dist/hooks/use-is-first-render.cjs.map +1 -1
  1032. package/dist/hooks/use-is-first-render.js.map +1 -1
  1033. package/dist/hooks/use-keyboard-shortcut.cjs.map +1 -1
  1034. package/dist/hooks/use-keyboard-shortcut.js.map +1 -1
  1035. package/dist/hooks/use-latest-ref.cjs.map +1 -1
  1036. package/dist/hooks/use-latest-ref.js.map +1 -1
  1037. package/dist/hooks/use-list-state.cjs.map +1 -1
  1038. package/dist/hooks/use-list-state.js.map +1 -1
  1039. package/dist/hooks/use-local-storage.cjs +1 -1
  1040. package/dist/hooks/use-local-storage.cjs.map +1 -1
  1041. package/dist/hooks/use-local-storage.js +2 -5
  1042. package/dist/hooks/use-local-storage.js.map +1 -1
  1043. package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
  1044. package/dist/hooks/use-long-press-handlers.js.map +1 -1
  1045. package/dist/hooks/use-long-press.cjs.map +1 -1
  1046. package/dist/hooks/use-long-press.js.map +1 -1
  1047. package/dist/hooks/use-map.cjs.map +1 -1
  1048. package/dist/hooks/use-map.js.map +1 -1
  1049. package/dist/hooks/use-media-devices.cjs.map +1 -1
  1050. package/dist/hooks/use-media-devices.js.map +1 -1
  1051. package/dist/hooks/use-media-permission.cjs +1 -1
  1052. package/dist/hooks/use-media-permission.cjs.map +1 -1
  1053. package/dist/hooks/use-media-permission.js +2 -5
  1054. package/dist/hooks/use-media-permission.js.map +1 -1
  1055. package/dist/hooks/use-media-query.cjs.map +1 -1
  1056. package/dist/hooks/use-media-query.js.map +1 -1
  1057. package/dist/hooks/use-object-url.cjs.map +1 -1
  1058. package/dist/hooks/use-object-url.js.map +1 -1
  1059. package/dist/hooks/use-online.cjs.map +1 -1
  1060. package/dist/hooks/use-online.js.map +1 -1
  1061. package/dist/hooks/use-pagination.cjs.map +1 -1
  1062. package/dist/hooks/use-pagination.js.map +1 -1
  1063. package/dist/hooks/use-previous.cjs.map +1 -1
  1064. package/dist/hooks/use-previous.js.map +1 -1
  1065. package/dist/hooks/use-push-to-talk.cjs.map +1 -1
  1066. package/dist/hooks/use-push-to-talk.js.map +1 -1
  1067. package/dist/hooks/use-queue.cjs.map +1 -1
  1068. package/dist/hooks/use-queue.js.map +1 -1
  1069. package/dist/hooks/use-resize-observer.cjs.map +1 -1
  1070. package/dist/hooks/use-resize-observer.js.map +1 -1
  1071. package/dist/hooks/use-scroll-lock.cjs.map +1 -1
  1072. package/dist/hooks/use-scroll-lock.js.map +1 -1
  1073. package/dist/hooks/use-scroll-overflow.cjs.map +1 -1
  1074. package/dist/hooks/use-scroll-overflow.js.map +1 -1
  1075. package/dist/hooks/use-service-worker-update.cjs.map +1 -1
  1076. package/dist/hooks/use-service-worker-update.js.map +1 -1
  1077. package/dist/hooks/use-set.cjs.map +1 -1
  1078. package/dist/hooks/use-set.js.map +1 -1
  1079. package/dist/hooks/use-sortable.cjs.map +1 -1
  1080. package/dist/hooks/use-sortable.js.map +1 -1
  1081. package/dist/hooks/use-stable-callback.cjs.map +1 -1
  1082. package/dist/hooks/use-stable-callback.js.map +1 -1
  1083. package/dist/hooks/use-storage-estimate.cjs.map +1 -1
  1084. package/dist/hooks/use-storage-estimate.js.map +1 -1
  1085. package/dist/hooks/use-throttle.cjs.map +1 -1
  1086. package/dist/hooks/use-throttle.js.map +1 -1
  1087. package/dist/hooks/use-timeout.cjs.map +1 -1
  1088. package/dist/hooks/use-timeout.js.map +1 -1
  1089. package/dist/hooks/use-toggle.cjs.map +1 -1
  1090. package/dist/hooks/use-toggle.js.map +1 -1
  1091. package/dist/hooks/use-typewriter.cjs.map +1 -1
  1092. package/dist/hooks/use-typewriter.js.map +1 -1
  1093. package/dist/hooks/use-window-size.cjs.map +1 -1
  1094. package/dist/hooks/use-window-size.js.map +1 -1
  1095. package/dist/http/api-client.cjs.map +1 -1
  1096. package/dist/http/api-client.js.map +1 -1
  1097. package/dist/http/build-url.cjs.map +1 -1
  1098. package/dist/http/build-url.js.map +1 -1
  1099. package/dist/http/credential-scope.cjs.map +1 -1
  1100. package/dist/http/credential-scope.js.map +1 -1
  1101. package/dist/http/csrf.cjs.map +1 -1
  1102. package/dist/http/csrf.js.map +1 -1
  1103. package/dist/http/decode-response.cjs.map +1 -1
  1104. package/dist/http/decode-response.js.map +1 -1
  1105. package/dist/http/describe-api-error.cjs.map +1 -1
  1106. package/dist/http/describe-api-error.js.map +1 -1
  1107. package/dist/http/errors.cjs.map +1 -1
  1108. package/dist/http/errors.js.map +1 -1
  1109. package/dist/http/idempotency.cjs.map +1 -1
  1110. package/dist/http/idempotency.js.map +1 -1
  1111. package/dist/http/parse-response.cjs.map +1 -1
  1112. package/dist/http/parse-response.js.map +1 -1
  1113. package/dist/http/resumable-upload.cjs +1 -1
  1114. package/dist/http/resumable-upload.cjs.map +1 -1
  1115. package/dist/http/resumable-upload.js +141 -105
  1116. package/dist/http/resumable-upload.js.map +1 -1
  1117. package/dist/http/retry.cjs +1 -1
  1118. package/dist/http/retry.cjs.map +1 -1
  1119. package/dist/http/retry.js +5 -6
  1120. package/dist/http/retry.js.map +1 -1
  1121. package/dist/http/timeout.cjs.map +1 -1
  1122. package/dist/http/timeout.js.map +1 -1
  1123. package/dist/http/upload-with-progress.cjs +1 -1
  1124. package/dist/http/upload-with-progress.cjs.map +1 -1
  1125. package/dist/http/upload-with-progress.js +2 -7
  1126. package/dist/http/upload-with-progress.js.map +1 -1
  1127. package/dist/http/use-describe-api-error.cjs.map +1 -1
  1128. package/dist/http/use-describe-api-error.js.map +1 -1
  1129. package/dist/http/use-poll.cjs +1 -1
  1130. package/dist/http/use-poll.cjs.map +1 -1
  1131. package/dist/http/use-poll.js +2 -5
  1132. package/dist/http/use-poll.js.map +1 -1
  1133. package/dist/i18n/I18nProvider.cjs.map +1 -1
  1134. package/dist/i18n/I18nProvider.js.map +1 -1
  1135. package/dist/i18n/create-i18n.cjs.map +1 -1
  1136. package/dist/i18n/create-i18n.js.map +1 -1
  1137. package/dist/icons/Icon.cjs.map +1 -1
  1138. package/dist/icons/Icon.js.map +1 -1
  1139. package/dist/icons/IconPicker.cjs.map +1 -1
  1140. package/dist/icons/IconPicker.js.map +1 -1
  1141. package/dist/icons/IconPicker.module.cjs.map +1 -1
  1142. package/dist/icons/IconPicker.module.js.map +1 -1
  1143. package/dist/icons/IconProvider.cjs.map +1 -1
  1144. package/dist/icons/IconProvider.js.map +1 -1
  1145. package/dist/icons/alias.cjs.map +1 -1
  1146. package/dist/icons/alias.js.map +1 -1
  1147. package/dist/icons/generated/aliases.cjs.map +1 -1
  1148. package/dist/icons/generated/aliases.js.map +1 -1
  1149. package/dist/icons/generated/icon-names.cjs.map +1 -1
  1150. package/dist/icons/generated/icon-names.js.map +1 -1
  1151. package/dist/icons/generated/loaders.cjs.map +1 -1
  1152. package/dist/icons/generated/loaders.js.map +1 -1
  1153. package/dist/icons/generated/shard-00.cjs.map +1 -1
  1154. package/dist/icons/generated/shard-00.js.map +1 -1
  1155. package/dist/icons/generated/shard-01.cjs.map +1 -1
  1156. package/dist/icons/generated/shard-01.js.map +1 -1
  1157. package/dist/icons/generated/shard-02.cjs.map +1 -1
  1158. package/dist/icons/generated/shard-02.js.map +1 -1
  1159. package/dist/icons/generated/shard-03.cjs.map +1 -1
  1160. package/dist/icons/generated/shard-03.js.map +1 -1
  1161. package/dist/icons/generated/shard-04.cjs.map +1 -1
  1162. package/dist/icons/generated/shard-04.js.map +1 -1
  1163. package/dist/icons/generated/shard-05.cjs.map +1 -1
  1164. package/dist/icons/generated/shard-05.js.map +1 -1
  1165. package/dist/icons/generated/shard-06.cjs.map +1 -1
  1166. package/dist/icons/generated/shard-06.js.map +1 -1
  1167. package/dist/icons/generated/shard-07.cjs.map +1 -1
  1168. package/dist/icons/generated/shard-07.js.map +1 -1
  1169. package/dist/icons/generated/shard-08.cjs.map +1 -1
  1170. package/dist/icons/generated/shard-08.js.map +1 -1
  1171. package/dist/icons/generated/shard-09.cjs.map +1 -1
  1172. package/dist/icons/generated/shard-09.js.map +1 -1
  1173. package/dist/icons/generated/shard-10.cjs.map +1 -1
  1174. package/dist/icons/generated/shard-10.js.map +1 -1
  1175. package/dist/icons/generated/shard-11.cjs.map +1 -1
  1176. package/dist/icons/generated/shard-11.js.map +1 -1
  1177. package/dist/icons/generated/shard-12.cjs.map +1 -1
  1178. package/dist/icons/generated/shard-12.js.map +1 -1
  1179. package/dist/icons/generated/shard-13.cjs.map +1 -1
  1180. package/dist/icons/generated/shard-13.js.map +1 -1
  1181. package/dist/icons/generated/shard-14.cjs.map +1 -1
  1182. package/dist/icons/generated/shard-14.js.map +1 -1
  1183. package/dist/icons/generated/shard-15.cjs.map +1 -1
  1184. package/dist/icons/generated/shard-15.js.map +1 -1
  1185. package/dist/icons/generated/shard-16.cjs.map +1 -1
  1186. package/dist/icons/generated/shard-16.js.map +1 -1
  1187. package/dist/icons/generated/shard-17.cjs.map +1 -1
  1188. package/dist/icons/generated/shard-17.js.map +1 -1
  1189. package/dist/icons/generated/shard-18.cjs.map +1 -1
  1190. package/dist/icons/generated/shard-18.js.map +1 -1
  1191. package/dist/icons/generated/shard-19.cjs.map +1 -1
  1192. package/dist/icons/generated/shard-19.js.map +1 -1
  1193. package/dist/icons/generated/shard-20.cjs.map +1 -1
  1194. package/dist/icons/generated/shard-20.js.map +1 -1
  1195. package/dist/icons/generated/shard-21.cjs.map +1 -1
  1196. package/dist/icons/generated/shard-21.js.map +1 -1
  1197. package/dist/icons/generated/shard-22.cjs.map +1 -1
  1198. package/dist/icons/generated/shard-22.js.map +1 -1
  1199. package/dist/icons/generated/shard-23.cjs.map +1 -1
  1200. package/dist/icons/generated/shard-23.js.map +1 -1
  1201. package/dist/icons/generated/shard-24.cjs.map +1 -1
  1202. package/dist/icons/generated/shard-24.js.map +1 -1
  1203. package/dist/icons/generated/shard-25.cjs.map +1 -1
  1204. package/dist/icons/generated/shard-25.js.map +1 -1
  1205. package/dist/icons/generated/shard-26.cjs.map +1 -1
  1206. package/dist/icons/generated/shard-26.js.map +1 -1
  1207. package/dist/icons/generated/shard-27.cjs.map +1 -1
  1208. package/dist/icons/generated/shard-27.js.map +1 -1
  1209. package/dist/icons/generated/shard-28.cjs.map +1 -1
  1210. package/dist/icons/generated/shard-28.js.map +1 -1
  1211. package/dist/icons/generated/shard-29.cjs.map +1 -1
  1212. package/dist/icons/generated/shard-29.js.map +1 -1
  1213. package/dist/icons/generated/shard-30.cjs.map +1 -1
  1214. package/dist/icons/generated/shard-30.js.map +1 -1
  1215. package/dist/icons/generated/shard-31.cjs.map +1 -1
  1216. package/dist/icons/generated/shard-31.js.map +1 -1
  1217. package/dist/icons/generated/shard-32.cjs.map +1 -1
  1218. package/dist/icons/generated/shard-32.js.map +1 -1
  1219. package/dist/icons/generated/shard-33.cjs.map +1 -1
  1220. package/dist/icons/generated/shard-33.js.map +1 -1
  1221. package/dist/icons/generated/shard-34.cjs.map +1 -1
  1222. package/dist/icons/generated/shard-34.js.map +1 -1
  1223. package/dist/icons/generated/shard-35.cjs.map +1 -1
  1224. package/dist/icons/generated/shard-35.js.map +1 -1
  1225. package/dist/icons/generated/shard-36.cjs.map +1 -1
  1226. package/dist/icons/generated/shard-36.js.map +1 -1
  1227. package/dist/icons/generated/shard-37.cjs.map +1 -1
  1228. package/dist/icons/generated/shard-37.js.map +1 -1
  1229. package/dist/icons/generated/shard-38.cjs.map +1 -1
  1230. package/dist/icons/generated/shard-38.js.map +1 -1
  1231. package/dist/icons/generated/shard-39.cjs.map +1 -1
  1232. package/dist/icons/generated/shard-39.js.map +1 -1
  1233. package/dist/icons/generated/shard-40.cjs.map +1 -1
  1234. package/dist/icons/generated/shard-40.js.map +1 -1
  1235. package/dist/icons/generated/shard-41.cjs.map +1 -1
  1236. package/dist/icons/generated/shard-41.js.map +1 -1
  1237. package/dist/icons/generated/shard-42.cjs.map +1 -1
  1238. package/dist/icons/generated/shard-42.js.map +1 -1
  1239. package/dist/icons/generated/shard-43.cjs.map +1 -1
  1240. package/dist/icons/generated/shard-43.js.map +1 -1
  1241. package/dist/icons/generated/shard-44.cjs.map +1 -1
  1242. package/dist/icons/generated/shard-44.js.map +1 -1
  1243. package/dist/icons/generated/shard-45.cjs.map +1 -1
  1244. package/dist/icons/generated/shard-45.js.map +1 -1
  1245. package/dist/icons/icon-context.cjs.map +1 -1
  1246. package/dist/icons/icon-context.js.map +1 -1
  1247. package/dist/icons/is-icon-name.cjs.map +1 -1
  1248. package/dist/icons/is-icon-name.js.map +1 -1
  1249. package/dist/icons/material-symbols.cjs.map +1 -1
  1250. package/dist/icons/material-symbols.js.map +1 -1
  1251. package/dist/icons/normalize-icon-name.cjs.map +1 -1
  1252. package/dist/icons/normalize-icon-name.js.map +1 -1
  1253. package/dist/icons/shard-cache.cjs +1 -1
  1254. package/dist/icons/shard-cache.cjs.map +1 -1
  1255. package/dist/icons/shard-cache.js +2 -5
  1256. package/dist/icons/shard-cache.js.map +1 -1
  1257. package/dist/icons/use-icon.cjs.map +1 -1
  1258. package/dist/icons/use-icon.js.map +1 -1
  1259. package/dist/icons/validate-icon-name.cjs.map +1 -1
  1260. package/dist/icons/validate-icon-name.js.map +1 -1
  1261. package/dist/icons-virtual.cjs.map +1 -1
  1262. package/dist/icons-virtual.js.map +1 -1
  1263. package/dist/imaging/canvas.cjs.map +1 -1
  1264. package/dist/imaging/canvas.js.map +1 -1
  1265. package/dist/imaging/compress.cjs.map +1 -1
  1266. package/dist/imaging/compress.js.map +1 -1
  1267. package/dist/imaging/decode.cjs.map +1 -1
  1268. package/dist/imaging/decode.js.map +1 -1
  1269. package/dist/imaging/encode.cjs.map +1 -1
  1270. package/dist/imaging/encode.js.map +1 -1
  1271. package/dist/imaging/exceptions.cjs.map +1 -1
  1272. package/dist/imaging/exceptions.js.map +1 -1
  1273. package/dist/imaging/frame.cjs.map +1 -1
  1274. package/dist/imaging/frame.js.map +1 -1
  1275. package/dist/imaging/thumbnails.cjs.map +1 -1
  1276. package/dist/imaging/thumbnails.js.map +1 -1
  1277. package/dist/imaging/transform.cjs.map +1 -1
  1278. package/dist/imaging/transform.js.map +1 -1
  1279. package/dist/imaging/use-image-processing.cjs.map +1 -1
  1280. package/dist/imaging/use-image-processing.js.map +1 -1
  1281. package/dist/logger/logger.cjs.map +1 -1
  1282. package/dist/logger/logger.js.map +1 -1
  1283. package/dist/neural.cjs.map +1 -1
  1284. package/dist/neural.js.map +1 -1
  1285. package/dist/oauth/GoogleSignIn.cjs +1 -1
  1286. package/dist/oauth/GoogleSignIn.cjs.map +1 -1
  1287. package/dist/oauth/GoogleSignIn.js +6 -9
  1288. package/dist/oauth/GoogleSignIn.js.map +1 -1
  1289. package/dist/oauth/callback.cjs.map +1 -1
  1290. package/dist/oauth/callback.js.map +1 -1
  1291. package/dist/oauth/use-oauth-callback.cjs.map +1 -1
  1292. package/dist/oauth/use-oauth-callback.js.map +1 -1
  1293. package/dist/offline/conflict.cjs.map +1 -1
  1294. package/dist/offline/conflict.js.map +1 -1
  1295. package/dist/offline/create-offline-database.cjs.map +1 -1
  1296. package/dist/offline/create-offline-database.js.map +1 -1
  1297. package/dist/offline/create-offline-store.cjs +1 -1
  1298. package/dist/offline/create-offline-store.cjs.map +1 -1
  1299. package/dist/offline/create-offline-store.js +1 -5
  1300. package/dist/offline/create-offline-store.js.map +1 -1
  1301. package/dist/offline/create-offline-sync.cjs +1 -1
  1302. package/dist/offline/create-offline-sync.cjs.map +1 -1
  1303. package/dist/offline/create-offline-sync.js +9 -14
  1304. package/dist/offline/create-offline-sync.js.map +1 -1
  1305. package/dist/offline/use-offline-sync.cjs.map +1 -1
  1306. package/dist/offline/use-offline-sync.js.map +1 -1
  1307. package/dist/perf/cache-size.cjs.map +1 -1
  1308. package/dist/perf/cache-size.js.map +1 -1
  1309. package/dist/perf/device.cjs.map +1 -1
  1310. package/dist/perf/device.js.map +1 -1
  1311. package/dist/perf/format.cjs.map +1 -1
  1312. package/dist/perf/format.js.map +1 -1
  1313. package/dist/perf/profiler.cjs.map +1 -1
  1314. package/dist/perf/profiler.js.map +1 -1
  1315. package/dist/push/use-push-subscription.cjs.map +1 -1
  1316. package/dist/push/use-push-subscription.js.map +1 -1
  1317. package/dist/push/utils.cjs.map +1 -1
  1318. package/dist/push/utils.js.map +1 -1
  1319. package/dist/push/web-push-client.cjs.map +1 -1
  1320. package/dist/push/web-push-client.js.map +1 -1
  1321. package/dist/query/QueryProvider.cjs.map +1 -1
  1322. package/dist/query/QueryProvider.js.map +1 -1
  1323. package/dist/query/constants.cjs.map +1 -1
  1324. package/dist/query/constants.js.map +1 -1
  1325. package/dist/query/create-query-keys.cjs.map +1 -1
  1326. package/dist/query/create-query-keys.js.map +1 -1
  1327. package/dist/query/foreign-client-warning.cjs.map +1 -1
  1328. package/dist/query/foreign-client-warning.js.map +1 -1
  1329. package/dist/query/offline-persistence.cjs +1 -1
  1330. package/dist/query/offline-persistence.cjs.map +1 -1
  1331. package/dist/query/offline-persistence.js +2 -10
  1332. package/dist/query/offline-persistence.js.map +1 -1
  1333. package/dist/query/optimistic.cjs.map +1 -1
  1334. package/dist/query/optimistic.js.map +1 -1
  1335. package/dist/query/pagination.cjs.map +1 -1
  1336. package/dist/query/pagination.js.map +1 -1
  1337. package/dist/query/retry-policy.cjs.map +1 -1
  1338. package/dist/query/retry-policy.js.map +1 -1
  1339. package/dist/query/use-cursor-query.cjs.map +1 -1
  1340. package/dist/query/use-cursor-query.js.map +1 -1
  1341. package/dist/query/use-offline-mutation.cjs.map +1 -1
  1342. package/dist/query/use-offline-mutation.js.map +1 -1
  1343. package/dist/query/use-paginated-query.cjs.map +1 -1
  1344. package/dist/query/use-paginated-query.js.map +1 -1
  1345. package/dist/router/AppRouter.cjs.map +1 -1
  1346. package/dist/router/AppRouter.js.map +1 -1
  1347. package/dist/router/RouteGuard.cjs.map +1 -1
  1348. package/dist/router/RouteGuard.js.map +1 -1
  1349. package/dist/router/index.js +2 -2
  1350. package/dist/share/share-or-download.cjs.map +1 -1
  1351. package/dist/share/share-or-download.js.map +1 -1
  1352. package/dist/share/share.cjs.map +1 -1
  1353. package/dist/share/share.js.map +1 -1
  1354. package/dist/sse/create-event-stream.cjs.map +1 -1
  1355. package/dist/sse/create-event-stream.js.map +1 -1
  1356. package/dist/sse/use-event-stream.cjs.map +1 -1
  1357. package/dist/sse/use-event-stream.js.map +1 -1
  1358. package/dist/store/create-selectors.cjs.map +1 -1
  1359. package/dist/store/create-selectors.js.map +1 -1
  1360. package/dist/store/create-store.cjs.map +1 -1
  1361. package/dist/store/create-store.js.map +1 -1
  1362. package/dist/sw/background-sync.cjs +1 -1
  1363. package/dist/sw/background-sync.cjs.map +1 -1
  1364. package/dist/sw/background-sync.js +32 -31
  1365. package/dist/sw/background-sync.js.map +1 -1
  1366. package/dist/sw/cache-inspect.cjs.map +1 -1
  1367. package/dist/sw/cache-inspect.js.map +1 -1
  1368. package/dist/sw/cache.cjs +1 -1
  1369. package/dist/sw/cache.cjs.map +1 -1
  1370. package/dist/sw/cache.js +2 -6
  1371. package/dist/sw/cache.js.map +1 -1
  1372. package/dist/sw/create-push-handler.cjs.map +1 -1
  1373. package/dist/sw/create-push-handler.js.map +1 -1
  1374. package/dist/sw/credential-headers.cjs.map +1 -1
  1375. package/dist/sw/credential-headers.js.map +1 -1
  1376. package/dist/sw/periodic-sync.cjs.map +1 -1
  1377. package/dist/sw/periodic-sync.js.map +1 -1
  1378. package/dist/sw/register-service-worker.cjs.map +1 -1
  1379. package/dist/sw/register-service-worker.js.map +1 -1
  1380. package/dist/sw/session-data.cjs.map +1 -1
  1381. package/dist/sw/session-data.js.map +1 -1
  1382. package/dist/sw.d.ts +46 -8
  1383. package/dist/tabular/assets.cjs.map +1 -1
  1384. package/dist/tabular/assets.js.map +1 -1
  1385. package/dist/tabular/cache.cjs.map +1 -1
  1386. package/dist/tabular/cache.js.map +1 -1
  1387. package/dist/tabular/compact.cjs.map +1 -1
  1388. package/dist/tabular/compact.js.map +1 -1
  1389. package/dist/tabular/exceptions.cjs.map +1 -1
  1390. package/dist/tabular/exceptions.js.map +1 -1
  1391. package/dist/tabular/manifest.cjs.map +1 -1
  1392. package/dist/tabular/manifest.js.map +1 -1
  1393. package/dist/tabular/predictor.cjs.map +1 -1
  1394. package/dist/tabular/predictor.js.map +1 -1
  1395. package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
  1396. package/dist/tabular/use-tabular-predictor.js.map +1 -1
  1397. package/dist/telemetry/TelemetryProvider.cjs.map +1 -1
  1398. package/dist/telemetry/TelemetryProvider.js.map +1 -1
  1399. package/dist/telemetry/app-error-adapter.cjs.map +1 -1
  1400. package/dist/telemetry/app-error-adapter.js.map +1 -1
  1401. package/dist/telemetry/app-error-queue.cjs.map +1 -1
  1402. package/dist/telemetry/app-error-queue.js.map +1 -1
  1403. package/dist/telemetry/app-error-reporter.cjs +1 -1
  1404. package/dist/telemetry/app-error-reporter.cjs.map +1 -1
  1405. package/dist/telemetry/app-error-reporter.js +1 -1
  1406. package/dist/telemetry/app-error-reporter.js.map +1 -1
  1407. package/dist/telemetry/app-error-schema.cjs.map +1 -1
  1408. package/dist/telemetry/app-error-schema.js.map +1 -1
  1409. package/dist/telemetry/app-error-transport.cjs.map +1 -1
  1410. package/dist/telemetry/app-error-transport.js.map +1 -1
  1411. package/dist/telemetry/console-adapter.cjs.map +1 -1
  1412. package/dist/telemetry/console-adapter.js.map +1 -1
  1413. package/dist/telemetry/describe-app-error.cjs.map +1 -1
  1414. package/dist/telemetry/describe-app-error.js.map +1 -1
  1415. package/dist/telemetry/posthog-adapter.cjs +1 -1
  1416. package/dist/telemetry/posthog-adapter.cjs.map +1 -1
  1417. package/dist/telemetry/posthog-adapter.js +1 -5
  1418. package/dist/telemetry/posthog-adapter.js.map +1 -1
  1419. package/dist/telemetry/sentry-adapter.cjs.map +1 -1
  1420. package/dist/telemetry/sentry-adapter.js.map +1 -1
  1421. package/dist/tempest-react-sdk.cjs +1 -1
  1422. package/dist/tempest-react-sdk.d.ts +448 -15
  1423. package/dist/tempest-react-sdk.js +119 -119
  1424. package/dist/testing/create-mock-handlers.cjs.map +1 -1
  1425. package/dist/testing/create-mock-handlers.js.map +1 -1
  1426. package/dist/testing.js +1 -2
  1427. package/dist/theme/ThemeProvider.cjs.map +1 -1
  1428. package/dist/theme/ThemeProvider.js.map +1 -1
  1429. package/dist/theme/apply-theme.cjs.map +1 -1
  1430. package/dist/theme/apply-theme.js.map +1 -1
  1431. package/dist/theme/color.cjs.map +1 -1
  1432. package/dist/theme/color.js.map +1 -1
  1433. package/dist/theme/create-theme.cjs.map +1 -1
  1434. package/dist/theme/create-theme.js.map +1 -1
  1435. package/dist/theme/data-viz-ramps.cjs.map +1 -1
  1436. package/dist/theme/data-viz-ramps.js.map +1 -1
  1437. package/dist/theme/initial-theme.cjs.map +1 -1
  1438. package/dist/theme/initial-theme.js.map +1 -1
  1439. package/dist/theme/inverse-render.cjs.map +1 -1
  1440. package/dist/theme/inverse-render.js.map +1 -1
  1441. package/dist/theme/inverse-restore.cjs.map +1 -1
  1442. package/dist/theme/inverse-restore.js.map +1 -1
  1443. package/dist/theme/inverse-surface.cjs.map +1 -1
  1444. package/dist/theme/inverse-surface.js.map +1 -1
  1445. package/dist/theme/status-tokens.cjs.map +1 -1
  1446. package/dist/theme/status-tokens.js.map +1 -1
  1447. package/dist/theme/theme-presets.cjs.map +1 -1
  1448. package/dist/theme/theme-presets.js.map +1 -1
  1449. package/dist/utils/absent.cjs.map +1 -1
  1450. package/dist/utils/absent.js.map +1 -1
  1451. package/dist/utils/arrays.cjs.map +1 -1
  1452. package/dist/utils/arrays.js.map +1 -1
  1453. package/dist/utils/base64.cjs.map +1 -1
  1454. package/dist/utils/base64.js.map +1 -1
  1455. package/dist/utils/cn.cjs.map +1 -1
  1456. package/dist/utils/cn.js.map +1 -1
  1457. package/dist/utils/compare-values.cjs.map +1 -1
  1458. package/dist/utils/compare-values.js.map +1 -1
  1459. package/dist/utils/compressed-storage.cjs.map +1 -1
  1460. package/dist/utils/compressed-storage.js.map +1 -1
  1461. package/dist/utils/csv.cjs +3 -3
  1462. package/dist/utils/csv.cjs.map +1 -1
  1463. package/dist/utils/csv.js +11 -10
  1464. package/dist/utils/csv.js.map +1 -1
  1465. package/dist/utils/dev-mode.cjs.map +1 -1
  1466. package/dist/utils/dev-mode.js.map +1 -1
  1467. package/dist/utils/duplicate-instance.cjs.map +1 -1
  1468. package/dist/utils/duplicate-instance.js.map +1 -1
  1469. package/dist/utils/format.cjs.map +1 -1
  1470. package/dist/utils/format.js.map +1 -1
  1471. package/dist/utils/functions.cjs +1 -1
  1472. package/dist/utils/functions.cjs.map +1 -1
  1473. package/dist/utils/functions.js +1 -1
  1474. package/dist/utils/functions.js.map +1 -1
  1475. package/dist/utils/guards.cjs.map +1 -1
  1476. package/dist/utils/guards.js.map +1 -1
  1477. package/dist/utils/ids.cjs.map +1 -1
  1478. package/dist/utils/ids.js.map +1 -1
  1479. package/dist/utils/intl-cache.cjs.map +1 -1
  1480. package/dist/utils/intl-cache.js.map +1 -1
  1481. package/dist/utils/json-frame.cjs.map +1 -1
  1482. package/dist/utils/json-frame.js.map +1 -1
  1483. package/dist/utils/numbers.cjs.map +1 -1
  1484. package/dist/utils/numbers.js.map +1 -1
  1485. package/dist/utils/objects.cjs.map +1 -1
  1486. package/dist/utils/objects.js.map +1 -1
  1487. package/dist/{telemetry/app-error-owner.cjs → utils/owner-fingerprint.cjs} +2 -2
  1488. package/dist/utils/owner-fingerprint.cjs.map +1 -0
  1489. package/dist/{telemetry/app-error-owner.js → utils/owner-fingerprint.js} +3 -3
  1490. package/dist/utils/owner-fingerprint.js.map +1 -0
  1491. package/dist/utils/promises.cjs.map +1 -1
  1492. package/dist/utils/promises.js.map +1 -1
  1493. package/dist/utils/relative-time.cjs.map +1 -1
  1494. package/dist/utils/relative-time.js.map +1 -1
  1495. package/dist/utils/safe-url.cjs.map +1 -1
  1496. package/dist/utils/safe-url.js.map +1 -1
  1497. package/dist/utils/schema-like.cjs.map +1 -1
  1498. package/dist/utils/schema-like.js.map +1 -1
  1499. package/dist/utils/storage.cjs +1 -1
  1500. package/dist/utils/storage.cjs.map +1 -1
  1501. package/dist/utils/storage.js +2 -2
  1502. package/dist/utils/storage.js.map +1 -1
  1503. package/dist/utils/strings.cjs.map +1 -1
  1504. package/dist/utils/strings.js.map +1 -1
  1505. package/dist/utils/xlsx.cjs.map +1 -1
  1506. package/dist/utils/xlsx.js.map +1 -1
  1507. package/dist/vision/core/canvas.cjs.map +1 -1
  1508. package/dist/vision/core/canvas.js.map +1 -1
  1509. package/dist/vision/core/dtypes.cjs.map +1 -1
  1510. package/dist/vision/core/dtypes.js.map +1 -1
  1511. package/dist/vision/core/exceptions.cjs.map +1 -1
  1512. package/dist/vision/core/exceptions.js.map +1 -1
  1513. package/dist/vision/core/graph.cjs.map +1 -1
  1514. package/dist/vision/core/graph.js.map +1 -1
  1515. package/dist/vision/core/metadata.cjs +1 -1
  1516. package/dist/vision/core/metadata.cjs.map +1 -1
  1517. package/dist/vision/core/metadata.js +9 -26
  1518. package/dist/vision/core/metadata.js.map +1 -1
  1519. package/dist/vision/core/providers.cjs.map +1 -1
  1520. package/dist/vision/core/providers.js.map +1 -1
  1521. package/dist/vision/core/session.cjs.map +1 -1
  1522. package/dist/vision/core/session.js.map +1 -1
  1523. package/dist/vision/core/timing.cjs.map +1 -1
  1524. package/dist/vision/core/timing.js.map +1 -1
  1525. package/dist/vision/fusion.cjs.map +1 -1
  1526. package/dist/vision/fusion.js.map +1 -1
  1527. package/dist/vision/index.cjs.map +1 -1
  1528. package/dist/vision/index.js.map +1 -1
  1529. package/dist/vision/io/image.cjs.map +1 -1
  1530. package/dist/vision/io/image.js.map +1 -1
  1531. package/dist/vision/labels.cjs.map +1 -1
  1532. package/dist/vision/labels.js.map +1 -1
  1533. package/dist/vision/luminance.cjs.map +1 -1
  1534. package/dist/vision/luminance.js.map +1 -1
  1535. package/dist/vision/normalization.cjs.map +1 -1
  1536. package/dist/vision/normalization.js.map +1 -1
  1537. package/dist/vision/postprocess/classification.cjs.map +1 -1
  1538. package/dist/vision/postprocess/classification.js.map +1 -1
  1539. package/dist/vision/postprocess/detection.cjs.map +1 -1
  1540. package/dist/vision/postprocess/detection.js.map +1 -1
  1541. package/dist/vision/postprocess/segmentation.cjs.map +1 -1
  1542. package/dist/vision/postprocess/segmentation.js.map +1 -1
  1543. package/dist/vision/preprocess/image.cjs.map +1 -1
  1544. package/dist/vision/preprocess/image.js.map +1 -1
  1545. package/dist/vision/preprocess/pipeline.cjs.map +1 -1
  1546. package/dist/vision/preprocess/pipeline.js.map +1 -1
  1547. package/dist/vision/results.cjs.map +1 -1
  1548. package/dist/vision/results.js.map +1 -1
  1549. package/dist/vision/tasks/base.cjs.map +1 -1
  1550. package/dist/vision/tasks/base.js.map +1 -1
  1551. package/dist/vision/tasks/classifier.cjs.map +1 -1
  1552. package/dist/vision/tasks/classifier.js.map +1 -1
  1553. package/dist/vision/tasks/detectClassify.cjs.map +1 -1
  1554. package/dist/vision/tasks/detectClassify.js.map +1 -1
  1555. package/dist/vision/tasks/detector.cjs.map +1 -1
  1556. package/dist/vision/tasks/detector.js.map +1 -1
  1557. package/dist/vision/tasks/segmenter.cjs.map +1 -1
  1558. package/dist/vision/tasks/segmenter.js.map +1 -1
  1559. package/dist/vision/types.cjs.map +1 -1
  1560. package/dist/vision/types.js.map +1 -1
  1561. package/dist/vision/use-camera-stream.cjs +1 -1
  1562. package/dist/vision/use-camera-stream.cjs.map +1 -1
  1563. package/dist/vision/use-camera-stream.js +3 -9
  1564. package/dist/vision/use-camera-stream.js.map +1 -1
  1565. package/dist/vision/use-live-luminance.cjs +1 -1
  1566. package/dist/vision/use-live-luminance.cjs.map +1 -1
  1567. package/dist/vision/use-live-luminance.js +1 -5
  1568. package/dist/vision/use-live-luminance.js.map +1 -1
  1569. package/dist/vite/base-url.cjs.map +1 -1
  1570. package/dist/vite/base-url.js.map +1 -1
  1571. package/dist/vite/create-vite-config.cjs.map +1 -1
  1572. package/dist/vite/create-vite-config.js.map +1 -1
  1573. package/dist/vite/narrow-tokens.cjs.map +1 -1
  1574. package/dist/vite/narrow-tokens.js.map +1 -1
  1575. package/dist/vite/tempest-csp.cjs +2 -2
  1576. package/dist/vite/tempest-csp.cjs.map +1 -1
  1577. package/dist/vite/tempest-csp.js +69 -66
  1578. package/dist/vite/tempest-csp.js.map +1 -1
  1579. package/dist/vite/tempest-icons.cjs.map +1 -1
  1580. package/dist/vite/tempest-icons.js.map +1 -1
  1581. package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
  1582. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  1583. package/dist/vite/tempest-pwa-dev-sw.js +9 -16
  1584. package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
  1585. package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
  1586. package/dist/vite/tempest-pwa-icons.js.map +1 -1
  1587. package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
  1588. package/dist/vite/tempest-pwa-manifest.js.map +1 -1
  1589. package/dist/vite/tempest-styles.cjs.map +1 -1
  1590. package/dist/vite/tempest-styles.js.map +1 -1
  1591. package/dist/vite/tempest-vitest.cjs.map +1 -1
  1592. package/dist/vite/tempest-vitest.js.map +1 -1
  1593. package/dist/vite.d.ts +5 -3
  1594. package/dist/webrtc/link-stats.cjs.map +1 -1
  1595. package/dist/webrtc/link-stats.js.map +1 -1
  1596. package/dist/webrtc/mesh-quality.cjs.map +1 -1
  1597. package/dist/webrtc/mesh-quality.js.map +1 -1
  1598. package/dist/webrtc/opus-sdp.cjs.map +1 -1
  1599. package/dist/webrtc/opus-sdp.js.map +1 -1
  1600. package/dist/webrtc/peer-link.cjs +1 -1
  1601. package/dist/webrtc/peer-link.cjs.map +1 -1
  1602. package/dist/webrtc/peer-link.js +2 -5
  1603. package/dist/webrtc/peer-link.js.map +1 -1
  1604. package/dist/webrtc/peer-mesh.cjs +1 -1
  1605. package/dist/webrtc/peer-mesh.cjs.map +1 -1
  1606. package/dist/webrtc/peer-mesh.js +8 -24
  1607. package/dist/webrtc/peer-mesh.js.map +1 -1
  1608. package/dist/webrtc/sender-bitrate.cjs.map +1 -1
  1609. package/dist/webrtc/sender-bitrate.js.map +1 -1
  1610. package/dist/webrtc/use-link-stats.cjs.map +1 -1
  1611. package/dist/webrtc/use-link-stats.js.map +1 -1
  1612. package/dist/ws/create-web-socket.cjs +1 -1
  1613. package/dist/ws/create-web-socket.cjs.map +1 -1
  1614. package/dist/ws/create-web-socket.js +4 -22
  1615. package/dist/ws/create-web-socket.js.map +1 -1
  1616. package/dist/ws/resilience.cjs.map +1 -1
  1617. package/dist/ws/resilience.js.map +1 -1
  1618. package/dist/ws/use-web-socket.cjs.map +1 -1
  1619. package/dist/ws/use-web-socket.js.map +1 -1
  1620. package/package.json +9 -6
  1621. package/template/package.json +1 -1
  1622. package/dist/telemetry/app-error-owner.cjs.map +0 -1
  1623. package/dist/telemetry/app-error-owner.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tempest-styles.js","names":[],"sources":["../../src/vite/tempest-styles.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { narrowTokens } from \"./narrow-tokens\";\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The stylesheet id an app imports to get exactly the CSS it uses. */\nexport const TEMPEST_STYLES_ID = \"tempest-react-sdk/styles/auto.css\";\n\nconst RESOLVED_ID = \"\\0tempest-styles/auto.css\";\n\n/** Package subpaths whose exports are components with stylesheets. */\nconst SDK_SPECIFIER = /^tempest-react-sdk(\\/(charts|editor|br|icons))?$/;\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\n\n/**\n * The app's own stylesheets, scanned for token reads rather than for imports.\n *\n * An app is documented as free to read `--tempest-*` in CSS of its own, so cutting\n * the token sheet to what the SDK's components reach would drop tokens the app is\n * still reading — and a dropped token is not a smaller sheet, it is an unset\n * property. These files contribute reads to the closure and nothing else.\n */\nconst STYLE_FILE = /\\.(css|scss|sass|less)$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\n/**\n * How much of the reset an app takes.\n *\n * The components are written against the reset — `.tempest_button` assumes\n * `button { background: none; border: 0; padding: 0 }`, every width assumes\n * `box-sizing: border-box` — so `\"none\"` is not \"unstyled document, styled\n * components\": it is components missing their box model. It exists for the app\n * that already ships an equivalent reset of its own.\n */\nexport type ResetMode = \"scoped\" | \"global\" | \"none\";\n\n/**\n * How much of the token sheet an app takes.\n *\n * `\"used\"` emits only the tokens the selected stylesheets — and the app's own CSS —\n * can reach, following token-to-token references. `\"all\"` emits `tokens.css` whole,\n * which is what to reach for when the app names tokens somewhere the scan cannot\n * see: a token read from a CSS-in-JS library, a sibling package's stylesheet, a\n * `<style>` block in `index.html`.\n */\nexport type TokenMode = \"used\" | \"all\";\n\n/** Shape of `dist/styles/manifest.json`. */\nexport interface StyleManifest {\n /** The foundation sheet — tokens and reset together, the pre-split default. */\n core: string;\n /** Public export name → the stylesheets it needs, transitive closure included. */\n components: Record<string, readonly string[]>;\n}\n\nexport interface TempestStylesOptions {\n /** Directory to scan, relative to the Vite root. Default: `\"src\"`. */\n dir?: string;\n /**\n * Which reset to emit alongside the tokens. Default: `\"scoped\"`.\n *\n * `\"scoped\"` confines the reset to `:where([class*=\"tempest_\"])`, so components\n * keep the normalisation they are written against and the app keeps `html`,\n * `body` and `#root`. `\"global\"` is the pre-split behaviour, correct when the\n * SDK owns the whole page. `\"none\"` emits tokens only.\n */\n reset?: ResetMode;\n /**\n * Extra export names to style even when the scan cannot see them — a component\n * reached only through a namespace import, or rendered by a sibling package.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n /**\n * How much of the token sheet to emit. Default: `\"used\"`.\n *\n * The default falls back to the whole sheet on its own whenever a token name is\n * assembled at runtime (`` `var(--tempest-${tone})` ``), so `\"all\"` is for the\n * case the scan cannot see at all — a token read from outside `dir`.\n */\n tokens?: TokenMode;\n}\n\n/**\n * Collect the SDK export names a source file imports.\n *\n * Reads the import specifier rather than the JSX, because a bare `<Button>` in the\n * markup says nothing about where it came from — the app may well define its own.\n * Type-only bindings are dropped: they are erased before any component renders, so\n * paying for their CSS would be paying for nothing.\n *\n * @param code - File contents.\n * @returns The imported names, or `null` when the file namespace-imports the SDK\n * and the set therefore cannot be known.\n */\nexport function scanStyleImports(code: string): string[] | null {\n const names = new Set<string>();\n\n for (const match of code.matchAll(\n /\\bimport\\s+(type\\s+)?([^;'\"]*?)\\s+from\\s*[\"']([^\"']+)[\"']/g,\n )) {\n const [, typeOnly, clause, specifier] = match;\n if (!SDK_SPECIFIER.test(specifier ?? \"\")) continue;\n if (typeOnly) continue;\n if (/^\\s*\\*\\s+as\\s/.test(clause ?? \"\")) return null;\n\n const braces = /\\{([^}]*)\\}/.exec(clause ?? \"\");\n if (!braces) continue;\n for (const part of (braces[1] ?? \"\").split(\",\")) {\n const binding = part.trim();\n if (!binding || binding.startsWith(\"type \")) continue;\n const name = binding.split(/\\s+as\\s+/)[0]?.trim();\n if (name) names.add(name);\n }\n }\n return [...names];\n}\n\n/**\n * Build the stylesheet for a set of export names.\n *\n * Emits `@import` statements rather than inlined rules so Vite's own CSS pipeline\n * resolves, deduplicates and minifies them — the same treatment a hand-written\n * list of imports gets, which is what this replaces.\n *\n * The narrowed token block is the exception, and it goes last for a reason the\n * cascade does not explain: `@import` is only valid before any rule, so a token\n * block written above the imports would make every one of them invalid and the\n * components would arrive with no CSS at all. Putting it last is safe because the\n * block declares custom properties and nothing else — resolving `var()` does not\n * depend on where in the document the declaration sits, only on which selector\n * wins, and no component sheet declares a token to compete with.\n *\n * @param names - Export names the app imports, or `null` to take everything.\n * @param manifest - The published style manifest.\n * @param reset - How much of the reset to emit.\n * @param tokenBlock - The narrowed token sheet, or `null` to `@import` it whole.\n * @returns CSS source.\n */\nexport function buildStyleEntry(\n names: readonly string[] | null,\n manifest: StyleManifest,\n reset: ResetMode = \"scoped\",\n tokenBlock: string | null = null,\n): string {\n const banner = \"/* Generated by tempestStyles() — the CSS this app actually uses. */\\n\";\n const tokenSheets = tokenBlock === null ? [\"tokens.css\"] : [];\n const foundation =\n reset === \"global\"\n ? tokenBlock === null\n ? [manifest.core]\n : [...tokenSheets, \"base.css\"]\n : reset === \"scoped\"\n ? [...tokenSheets, \"scoped.css\"]\n : tokenSheets;\n\n const trailer = tokenBlock === null ? \"\" : `${tokenBlock}\\n`;\n\n if (names === null) {\n const whole =\n reset === \"global\"\n ? [manifest.core, \"../styles.css\"]\n : [...foundation, \"../styles.css\"];\n return `${banner}${whole.map((sheet) => `@import \"tempest-react-sdk/styles/${sheet}\";`).join(\"\\n\")}\\n${trailer}`;\n }\n\n const sheets = new Set<string>();\n for (const name of names) {\n for (const sheet of manifest.components[name] ?? []) sheets.add(sheet);\n }\n const lines = [...foundation, ...[...sheets].sort()].map(\n (sheet) => `@import \"tempest-react-sdk/styles/${sheet}\";`,\n );\n return `${banner}${lines.join(\"\\n\")}\\n${trailer}`;\n}\n\n/**\n * The stylesheets an entry will load, by name, for the token closure to read.\n *\n * @param names - Export names the app imports, or `null` when unknowable.\n * @param manifest - The published style manifest.\n * @param reset - How much of the reset to emit.\n * @returns Stylesheet file names under `dist/styles/`.\n */\nexport function selectedSheets(\n names: readonly string[] | null,\n manifest: StyleManifest,\n reset: ResetMode,\n): string[] {\n if (names === null) return [\"../styles.css\"];\n const sheets = new Set<string>();\n if (reset === \"scoped\") sheets.add(\"scoped.css\");\n if (reset === \"global\") sheets.add(\"base.css\");\n for (const name of names) {\n for (const sheet of manifest.components[name] ?? []) sheets.add(sheet);\n }\n return [...sheets].sort();\n}\n\n/**\n * Walk a directory and collect every source and stylesheet path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (\n entry.isFile() &&\n (SOURCE_FILE.test(entry.name) || STYLE_FILE.test(entry.name))\n ) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Read the style manifest the installed package ships.\n *\n * Resolved by walking the filesystem rather than through `createRequire`, which\n * does not survive the trip: Vite bundles `vite.config.ts` into a temporary module\n * before running it, and in that bundle the `node:module` namespace import comes\n * back without the function — `(0, a.createRequire) is not a function`, at load\n * time, inside the build. The same rewrite is why `import.meta.url` is only the\n * fallback here and the app's `node_modules` is tried first.\n *\n * @param root - The Vite root, where the walk for `node_modules` starts.\n * @returns The manifest and the directory it was read from, which is also where the\n * stylesheets it names live.\n * @throws If no installed copy carries one — an SDK older than the manifest.\n */\nfunction readManifest(root: string): { manifest: StyleManifest; dir: string } {\n const relative = join(\"tempest-react-sdk\", \"dist\", \"styles\", \"manifest.json\");\n const candidates: string[] = [];\n\n let dir = resolve(root);\n for (;;) {\n candidates.push(join(dir, \"node_modules\", relative));\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n candidates.push(join(dirname(fileURLToPath(import.meta.url)), \"..\", \"styles\", \"manifest.json\"));\n\n for (const candidate of candidates) {\n try {\n const manifest = JSON.parse(readFileSync(candidate, \"utf8\")) as StyleManifest;\n return { manifest, dir: dirname(candidate) };\n } catch {\n continue;\n }\n }\n throw new Error(\n \"tempestStyles(): tempest-react-sdk/dist/styles/manifest.json not found — \" +\n \"the installed SDK predates it. Upgrade, or drop the plugin and import \" +\n \"tempest-react-sdk/styles.css.\",\n );\n}\n\n/**\n * Import only the SDK stylesheets your app can actually reach.\n *\n * `styles.css` carries every component the SDK ships — measured at 236.71 kB raw\n * against the 38.94 kB an app mounting twelve of them can reach. The per-component\n * sheets under `styles/` have always closed that gap, but by hand: the app lists\n * them and keeps the list honest as it grows. This plugin makes the list the build's\n * problem, resolving it from the imports the source already writes.\n *\n * The closure matters more than the scan. A component pays for the CSS of every SDK\n * component it renders internally, so `<DataTable>` alone needs six sheets and\n * `<AIChat>` seven — a hand-kept list of one sheet per component the app names is\n * wrong in a way that only shows up as an unstyled child.\n *\n * The reset is the other half, and the half that breaks apps. `styles.css` claims\n * `html`, `body` and `#root`; an app with its own layout that imports it loses its\n * document surface, and an app that drops the import loses the box model its\n * components are written against — components render unstyled, which is what the\n * \"just remove the import\" fix actually costs. The default `reset: \"scoped\"` is the\n * third option: the same normalisation, confined to\n * `:where([class*=\"tempest_\"])`, so it reaches inside a Tempest component and\n * nowhere else.\n *\n * `tempest-react-sdk/styles/auto.css` is a real file, so it resolves without the\n * plugin too — to the complete sheet. Dropping the plugin costs bytes, never\n * correctness.\n *\n * @param options - Scan configuration.\n * @returns The Vite plugin.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestStyles } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestStyles()] });\n *\n * @example\n * // src/main.tsx — the one import, instead of a maintained list\n * import \"tempest-react-sdk/styles/auto.css\";\n */\nexport function tempestStyles(options: TempestStylesOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs, reset = \"scoped\", tokens = \"used\" } = options;\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n let root = process.cwd();\n let names: string[] | null = [...include];\n let appStyleUsage = \"\";\n\n /**\n * Rescan the source tree, keeping the explicitly included names.\n *\n * @tempest-limits empty-catch — the scan races the editor and the file system:\n * a file listed a moment ago can be renamed, deleted, or held by another process\n * by the time it is read. An unreadable file contributes no names and the build\n * continues; failing the whole scan would break `vite dev` over a temp file that\n * no longer exists.\n */\n const rescan = async (): Promise<void> => {\n const found = new Set<string>(include);\n const usage: string[] = [];\n let namespaced = false;\n for (const file of await collectSourceFiles(resolve(root, dir), skip)) {\n let code: string;\n try {\n code = await readFile(file, \"utf8\");\n } catch {\n continue;\n }\n if (code.includes(\"--tempest-\")) usage.push(code);\n if (STYLE_FILE.test(file)) continue;\n const scanned = scanStyleImports(code);\n if (scanned === null) namespaced = true;\n else for (const name of scanned) found.add(name);\n }\n appStyleUsage = usage.join(\"\\n\");\n names = namespaced ? null : [...found];\n };\n\n /**\n * The token block for this build, or `null` to `@import` the whole sheet.\n *\n * Reads the stylesheets the entry is about to load and closes over the tokens\n * they consult, plus the ones the app's own CSS consults. Any read it cannot\n * resolve statically — a name assembled at runtime, an unreadable sheet, a\n * package too old to ship `tokens.css` — returns `null`, which costs bytes and\n * never correctness. Serving a token sheet with a hole in it would do the\n * opposite.\n *\n * @param manifest - The published style manifest.\n * @param styleDir - Directory the manifest and its stylesheets live in.\n * @returns The narrowed sheet, or `null`.\n */\n const narrowedTokens = (manifest: StyleManifest, styleDir: string): string | null => {\n if (tokens === \"all\" || names === null) return null;\n let tokensCss: string;\n try {\n tokensCss = readFileSync(join(styleDir, \"tokens.css\"), \"utf8\");\n } catch {\n return null;\n }\n const parts: string[] = [appStyleUsage];\n for (const sheet of selectedSheets(names, manifest, reset)) {\n try {\n parts.push(readFileSync(join(styleDir, sheet), \"utf8\"));\n } catch {\n return null;\n }\n }\n return narrowTokens(tokensCss, parts.join(\"\\n\"));\n };\n\n return {\n name: \"tempest-styles\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n resolveId(id: string) {\n return id === TEMPEST_STYLES_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n if (id !== RESOLVED_ID) return null;\n const { manifest, dir: styleDir } = readManifest(root);\n return buildStyleEntry(names, manifest, reset, narrowedTokens(manifest, styleDir));\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tempest-styles.js","names":["TEMPEST_STYLES_ID","RESOLVED_ID","SDK_SPECIFIER","SOURCE_FILE","STYLE_FILE","SKIP_DIRS","scanStyleImports","names","match","code","typeOnly","clause","specifier","braces","part","binding","name","buildStyleEntry","banner","tokenSheets","tokenBlock","foundation","reset","manifest","trailer","sheet","sheets","selectedSheets","collectSourceFiles","out","entries","readdir","dir","entry","skip","join","readManifest","relative","candidates","resolve","root","parent","dirname","fileURLToPath","candidate","readFileSync","tempestStyles","options","skipDirs","include","appStyleUsage","rescan","found","usage","namespaced","file","readFile","scanned","narrowedTokens","tokens","tokensCss","styleDir","parts","narrowTokens","config","id"],"sources":["../../src/vite/tempest-styles.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { narrowTokens } from \"./narrow-tokens\";\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The stylesheet id an app imports to get exactly the CSS it uses. */\nexport const TEMPEST_STYLES_ID = \"tempest-react-sdk/styles/auto.css\";\n\nconst RESOLVED_ID = \"\\0tempest-styles/auto.css\";\n\n/** Package subpaths whose exports are components with stylesheets. */\nconst SDK_SPECIFIER = /^tempest-react-sdk(\\/(charts|editor|br|icons))?$/;\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\n\n/**\n * The app's own stylesheets, scanned for token reads rather than for imports.\n *\n * An app is documented as free to read `--tempest-*` in CSS of its own, so cutting\n * the token sheet to what the SDK's components reach would drop tokens the app is\n * still reading — and a dropped token is not a smaller sheet, it is an unset\n * property. These files contribute reads to the closure and nothing else.\n */\nconst STYLE_FILE = /\\.(css|scss|sass|less)$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\n/**\n * How much of the reset an app takes.\n *\n * The components are written against the reset — `.tempest_button` assumes\n * `button { background: none; border: 0; padding: 0 }`, every width assumes\n * `box-sizing: border-box` — so `\"none\"` is not \"unstyled document, styled\n * components\": it is components missing their box model. It exists for the app\n * that already ships an equivalent reset of its own.\n */\nexport type ResetMode = \"scoped\" | \"global\" | \"none\";\n\n/**\n * How much of the token sheet an app takes.\n *\n * `\"used\"` emits only the tokens the selected stylesheets — and the app's own CSS —\n * can reach, following token-to-token references. `\"all\"` emits `tokens.css` whole,\n * which is what to reach for when the app names tokens somewhere the scan cannot\n * see: a token read from a CSS-in-JS library, a sibling package's stylesheet, a\n * `<style>` block in `index.html`.\n */\nexport type TokenMode = \"used\" | \"all\";\n\n/** Shape of `dist/styles/manifest.json`. */\nexport interface StyleManifest {\n /** The foundation sheet — tokens and reset together, the pre-split default. */\n core: string;\n /** Public export name → the stylesheets it needs, transitive closure included. */\n components: Record<string, readonly string[]>;\n}\n\nexport interface TempestStylesOptions {\n /** Directory to scan, relative to the Vite root. Default: `\"src\"`. */\n dir?: string;\n /**\n * Which reset to emit alongside the tokens. Default: `\"scoped\"`.\n *\n * `\"scoped\"` confines the reset to `:where([class*=\"tempest_\"])`, so components\n * keep the normalisation they are written against and the app keeps `html`,\n * `body` and `#root`. `\"global\"` is the pre-split behaviour, correct when the\n * SDK owns the whole page. `\"none\"` emits tokens only.\n */\n reset?: ResetMode;\n /**\n * Extra export names to style even when the scan cannot see them — a component\n * reached only through a namespace import, or rendered by a sibling package.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n /**\n * How much of the token sheet to emit. Default: `\"used\"`.\n *\n * The default falls back to the whole sheet on its own whenever a token name is\n * assembled at runtime (`` `var(--tempest-${tone})` ``), so `\"all\"` is for the\n * case the scan cannot see at all — a token read from outside `dir`.\n */\n tokens?: TokenMode;\n}\n\n/**\n * Collect the SDK export names a source file imports.\n *\n * Reads the import specifier rather than the JSX, because a bare `<Button>` in the\n * markup says nothing about where it came from — the app may well define its own.\n * Type-only bindings are dropped: they are erased before any component renders, so\n * paying for their CSS would be paying for nothing.\n *\n * @param code - File contents.\n * @returns The imported names, or `null` when the file namespace-imports the SDK\n * and the set therefore cannot be known.\n */\nexport function scanStyleImports(code: string): string[] | null {\n const names = new Set<string>();\n\n for (const match of code.matchAll(\n /\\bimport\\s+(type\\s+)?([^;'\"]*?)\\s+from\\s*[\"']([^\"']+)[\"']/g,\n )) {\n const [, typeOnly, clause, specifier] = match;\n if (!SDK_SPECIFIER.test(specifier ?? \"\")) continue;\n if (typeOnly) continue;\n if (/^\\s*\\*\\s+as\\s/.test(clause ?? \"\")) return null;\n\n const braces = /\\{([^}]*)\\}/.exec(clause ?? \"\");\n if (!braces) continue;\n for (const part of (braces[1] ?? \"\").split(\",\")) {\n const binding = part.trim();\n if (!binding || binding.startsWith(\"type \")) continue;\n const name = binding.split(/\\s+as\\s+/)[0]?.trim();\n if (name) names.add(name);\n }\n }\n return [...names];\n}\n\n/**\n * Build the stylesheet for a set of export names.\n *\n * Emits `@import` statements rather than inlined rules so Vite's own CSS pipeline\n * resolves, deduplicates and minifies them — the same treatment a hand-written\n * list of imports gets, which is what this replaces.\n *\n * The narrowed token block is the exception, and it goes last for a reason the\n * cascade does not explain: `@import` is only valid before any rule, so a token\n * block written above the imports would make every one of them invalid and the\n * components would arrive with no CSS at all. Putting it last is safe because the\n * block declares custom properties and nothing else — resolving `var()` does not\n * depend on where in the document the declaration sits, only on which selector\n * wins, and no component sheet declares a token to compete with.\n *\n * @param names - Export names the app imports, or `null` to take everything.\n * @param manifest - The published style manifest.\n * @param reset - How much of the reset to emit.\n * @param tokenBlock - The narrowed token sheet, or `null` to `@import` it whole.\n * @returns CSS source.\n */\nexport function buildStyleEntry(\n names: readonly string[] | null,\n manifest: StyleManifest,\n reset: ResetMode = \"scoped\",\n tokenBlock: string | null = null,\n): string {\n const banner = \"/* Generated by tempestStyles() — the CSS this app actually uses. */\\n\";\n const tokenSheets = tokenBlock === null ? [\"tokens.css\"] : [];\n const foundation =\n reset === \"global\"\n ? tokenBlock === null\n ? [manifest.core]\n : [...tokenSheets, \"base.css\"]\n : reset === \"scoped\"\n ? [...tokenSheets, \"scoped.css\"]\n : tokenSheets;\n\n const trailer = tokenBlock === null ? \"\" : `${tokenBlock}\\n`;\n\n if (names === null) {\n const whole =\n reset === \"global\"\n ? [manifest.core, \"../styles.css\"]\n : [...foundation, \"../styles.css\"];\n return `${banner}${whole.map((sheet) => `@import \"tempest-react-sdk/styles/${sheet}\";`).join(\"\\n\")}\\n${trailer}`;\n }\n\n const sheets = new Set<string>();\n for (const name of names) {\n for (const sheet of manifest.components[name] ?? []) sheets.add(sheet);\n }\n const lines = [...foundation, ...[...sheets].sort()].map(\n (sheet) => `@import \"tempest-react-sdk/styles/${sheet}\";`,\n );\n return `${banner}${lines.join(\"\\n\")}\\n${trailer}`;\n}\n\n/**\n * The stylesheets an entry will load, by name, for the token closure to read.\n *\n * @param names - Export names the app imports, or `null` when unknowable.\n * @param manifest - The published style manifest.\n * @param reset - How much of the reset to emit.\n * @returns Stylesheet file names under `dist/styles/`.\n */\nexport function selectedSheets(\n names: readonly string[] | null,\n manifest: StyleManifest,\n reset: ResetMode,\n): string[] {\n if (names === null) return [\"../styles.css\"];\n const sheets = new Set<string>();\n if (reset === \"scoped\") sheets.add(\"scoped.css\");\n if (reset === \"global\") sheets.add(\"base.css\");\n for (const name of names) {\n for (const sheet of manifest.components[name] ?? []) sheets.add(sheet);\n }\n return [...sheets].sort();\n}\n\n/**\n * Walk a directory and collect every source and stylesheet path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (\n entry.isFile() &&\n (SOURCE_FILE.test(entry.name) || STYLE_FILE.test(entry.name))\n ) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Read the style manifest the installed package ships.\n *\n * Resolved by walking the filesystem rather than through `createRequire`, which\n * does not survive the trip: Vite bundles `vite.config.ts` into a temporary module\n * before running it, and in that bundle the `node:module` namespace import comes\n * back without the function — `(0, a.createRequire) is not a function`, at load\n * time, inside the build. The same rewrite is why `import.meta.url` is only the\n * fallback here and the app's `node_modules` is tried first.\n *\n * @param root - The Vite root, where the walk for `node_modules` starts.\n * @returns The manifest and the directory it was read from, which is also where the\n * stylesheets it names live.\n * @throws If no installed copy carries one — an SDK older than the manifest.\n */\nfunction readManifest(root: string): { manifest: StyleManifest; dir: string } {\n const relative = join(\"tempest-react-sdk\", \"dist\", \"styles\", \"manifest.json\");\n const candidates: string[] = [];\n\n let dir = resolve(root);\n for (;;) {\n candidates.push(join(dir, \"node_modules\", relative));\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n candidates.push(join(dirname(fileURLToPath(import.meta.url)), \"..\", \"styles\", \"manifest.json\"));\n\n for (const candidate of candidates) {\n try {\n const manifest = JSON.parse(readFileSync(candidate, \"utf8\")) as StyleManifest;\n return { manifest, dir: dirname(candidate) };\n } catch {\n continue;\n }\n }\n throw new Error(\n \"tempestStyles(): tempest-react-sdk/dist/styles/manifest.json not found — \" +\n \"the installed SDK predates it. Upgrade, or drop the plugin and import \" +\n \"tempest-react-sdk/styles.css.\",\n );\n}\n\n/**\n * Import only the SDK stylesheets your app can actually reach.\n *\n * `styles.css` carries every component the SDK ships — measured at 236.71 kB raw\n * against the 38.94 kB an app mounting twelve of them can reach. The per-component\n * sheets under `styles/` have always closed that gap, but by hand: the app lists\n * them and keeps the list honest as it grows. This plugin makes the list the build's\n * problem, resolving it from the imports the source already writes.\n *\n * The closure matters more than the scan. A component pays for the CSS of every SDK\n * component it renders internally, so `<DataTable>` alone needs six sheets and\n * `<AIChat>` seven — a hand-kept list of one sheet per component the app names is\n * wrong in a way that only shows up as an unstyled child.\n *\n * The reset is the other half, and the half that breaks apps. `styles.css` claims\n * `html`, `body` and `#root`; an app with its own layout that imports it loses its\n * document surface, and an app that drops the import loses the box model its\n * components are written against — components render unstyled, which is what the\n * \"just remove the import\" fix actually costs. The default `reset: \"scoped\"` is the\n * third option: the same normalisation, confined to\n * `:where([class*=\"tempest_\"])`, so it reaches inside a Tempest component and\n * nowhere else.\n *\n * `tempest-react-sdk/styles/auto.css` is a real file, so it resolves without the\n * plugin too — to the complete sheet. Dropping the plugin costs bytes, never\n * correctness.\n *\n * @param options - Scan configuration.\n * @returns The Vite plugin.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestStyles } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestStyles()] });\n *\n * @example\n * // src/main.tsx — the one import, instead of a maintained list\n * import \"tempest-react-sdk/styles/auto.css\";\n */\nexport function tempestStyles(options: TempestStylesOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs, reset = \"scoped\", tokens = \"used\" } = options;\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n let root = process.cwd();\n let names: string[] | null = [...include];\n let appStyleUsage = \"\";\n\n /**\n * Rescan the source tree, keeping the explicitly included names.\n *\n * @tempest-limits empty-catch — the scan races the editor and the file system:\n * a file listed a moment ago can be renamed, deleted, or held by another process\n * by the time it is read. An unreadable file contributes no names and the build\n * continues; failing the whole scan would break `vite dev` over a temp file that\n * no longer exists.\n */\n const rescan = async (): Promise<void> => {\n const found = new Set<string>(include);\n const usage: string[] = [];\n let namespaced = false;\n for (const file of await collectSourceFiles(resolve(root, dir), skip)) {\n let code: string;\n try {\n code = await readFile(file, \"utf8\");\n } catch {\n continue;\n }\n if (code.includes(\"--tempest-\")) usage.push(code);\n if (STYLE_FILE.test(file)) continue;\n const scanned = scanStyleImports(code);\n if (scanned === null) namespaced = true;\n else for (const name of scanned) found.add(name);\n }\n appStyleUsage = usage.join(\"\\n\");\n names = namespaced ? null : [...found];\n };\n\n /**\n * The token block for this build, or `null` to `@import` the whole sheet.\n *\n * Reads the stylesheets the entry is about to load and closes over the tokens\n * they consult, plus the ones the app's own CSS consults. Any read it cannot\n * resolve statically — a name assembled at runtime, an unreadable sheet, a\n * package too old to ship `tokens.css` — returns `null`, which costs bytes and\n * never correctness. Serving a token sheet with a hole in it would do the\n * opposite.\n *\n * @param manifest - The published style manifest.\n * @param styleDir - Directory the manifest and its stylesheets live in.\n * @returns The narrowed sheet, or `null`.\n */\n const narrowedTokens = (manifest: StyleManifest, styleDir: string): string | null => {\n if (tokens === \"all\" || names === null) return null;\n let tokensCss: string;\n try {\n tokensCss = readFileSync(join(styleDir, \"tokens.css\"), \"utf8\");\n } catch {\n return null;\n }\n const parts: string[] = [appStyleUsage];\n for (const sheet of selectedSheets(names, manifest, reset)) {\n try {\n parts.push(readFileSync(join(styleDir, sheet), \"utf8\"));\n } catch {\n return null;\n }\n }\n return narrowTokens(tokensCss, parts.join(\"\\n\"));\n };\n\n return {\n name: \"tempest-styles\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n resolveId(id: string) {\n return id === TEMPEST_STYLES_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n if (id !== RESOLVED_ID) return null;\n const { manifest, dir: styleDir } = readManifest(root);\n return buildStyleEntry(names, manifest, reset, narrowedTokens(manifest, styleDir));\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tempest-vitest.cjs","names":[],"sources":["../../src/vite/tempest-vitest.ts"],"sourcesContent":["import type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/**\n * The `server.deps.inline` entry that makes Vitest transform the SDK instead of\n * handing it to Node.\n *\n * Vitest matches a string entry as `/node_modules/tempest-react-sdk` anywhere in\n * the module id, which covers npm, Yarn and pnpm's\n * `.pnpm/<name>@<version>/node_modules/` layout alike, without catching an app\n * that merely lives in a folder of the same name — which a bare regex would.\n */\nconst TEMPEST_VITEST_INLINE = \"tempest-react-sdk\";\n\n/** The slice of a user config this plugin reads to stay out of the way. */\ninterface VitestUserConfig {\n test?: { server?: { deps?: { inline?: unknown } } };\n}\n\n/**\n * Make Vitest load the SDK the way the app's bundler does.\n *\n * Every component module imports its own stylesheet. Vitest externalizes an ESM\n * package from `node_modules` to plain Node, and Node has no loader for `.css`, so\n * any test that imports the real barrel dies before running with\n * `TypeError: Unknown file extension \".css\"`. Inlining the package sends it through\n * Vite's pipeline, where the stylesheet import is processed like in the app.\n *\n * Measured with `tempest-react-sdk@0.70.0` and a one-line test importing `Button`:\n * Vitest 2.1.9, 3.2.7 and 4.1.11 all fail under the default config and pass with\n * this plugin.\n *\n * The entry is contributed through the `config` hook, so Vite concatenates it onto\n * whatever `test.server.deps.inline` list the app declares. The one value it must\n * not merge into is `true` (inline everything): Vite's merge would turn it into\n * `[true, \"tempest-react-sdk\"]`, and Vitest 4 then crashes with\n * `ex.test is not a function` matching ids against it. That config already inlines\n * the SDK, so the plugin contributes nothing.\n *\n * Outside Vitest the `test` key is ignored by Vite, so the plugin is inert in\n * `vite dev` and `vite build` — which is why `createViteConfig` always includes it.\n *\n * @returns A Vite plugin; add it to `plugins` in `vite.config.ts` or\n * `vitest.config.ts`.\n *\n * @example\n * // vitest.config.ts\n * import { defineConfig } from \"vitest/config\";\n * import { tempestVitest } from \"tempest-react-sdk/vite\";\n *\n * export default defineConfig({\n * plugins: [tempestVitest()],\n * test: { environment: \"jsdom\" },\n * });\n */\nexport function tempestVitest(): TempestVitePlugin {\n return {\n name: \"tempest-vitest\",\n config(userConfig: VitestUserConfig): VitestUserConfig | undefined {\n if (userConfig.test?.server?.deps?.inline === true) return undefined;\n return { test: { server: { deps: { inline: [TEMPEST_VITEST_INLINE] } } } };\n },\n };\n}\n"],"mappings":"AAsDA,SAAgB,GAAmC,CAC/C,MAAO,CACH,KAAM,iBACN,OAAO,EAA4D,CAC3D,KAAW,MAAM,QAAQ,MAAM,SAAW,GAC9C,MAAO,CAAE,KAAM,CAAE,OAAQ,CAAE,KAAM,CAAE,OAAQ,CAAC,mBAAqB,CAAE,CAAE,CAAE,CAAE,CAC7E,CACJ,CACJ"}
1
+ {"version":3,"file":"tempest-vitest.cjs","names":["tempestVitest","userConfig"],"sources":["../../src/vite/tempest-vitest.ts"],"sourcesContent":["import type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/**\n * The `server.deps.inline` entry that makes Vitest transform the SDK instead of\n * handing it to Node.\n *\n * Vitest matches a string entry as `/node_modules/tempest-react-sdk` anywhere in\n * the module id, which covers npm, Yarn and pnpm's\n * `.pnpm/<name>@<version>/node_modules/` layout alike, without catching an app\n * that merely lives in a folder of the same name — which a bare regex would.\n */\nconst TEMPEST_VITEST_INLINE = \"tempest-react-sdk\";\n\n/** The slice of a user config this plugin reads to stay out of the way. */\ninterface VitestUserConfig {\n test?: { server?: { deps?: { inline?: unknown } } };\n}\n\n/**\n * Make Vitest load the SDK the way the app's bundler does.\n *\n * Every component module imports its own stylesheet. Vitest externalizes an ESM\n * package from `node_modules` to plain Node, and Node has no loader for `.css`, so\n * any test that imports the real barrel dies before running with\n * `TypeError: Unknown file extension \".css\"`. Inlining the package sends it through\n * Vite's pipeline, where the stylesheet import is processed like in the app.\n *\n * Measured with `tempest-react-sdk@0.70.0` and a one-line test importing `Button`:\n * Vitest 2.1.9, 3.2.7 and 4.1.11 all fail under the default config and pass with\n * this plugin.\n *\n * The entry is contributed through the `config` hook, so Vite concatenates it onto\n * whatever `test.server.deps.inline` list the app declares. The one value it must\n * not merge into is `true` (inline everything): Vite's merge would turn it into\n * `[true, \"tempest-react-sdk\"]`, and Vitest 4 then crashes with\n * `ex.test is not a function` matching ids against it. That config already inlines\n * the SDK, so the plugin contributes nothing.\n *\n * Outside Vitest the `test` key is ignored by Vite, so the plugin is inert in\n * `vite dev` and `vite build` — which is why `createViteConfig` always includes it.\n *\n * @returns A Vite plugin; add it to `plugins` in `vite.config.ts` or\n * `vitest.config.ts`.\n *\n * @example\n * // vitest.config.ts\n * import { defineConfig } from \"vitest/config\";\n * import { tempestVitest } from \"tempest-react-sdk/vite\";\n *\n * export default defineConfig({\n * plugins: [tempestVitest()],\n * test: { environment: \"jsdom\" },\n * });\n */\nexport function tempestVitest(): TempestVitePlugin {\n return {\n name: \"tempest-vitest\",\n config(userConfig: VitestUserConfig): VitestUserConfig | undefined {\n if (userConfig.test?.server?.deps?.inline === true) return undefined;\n return { test: { server: { deps: { inline: [TEMPEST_VITEST_INLINE] } } } };\n },\n };\n}\n"],"mappings":"AAsDA,SAAgBA,GAAmC,CAC/C,MAAO,CACH,KAAM,iBACN,OAAO,EAA4D,CAC/D,GAAIC,EAAW,MAAM,QAAQ,MAAM,SAAW,GAC9C,MAAO,CAAE,KAAM,CAAE,OAAQ,CAAE,KAAM,CAAE,OAAQ,CAAC,mBAAqB,CAAE,CAAE,CAAE,CAAE,CAC7E,CACJ,CACJ,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"tempest-vitest.js","names":[],"sources":["../../src/vite/tempest-vitest.ts"],"sourcesContent":["import type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/**\n * The `server.deps.inline` entry that makes Vitest transform the SDK instead of\n * handing it to Node.\n *\n * Vitest matches a string entry as `/node_modules/tempest-react-sdk` anywhere in\n * the module id, which covers npm, Yarn and pnpm's\n * `.pnpm/<name>@<version>/node_modules/` layout alike, without catching an app\n * that merely lives in a folder of the same name — which a bare regex would.\n */\nconst TEMPEST_VITEST_INLINE = \"tempest-react-sdk\";\n\n/** The slice of a user config this plugin reads to stay out of the way. */\ninterface VitestUserConfig {\n test?: { server?: { deps?: { inline?: unknown } } };\n}\n\n/**\n * Make Vitest load the SDK the way the app's bundler does.\n *\n * Every component module imports its own stylesheet. Vitest externalizes an ESM\n * package from `node_modules` to plain Node, and Node has no loader for `.css`, so\n * any test that imports the real barrel dies before running with\n * `TypeError: Unknown file extension \".css\"`. Inlining the package sends it through\n * Vite's pipeline, where the stylesheet import is processed like in the app.\n *\n * Measured with `tempest-react-sdk@0.70.0` and a one-line test importing `Button`:\n * Vitest 2.1.9, 3.2.7 and 4.1.11 all fail under the default config and pass with\n * this plugin.\n *\n * The entry is contributed through the `config` hook, so Vite concatenates it onto\n * whatever `test.server.deps.inline` list the app declares. The one value it must\n * not merge into is `true` (inline everything): Vite's merge would turn it into\n * `[true, \"tempest-react-sdk\"]`, and Vitest 4 then crashes with\n * `ex.test is not a function` matching ids against it. That config already inlines\n * the SDK, so the plugin contributes nothing.\n *\n * Outside Vitest the `test` key is ignored by Vite, so the plugin is inert in\n * `vite dev` and `vite build` — which is why `createViteConfig` always includes it.\n *\n * @returns A Vite plugin; add it to `plugins` in `vite.config.ts` or\n * `vitest.config.ts`.\n *\n * @example\n * // vitest.config.ts\n * import { defineConfig } from \"vitest/config\";\n * import { tempestVitest } from \"tempest-react-sdk/vite\";\n *\n * export default defineConfig({\n * plugins: [tempestVitest()],\n * test: { environment: \"jsdom\" },\n * });\n */\nexport function tempestVitest(): TempestVitePlugin {\n return {\n name: \"tempest-vitest\",\n config(userConfig: VitestUserConfig): VitestUserConfig | undefined {\n if (userConfig.test?.server?.deps?.inline === true) return undefined;\n return { test: { server: { deps: { inline: [TEMPEST_VITEST_INLINE] } } } };\n },\n };\n}\n"],"mappings":"AAsDA,SAAgB,IAAmC;CAC/C,OAAO;EACH,MAAM;EACN,OAAO,GAA4D;GAC3D,MAAW,MAAM,QAAQ,MAAM,WAAW,IAC9C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,mBAAqB,EAAE,EAAE,EAAE,EAAE;EAC7E;CACJ;AACJ"}
1
+ {"version":3,"file":"tempest-vitest.js","names":["tempestVitest","userConfig"],"sources":["../../src/vite/tempest-vitest.ts"],"sourcesContent":["import type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/**\n * The `server.deps.inline` entry that makes Vitest transform the SDK instead of\n * handing it to Node.\n *\n * Vitest matches a string entry as `/node_modules/tempest-react-sdk` anywhere in\n * the module id, which covers npm, Yarn and pnpm's\n * `.pnpm/<name>@<version>/node_modules/` layout alike, without catching an app\n * that merely lives in a folder of the same name — which a bare regex would.\n */\nconst TEMPEST_VITEST_INLINE = \"tempest-react-sdk\";\n\n/** The slice of a user config this plugin reads to stay out of the way. */\ninterface VitestUserConfig {\n test?: { server?: { deps?: { inline?: unknown } } };\n}\n\n/**\n * Make Vitest load the SDK the way the app's bundler does.\n *\n * Every component module imports its own stylesheet. Vitest externalizes an ESM\n * package from `node_modules` to plain Node, and Node has no loader for `.css`, so\n * any test that imports the real barrel dies before running with\n * `TypeError: Unknown file extension \".css\"`. Inlining the package sends it through\n * Vite's pipeline, where the stylesheet import is processed like in the app.\n *\n * Measured with `tempest-react-sdk@0.70.0` and a one-line test importing `Button`:\n * Vitest 2.1.9, 3.2.7 and 4.1.11 all fail under the default config and pass with\n * this plugin.\n *\n * The entry is contributed through the `config` hook, so Vite concatenates it onto\n * whatever `test.server.deps.inline` list the app declares. The one value it must\n * not merge into is `true` (inline everything): Vite's merge would turn it into\n * `[true, \"tempest-react-sdk\"]`, and Vitest 4 then crashes with\n * `ex.test is not a function` matching ids against it. That config already inlines\n * the SDK, so the plugin contributes nothing.\n *\n * Outside Vitest the `test` key is ignored by Vite, so the plugin is inert in\n * `vite dev` and `vite build` — which is why `createViteConfig` always includes it.\n *\n * @returns A Vite plugin; add it to `plugins` in `vite.config.ts` or\n * `vitest.config.ts`.\n *\n * @example\n * // vitest.config.ts\n * import { defineConfig } from \"vitest/config\";\n * import { tempestVitest } from \"tempest-react-sdk/vite\";\n *\n * export default defineConfig({\n * plugins: [tempestVitest()],\n * test: { environment: \"jsdom\" },\n * });\n */\nexport function tempestVitest(): TempestVitePlugin {\n return {\n name: \"tempest-vitest\",\n config(userConfig: VitestUserConfig): VitestUserConfig | undefined {\n if (userConfig.test?.server?.deps?.inline === true) return undefined;\n return { test: { server: { deps: { inline: [TEMPEST_VITEST_INLINE] } } } };\n },\n };\n}\n"],"mappings":"AAsDA,SAAgBA,IAAmC;CAC/C,OAAO;EACH,MAAM;EACN,OAAO,GAA4D;GAC/D,IAAIC,EAAW,MAAM,QAAQ,MAAM,WAAW,IAC9C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,mBAAqB,EAAE,EAAE,EAAE,EAAE;EAC7E;CACJ;AACJ"}
package/dist/vite.d.ts CHANGED
@@ -117,9 +117,11 @@ export declare interface StyleManifest {
117
117
  * merge never loosens what the app wrote:
118
118
  *
119
119
  * - a directive it declares is kept verbatim, except `connect-src`, which gains
120
- * the resolved origins (the list the plugin exists to maintain) unless it is
121
- * `'none'` — declared or inherited from `default-src` — in which case the
122
- * origins stay out and each one is reported as a build warning. Sources in
120
+ * the resolved origins (the list the plugin exists to maintain) unless it
121
+ * matches nothing — `'none'` in any letter case, or an empty list, declared or
122
+ * inherited from `default-src` — in which case the origins stay out and each
123
+ * one is reported as a build warning. Keywords are compared case-insensitively
124
+ * throughout, as browsers do. Sources in
123
125
  * {@link TempestCspOptions.directives} for a declared directive are not added
124
126
  * (a build warning names them); `false` there removes it;
125
127
  * - a fetch directive it does not declare but already governs through the CSP3
@@ -1 +1 @@
1
- {"version":3,"file":"link-stats.cjs","names":[],"sources":["../../src/webrtc/link-stats.ts"],"sourcesContent":["/**\n * One link's outbound picture, in the shape a call UI actually renders.\n *\n * Everything here is derived, not reported: WebRTC hands out cumulative\n * counters and a graph of candidate pairs, and turning that into\n * `\"1,2 Mbps · 42 ms · 1080p60\"` is the work {@link createLinkStatsSampler}\n * does.\n */\nexport interface LinkStats {\n /** Throughput since the previous sample. `0` on the first one — there is no delta yet. */\n kbps: number;\n /** Width of the largest stream being sent, or `0` before one is reported. */\n width: number;\n /** Height of the largest stream being sent, or `0` before one is reported. */\n height: number;\n /** Frame rate of the largest stream being sent, or `0` when the browser omits it. */\n fps: number;\n /** Round trip to the peer in milliseconds, or `null` before the first reading. */\n rttMs: number | null;\n /**\n * Uplink the transport estimates for this path, in kbps, or `null`.\n *\n * `null` and not `0`, because the two mean opposite things: no estimate yet\n * is the normal state for the first seconds of every call and the permanent\n * state on an engine that publishes none, while `0` is indistinguishable\n * from a path that died. A consumer that reads absence as zero drops the\n * quality at the start of every call.\n *\n * This is the field that separates \"healthy at 2.5 Mbps\" from \"capped at\n * 2.5 Mbps and drowning\" — `kbps` reports the cap being honoured either\n * way, while the queue behind it grows.\n */\n availableKbps: number | null;\n /**\n * What the encoder says is holding the picture back, or `null` for nothing.\n *\n * `\"bandwidth\"` wins over the other values when senders disagree, because\n * it is the only one a lower cap answers. Reacting to bandwidth on a\n * machine that is actually CPU-bound buys a worse picture and no relief.\n *\n * The spec's `\"none\"` is reported as `null`: a consumer should not have to\n * know that one of the truthy strings means \"nothing\".\n */\n limitedBy: RTCQualityLimitationReason | null;\n /**\n * Whether the link is travelling through a TURN relay.\n *\n * On a self-hosted mesh this is the hosting bill: a relayed stream goes up\n * and down through the machine somebody is paying for, and the person who\n * picked 4K is not that somebody.\n *\n * Resolved only from the pair the transport **names**, never from a merely\n * `succeeded` one — guessing the route from a pair that carries nothing\n * would report a cost nobody is paying.\n */\n relayed: boolean;\n}\n\n/** Which media a sampler counts. */\nexport type LinkStatsKind = \"video\" | \"audio\" | \"all\";\n\n/** Options for {@link createLinkStatsSampler}. */\nexport interface LinkStatsSamplerOptions {\n /**\n * Which media the throughput counts. Default `\"video\"`.\n *\n * Video is the default because it is what saturates an uplink — audio is an\n * order of magnitude cheaper, and mixing it in moves the number by less than\n * the noise between two samples. Use `\"all\"` when the figure is meant to be\n * the connection's real cost rather than the picture's.\n */\n kind?: LinkStatsKind;\n}\n\n/**\n * A sampler bound to one connection.\n *\n * Holds the previous byte counter and timestamp, which is the whole reason this\n * is an object rather than a function: the rate is a delta, so somebody has to\n * remember the last reading. One sampler per link — sharing one across peers\n * subtracts one connection's counter from another's and reports nonsense.\n */\nexport interface LinkStatsSampler {\n /**\n * Reduce a report you already have.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The stats for this sample.\n */\n read: (report: RTCStatsReport) => LinkStats;\n /**\n * Fetch a report and reduce it.\n *\n * @param connection - The connection to sample.\n * @returns The stats for this sample.\n */\n sample: (connection: RTCPeerConnection) => Promise<LinkStats>;\n /**\n * Drop the baseline the rate is derived from.\n *\n * Call it after an ICE restart, a reconnect, or a pause — otherwise the next\n * sample divides the bytes of the whole gap by the whole gap and reports the\n * average of a period nobody is asking about. The next reading comes back at\n * `0` kbps and starts a fresh baseline; the resolution and round trip already\n * on screen are kept, so the badge does not blank out.\n */\n reset: () => void;\n}\n\n/**\n * The three field readers below take an entry the collector has already\n * established is an object.\n *\n * A report is a `Map` whose values the browser fills, and nothing says they\n * have to be objects — a polyfill or a mock can put anything in there. That\n * check belongs at the door of the one loop that walks the report, not repeated\n * in each reader: three copies of the same guard means three branches no test\n * can reach past the first, and a reader that silently returns `null` for a\n * primitive hides the case instead of skipping it.\n */\ntype StatsEntry = Record<string, unknown>;\n\nfunction numberField(entry: StatsEntry, key: string): number | null {\n const value: unknown = entry[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction stringField(entry: StatsEntry, key: string): string | null {\n const value: unknown = entry[key];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction booleanField(entry: StatsEntry, key: string): boolean | null {\n const value: unknown = entry[key];\n return typeof value === \"boolean\" ? value : null;\n}\n\n/**\n * Whether a string is one of the reasons the spec defines.\n *\n * `\"none\"` is deliberately not one of them here: the field it feeds reports\n * \"nothing is limiting\" as `null`, so a consumer never has to know that one of\n * the truthy strings means no.\n */\nfunction isLimitationReason(value: string | null): value is RTCQualityLimitationReason {\n return value === \"bandwidth\" || value === \"cpu\" || value === \"other\";\n}\n\n/**\n * Resolve the media an RTP entry carries.\n *\n * `kind` is the standard field and `mediaType` is what older Chrome reported;\n * both are still in the wild, and a sampler that reads only one of them\n * silently counts nothing on the browser that uses the other.\n */\nfunction entryKind(entry: StatsEntry): string | null {\n return stringField(entry, \"kind\") ?? stringField(entry, \"mediaType\");\n}\n\n/** What one candidate pair says about the path it describes. */\ninterface PairFacts {\n id: string;\n /** `true` when the browser marks this pair as the chosen one non-standardly. */\n selected: boolean;\n state: string | null;\n rttMs: number | null;\n availableKbps: number | null;\n localCandidateId: string | null;\n}\n\n/** What one sender says about what it is sending. */\ninterface SenderFacts {\n kind: string | null;\n bytes: number;\n width: number;\n height: number;\n fps: number;\n limitedBy: RTCQualityLimitationReason | null;\n}\n\n/** Everything a single walk over a report yields. */\ninterface CollectedReport {\n namedPairId: string | null;\n pairs: PairFacts[];\n relayCandidateIds: Set<string>;\n senders: SenderFacts[];\n}\n\n/**\n * Reduce a report in **one** pass.\n *\n * One pass is the point rather than tidiness. Every field below lives in the\n * same report, and the pair the transport selected has to be resolved before\n * any of the path fields can be read — so a consumer that asks for round trip,\n * then throughput headroom, then whether the route is relayed, walks the same\n * report three times and resolves the same pair three times, per link, on every\n * tick. On a mesh of eight at one sample every two seconds that is the most\n * expensive recurring work in the call, on the device least able to pay it.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The entries that matter, grouped.\n */\nfunction collect(report: RTCStatsReport): CollectedReport {\n let namedPairId: string | null = null;\n const pairs: PairFacts[] = [];\n const relayCandidateIds = new Set<string>();\n const senders: SenderFacts[] = [];\n\n report.forEach((raw: unknown) => {\n if (typeof raw !== \"object\" || raw === null) return;\n const entry = raw as StatsEntry;\n const type = stringField(entry, \"type\");\n if (type === \"transport\") {\n namedPairId = stringField(entry, \"selectedCandidatePairId\") ?? namedPairId;\n return;\n }\n if (type === \"candidate-pair\") {\n const seconds = numberField(entry, \"currentRoundTripTime\");\n const bps = numberField(entry, \"availableOutgoingBitrate\");\n pairs.push({\n id: stringField(entry, \"id\") ?? \"\",\n selected: booleanField(entry, \"selected\") === true,\n state: stringField(entry, \"state\"),\n rttMs: seconds === null ? null : seconds * 1000,\n availableKbps: bps === null ? null : Math.round(bps / 1000),\n localCandidateId: stringField(entry, \"localCandidateId\"),\n });\n return;\n }\n if (type === \"local-candidate\") {\n const id = stringField(entry, \"id\");\n if (id !== null && stringField(entry, \"candidateType\") === \"relay\") {\n relayCandidateIds.add(id);\n }\n return;\n }\n if (type !== \"outbound-rtp\") return;\n const reason = stringField(entry, \"qualityLimitationReason\");\n senders.push({\n kind: entryKind(entry),\n bytes: numberField(entry, \"bytesSent\") ?? 0,\n width: numberField(entry, \"frameWidth\") ?? 0,\n height: numberField(entry, \"frameHeight\") ?? 0,\n fps: Math.round(numberField(entry, \"framesPerSecond\") ?? 0),\n limitedBy: isLimitationReason(reason) ? reason : null,\n });\n });\n\n return { namedPairId, pairs, relayCandidateIds, senders };\n}\n\n/**\n * The candidate pair carrying the link, and how sure we are that it is.\n *\n * A connection routinely keeps several viable pairs alive at once — host,\n * server-reflexive, relayed — and only one of them carries traffic. Reading the\n * first `succeeded` pair makes a reading jump between paths that are not being\n * travelled: 8 ms on an idle host pair alternating with 180 ms on the TURN pair\n * doing the work.\n *\n * The chain is `transport.selectedCandidatePairId` → a pair flagged\n * `selected: true` → the first `succeeded` one. The middle step is not in the\n * spec and is there because an engine that fills neither the transport field\n * nor it does not appear to exist, while one that fills only the flag does: a\n * reader that skips straight to `succeeded` silently answers about the wrong\n * path there. The last step is a guess, and `named` says so — the fields where\n * guessing would report something false refuse it.\n *\n * @param collected - A collected report.\n * @returns The pair and whether the browser actually named it.\n */\nfunction carryingPair(collected: CollectedReport): { pair: PairFacts | null; named: boolean } {\n const byId =\n collected.namedPairId === null\n ? undefined\n : collected.pairs.find((pair) => pair.id === collected.namedPairId);\n if (byId !== undefined) return { pair: byId, named: true };\n\n const flagged = collected.pairs.find((pair) => pair.selected);\n if (flagged !== undefined) return { pair: flagged, named: true };\n\n const succeeded = collected.pairs.find((pair) => pair.state === \"succeeded\");\n return { pair: succeeded ?? null, named: false };\n}\n\n/**\n * Read the round trip of the candidate pair actually carrying the link.\n *\n * A `succeeded` pair is kept as a last resort because not every browser names\n * the selected one — losing the reading entirely is worse than an occasionally\n * optimistic one. See {@link carryingPair} for the chain and why the middle\n * step exists.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns Round trip in milliseconds, rounded, or `null` when nothing reported\n * one — which is the normal state before the connection settles.\n *\n * @example\n * const rttMs = readRoundTripMs(await pc.getStats());\n */\nexport function readRoundTripMs(report: RTCStatsReport): number | null {\n return roundTripOf(collect(report));\n}\n\n/**\n * Read the uplink the transport estimates for this path, in kbps.\n *\n * This is the field that tells a cap being honoured apart from a cap that is\n * drowning: `bytesSent` reports the same 2500 kbps whether the path has room\n * for it or the queue behind it is growing. No fixed budget can stand in for it\n * — a domestic uplink of 1 Mbps and a fibre link differ by an order of\n * magnitude, and in Brazil the upload routinely is a tenth of the download\n * beside it.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The estimate in kbps, or `null` while there is none. Every reader\n * needs a fallback for that, not a default of zero.\n *\n * @example\n * const headroom = readAvailableOutgoingKbps(await pc.getStats());\n * if (headroom !== null && headroom < asked) lowerTheCap(headroom);\n */\nexport function readAvailableOutgoingKbps(report: RTCStatsReport): number | null {\n return availableOf(collect(report));\n}\n\n/**\n * Read what the encoder says is holding the picture back.\n *\n * `\"bandwidth\"` wins when senders disagree, because it is the only reason a\n * lower cap answers. The spec's `\"none\"` comes back as `null`.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The reason, or `null` when nothing is limiting the picture.\n *\n * @example\n * if (readQualityLimitation(await pc.getStats()) === \"cpu\") stopBlurringTheBackground();\n */\nexport function readQualityLimitation(report: RTCStatsReport): RTCQualityLimitationReason | null {\n return limitationOf(collect(report));\n}\n\n/**\n * Read whether the link is travelling through a TURN relay.\n *\n * Resolved only from the pair the browser names, never from a merely\n * `succeeded` one: a relayed route is somebody's hosting bill, and reporting\n * one from a pair that carries nothing bills a cost nobody is paying.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns `true` when the carrying pair's local candidate is a relay.\n *\n * @example\n * if (readRelayed(await pc.getStats())) capTheStreamThatCostsMoney();\n */\nexport function readRelayed(report: RTCStatsReport): boolean {\n return relayedOf(collect(report));\n}\n\n/** Round trip of the carrying pair, in whole milliseconds. */\nfunction roundTripOf(collected: CollectedReport): number | null {\n const withTiming: CollectedReport = {\n ...collected,\n pairs: collected.pairs.filter((pair) => pair.rttMs !== null),\n };\n const { pair } = carryingPair(withTiming);\n return pair?.rttMs === undefined || pair.rttMs === null ? null : Math.round(pair.rttMs);\n}\n\n/** Estimated uplink of the carrying pair, in kbps. */\nfunction availableOf(collected: CollectedReport): number | null {\n const { pair } = carryingPair(collected);\n return pair?.availableKbps ?? null;\n}\n\n/** Whether the pair the browser named travels through a relay. */\nfunction relayedOf(collected: CollectedReport): boolean {\n const { pair, named } = carryingPair(collected);\n if (!named || pair === null || pair.localCandidateId === null) return false;\n return collected.relayCandidateIds.has(pair.localCandidateId);\n}\n\n/** The strongest limitation any sender reports, with bandwidth winning. */\nfunction limitationOf(collected: CollectedReport): RTCQualityLimitationReason | null {\n let found: RTCQualityLimitationReason | null = null;\n for (const sender of collected.senders) {\n if (sender.limitedBy === null) continue;\n if (sender.limitedBy === \"bandwidth\") return \"bandwidth\";\n found = found ?? sender.limitedBy;\n }\n return found;\n}\n\n/**\n * Track one link's throughput, resolution and round trip across samples.\n *\n * Every rate here is a **delta**. `bytesSent` is cumulative since the connection\n * opened, so dividing it by the session length gives the historical average —\n * a number that only ever falls and never shows what is happening now. The\n * previous reading is kept on the sampler and subtracted, which is the part\n * every hand-rolled copy of this ends up rewriting.\n *\n * Bytes are summed across every matching sender, because a peer publishing a\n * camera and a screen at once occupies one uplink with both — and the uplink is\n * what runs out. Resolution and frame rate come from the **largest** stream by\n * area, which is the one that dominates that bandwidth and the one somebody\n * watching the call is looking at.\n *\n * @param options - See {@link LinkStatsSamplerOptions}.\n * @returns A sampler. Use one per `RTCPeerConnection`.\n *\n * @example\n * const sampler = createLinkStatsSampler();\n *\n * setInterval(async () => {\n * const stats = await sampler.sample(pc);\n * badge.textContent = `${stats.kbps} kbps · ${stats.rttMs ?? \"—\"} ms`;\n * }, 2000);\n */\nexport function createLinkStatsSampler(options: LinkStatsSamplerOptions = {}): LinkStatsSampler {\n const kind: LinkStatsKind = options.kind ?? \"video\";\n let lastBytes: number | null = null;\n let lastSampleAt = 0;\n let last: LinkStats = {\n kbps: 0,\n width: 0,\n height: 0,\n fps: 0,\n rttMs: null,\n availableKbps: null,\n limitedBy: null,\n relayed: false,\n };\n\n function read(report: RTCStatsReport): LinkStats {\n const now = performance.now();\n const collected = collect(report);\n const path = {\n rttMs: roundTripOf(collected),\n availableKbps: availableOf(collected),\n limitedBy: limitationOf(collected),\n relayed: relayedOf(collected),\n };\n\n let bytes = 0;\n let sawSender = false;\n let bestArea = 0;\n let width = 0;\n let height = 0;\n let fps = 0;\n\n for (const sender of collected.senders) {\n if (kind !== \"all\" && sender.kind !== kind) continue;\n sawSender = true;\n bytes += sender.bytes;\n\n const area = sender.width * sender.height;\n if (area < bestArea) continue;\n bestArea = area;\n width = sender.width;\n height = sender.height;\n fps = sender.fps;\n }\n\n if (!sawSender) {\n last = { ...last, ...path };\n return last;\n }\n\n const elapsed = lastBytes === null ? 0 : (now - lastSampleAt) / 1000;\n const delta = lastBytes === null ? 0 : bytes - lastBytes;\n const kbps = elapsed > 0 && delta > 0 ? Math.round((delta * 8) / 1000 / elapsed) : 0;\n\n lastBytes = bytes;\n lastSampleAt = now;\n last = {\n kbps,\n width: width > 0 ? width : last.width,\n height: height > 0 ? height : last.height,\n fps: fps > 0 ? fps : last.fps,\n ...path,\n };\n return last;\n }\n\n return {\n read,\n sample: async (connection: RTCPeerConnection): Promise<LinkStats> =>\n read(await connection.getStats()),\n reset: (): void => {\n lastBytes = null;\n lastSampleAt = 0;\n last = { ...last, kbps: 0 };\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"link-stats.cjs","names":["numberField","value","entry","key","stringField","booleanField","isLimitationReason","entryKind","collect","namedPairId","pairs","relayCandidateIds","senders","report","raw","type","seconds","bps","id","reason","carryingPair","byId","collected","pair","flagged","readRoundTripMs","roundTripOf","readAvailableOutgoingKbps","availableOf","readQualityLimitation","limitationOf","readRelayed","relayedOf","named","found","sender","createLinkStatsSampler","kind","options","lastBytes","lastSampleAt","last","read","now","path","bytes","sawSender","bestArea","width","height","fps","area","elapsed","delta","kbps","connection"],"sources":["../../src/webrtc/link-stats.ts"],"sourcesContent":["/**\n * One link's outbound picture, in the shape a call UI actually renders.\n *\n * Everything here is derived, not reported: WebRTC hands out cumulative\n * counters and a graph of candidate pairs, and turning that into\n * `\"1,2 Mbps · 42 ms · 1080p60\"` is the work {@link createLinkStatsSampler}\n * does.\n */\nexport interface LinkStats {\n /** Throughput since the previous sample. `0` on the first one — there is no delta yet. */\n kbps: number;\n /** Width of the largest stream being sent, or `0` before one is reported. */\n width: number;\n /** Height of the largest stream being sent, or `0` before one is reported. */\n height: number;\n /** Frame rate of the largest stream being sent, or `0` when the browser omits it. */\n fps: number;\n /** Round trip to the peer in milliseconds, or `null` before the first reading. */\n rttMs: number | null;\n /**\n * Uplink the transport estimates for this path, in kbps, or `null`.\n *\n * `null` and not `0`, because the two mean opposite things: no estimate yet\n * is the normal state for the first seconds of every call and the permanent\n * state on an engine that publishes none, while `0` is indistinguishable\n * from a path that died. A consumer that reads absence as zero drops the\n * quality at the start of every call.\n *\n * This is the field that separates \"healthy at 2.5 Mbps\" from \"capped at\n * 2.5 Mbps and drowning\" — `kbps` reports the cap being honoured either\n * way, while the queue behind it grows.\n */\n availableKbps: number | null;\n /**\n * What the encoder says is holding the picture back, or `null` for nothing.\n *\n * `\"bandwidth\"` wins over the other values when senders disagree, because\n * it is the only one a lower cap answers. Reacting to bandwidth on a\n * machine that is actually CPU-bound buys a worse picture and no relief.\n *\n * The spec's `\"none\"` is reported as `null`: a consumer should not have to\n * know that one of the truthy strings means \"nothing\".\n */\n limitedBy: RTCQualityLimitationReason | null;\n /**\n * Whether the link is travelling through a TURN relay.\n *\n * On a self-hosted mesh this is the hosting bill: a relayed stream goes up\n * and down through the machine somebody is paying for, and the person who\n * picked 4K is not that somebody.\n *\n * Resolved only from the pair the transport **names**, never from a merely\n * `succeeded` one — guessing the route from a pair that carries nothing\n * would report a cost nobody is paying.\n */\n relayed: boolean;\n}\n\n/** Which media a sampler counts. */\nexport type LinkStatsKind = \"video\" | \"audio\" | \"all\";\n\n/** Options for {@link createLinkStatsSampler}. */\nexport interface LinkStatsSamplerOptions {\n /**\n * Which media the throughput counts. Default `\"video\"`.\n *\n * Video is the default because it is what saturates an uplink — audio is an\n * order of magnitude cheaper, and mixing it in moves the number by less than\n * the noise between two samples. Use `\"all\"` when the figure is meant to be\n * the connection's real cost rather than the picture's.\n */\n kind?: LinkStatsKind;\n}\n\n/**\n * A sampler bound to one connection.\n *\n * Holds the previous byte counter and timestamp, which is the whole reason this\n * is an object rather than a function: the rate is a delta, so somebody has to\n * remember the last reading. One sampler per link — sharing one across peers\n * subtracts one connection's counter from another's and reports nonsense.\n */\nexport interface LinkStatsSampler {\n /**\n * Reduce a report you already have.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The stats for this sample.\n */\n read: (report: RTCStatsReport) => LinkStats;\n /**\n * Fetch a report and reduce it.\n *\n * @param connection - The connection to sample.\n * @returns The stats for this sample.\n */\n sample: (connection: RTCPeerConnection) => Promise<LinkStats>;\n /**\n * Drop the baseline the rate is derived from.\n *\n * Call it after an ICE restart, a reconnect, or a pause — otherwise the next\n * sample divides the bytes of the whole gap by the whole gap and reports the\n * average of a period nobody is asking about. The next reading comes back at\n * `0` kbps and starts a fresh baseline; the resolution and round trip already\n * on screen are kept, so the badge does not blank out.\n */\n reset: () => void;\n}\n\n/**\n * The three field readers below take an entry the collector has already\n * established is an object.\n *\n * A report is a `Map` whose values the browser fills, and nothing says they\n * have to be objects — a polyfill or a mock can put anything in there. That\n * check belongs at the door of the one loop that walks the report, not repeated\n * in each reader: three copies of the same guard means three branches no test\n * can reach past the first, and a reader that silently returns `null` for a\n * primitive hides the case instead of skipping it.\n */\ntype StatsEntry = Record<string, unknown>;\n\nfunction numberField(entry: StatsEntry, key: string): number | null {\n const value: unknown = entry[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction stringField(entry: StatsEntry, key: string): string | null {\n const value: unknown = entry[key];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction booleanField(entry: StatsEntry, key: string): boolean | null {\n const value: unknown = entry[key];\n return typeof value === \"boolean\" ? value : null;\n}\n\n/**\n * Whether a string is one of the reasons the spec defines.\n *\n * `\"none\"` is deliberately not one of them here: the field it feeds reports\n * \"nothing is limiting\" as `null`, so a consumer never has to know that one of\n * the truthy strings means no.\n */\nfunction isLimitationReason(value: string | null): value is RTCQualityLimitationReason {\n return value === \"bandwidth\" || value === \"cpu\" || value === \"other\";\n}\n\n/**\n * Resolve the media an RTP entry carries.\n *\n * `kind` is the standard field and `mediaType` is what older Chrome reported;\n * both are still in the wild, and a sampler that reads only one of them\n * silently counts nothing on the browser that uses the other.\n */\nfunction entryKind(entry: StatsEntry): string | null {\n return stringField(entry, \"kind\") ?? stringField(entry, \"mediaType\");\n}\n\n/** What one candidate pair says about the path it describes. */\ninterface PairFacts {\n id: string;\n /** `true` when the browser marks this pair as the chosen one non-standardly. */\n selected: boolean;\n state: string | null;\n rttMs: number | null;\n availableKbps: number | null;\n localCandidateId: string | null;\n}\n\n/** What one sender says about what it is sending. */\ninterface SenderFacts {\n kind: string | null;\n bytes: number;\n width: number;\n height: number;\n fps: number;\n limitedBy: RTCQualityLimitationReason | null;\n}\n\n/** Everything a single walk over a report yields. */\ninterface CollectedReport {\n namedPairId: string | null;\n pairs: PairFacts[];\n relayCandidateIds: Set<string>;\n senders: SenderFacts[];\n}\n\n/**\n * Reduce a report in **one** pass.\n *\n * One pass is the point rather than tidiness. Every field below lives in the\n * same report, and the pair the transport selected has to be resolved before\n * any of the path fields can be read — so a consumer that asks for round trip,\n * then throughput headroom, then whether the route is relayed, walks the same\n * report three times and resolves the same pair three times, per link, on every\n * tick. On a mesh of eight at one sample every two seconds that is the most\n * expensive recurring work in the call, on the device least able to pay it.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The entries that matter, grouped.\n */\nfunction collect(report: RTCStatsReport): CollectedReport {\n let namedPairId: string | null = null;\n const pairs: PairFacts[] = [];\n const relayCandidateIds = new Set<string>();\n const senders: SenderFacts[] = [];\n\n report.forEach((raw: unknown) => {\n if (typeof raw !== \"object\" || raw === null) return;\n const entry = raw as StatsEntry;\n const type = stringField(entry, \"type\");\n if (type === \"transport\") {\n namedPairId = stringField(entry, \"selectedCandidatePairId\") ?? namedPairId;\n return;\n }\n if (type === \"candidate-pair\") {\n const seconds = numberField(entry, \"currentRoundTripTime\");\n const bps = numberField(entry, \"availableOutgoingBitrate\");\n pairs.push({\n id: stringField(entry, \"id\") ?? \"\",\n selected: booleanField(entry, \"selected\") === true,\n state: stringField(entry, \"state\"),\n rttMs: seconds === null ? null : seconds * 1000,\n availableKbps: bps === null ? null : Math.round(bps / 1000),\n localCandidateId: stringField(entry, \"localCandidateId\"),\n });\n return;\n }\n if (type === \"local-candidate\") {\n const id = stringField(entry, \"id\");\n if (id !== null && stringField(entry, \"candidateType\") === \"relay\") {\n relayCandidateIds.add(id);\n }\n return;\n }\n if (type !== \"outbound-rtp\") return;\n const reason = stringField(entry, \"qualityLimitationReason\");\n senders.push({\n kind: entryKind(entry),\n bytes: numberField(entry, \"bytesSent\") ?? 0,\n width: numberField(entry, \"frameWidth\") ?? 0,\n height: numberField(entry, \"frameHeight\") ?? 0,\n fps: Math.round(numberField(entry, \"framesPerSecond\") ?? 0),\n limitedBy: isLimitationReason(reason) ? reason : null,\n });\n });\n\n return { namedPairId, pairs, relayCandidateIds, senders };\n}\n\n/**\n * The candidate pair carrying the link, and how sure we are that it is.\n *\n * A connection routinely keeps several viable pairs alive at once — host,\n * server-reflexive, relayed — and only one of them carries traffic. Reading the\n * first `succeeded` pair makes a reading jump between paths that are not being\n * travelled: 8 ms on an idle host pair alternating with 180 ms on the TURN pair\n * doing the work.\n *\n * The chain is `transport.selectedCandidatePairId` → a pair flagged\n * `selected: true` → the first `succeeded` one. The middle step is not in the\n * spec and is there because an engine that fills neither the transport field\n * nor it does not appear to exist, while one that fills only the flag does: a\n * reader that skips straight to `succeeded` silently answers about the wrong\n * path there. The last step is a guess, and `named` says so — the fields where\n * guessing would report something false refuse it.\n *\n * @param collected - A collected report.\n * @returns The pair and whether the browser actually named it.\n */\nfunction carryingPair(collected: CollectedReport): { pair: PairFacts | null; named: boolean } {\n const byId =\n collected.namedPairId === null\n ? undefined\n : collected.pairs.find((pair) => pair.id === collected.namedPairId);\n if (byId !== undefined) return { pair: byId, named: true };\n\n const flagged = collected.pairs.find((pair) => pair.selected);\n if (flagged !== undefined) return { pair: flagged, named: true };\n\n const succeeded = collected.pairs.find((pair) => pair.state === \"succeeded\");\n return { pair: succeeded ?? null, named: false };\n}\n\n/**\n * Read the round trip of the candidate pair actually carrying the link.\n *\n * A `succeeded` pair is kept as a last resort because not every browser names\n * the selected one — losing the reading entirely is worse than an occasionally\n * optimistic one. See {@link carryingPair} for the chain and why the middle\n * step exists.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns Round trip in milliseconds, rounded, or `null` when nothing reported\n * one — which is the normal state before the connection settles.\n *\n * @example\n * const rttMs = readRoundTripMs(await pc.getStats());\n */\nexport function readRoundTripMs(report: RTCStatsReport): number | null {\n return roundTripOf(collect(report));\n}\n\n/**\n * Read the uplink the transport estimates for this path, in kbps.\n *\n * This is the field that tells a cap being honoured apart from a cap that is\n * drowning: `bytesSent` reports the same 2500 kbps whether the path has room\n * for it or the queue behind it is growing. No fixed budget can stand in for it\n * — a domestic uplink of 1 Mbps and a fibre link differ by an order of\n * magnitude, and in Brazil the upload routinely is a tenth of the download\n * beside it.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The estimate in kbps, or `null` while there is none. Every reader\n * needs a fallback for that, not a default of zero.\n *\n * @example\n * const headroom = readAvailableOutgoingKbps(await pc.getStats());\n * if (headroom !== null && headroom < asked) lowerTheCap(headroom);\n */\nexport function readAvailableOutgoingKbps(report: RTCStatsReport): number | null {\n return availableOf(collect(report));\n}\n\n/**\n * Read what the encoder says is holding the picture back.\n *\n * `\"bandwidth\"` wins when senders disagree, because it is the only reason a\n * lower cap answers. The spec's `\"none\"` comes back as `null`.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The reason, or `null` when nothing is limiting the picture.\n *\n * @example\n * if (readQualityLimitation(await pc.getStats()) === \"cpu\") stopBlurringTheBackground();\n */\nexport function readQualityLimitation(report: RTCStatsReport): RTCQualityLimitationReason | null {\n return limitationOf(collect(report));\n}\n\n/**\n * Read whether the link is travelling through a TURN relay.\n *\n * Resolved only from the pair the browser names, never from a merely\n * `succeeded` one: a relayed route is somebody's hosting bill, and reporting\n * one from a pair that carries nothing bills a cost nobody is paying.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns `true` when the carrying pair's local candidate is a relay.\n *\n * @example\n * if (readRelayed(await pc.getStats())) capTheStreamThatCostsMoney();\n */\nexport function readRelayed(report: RTCStatsReport): boolean {\n return relayedOf(collect(report));\n}\n\n/** Round trip of the carrying pair, in whole milliseconds. */\nfunction roundTripOf(collected: CollectedReport): number | null {\n const withTiming: CollectedReport = {\n ...collected,\n pairs: collected.pairs.filter((pair) => pair.rttMs !== null),\n };\n const { pair } = carryingPair(withTiming);\n return pair?.rttMs === undefined || pair.rttMs === null ? null : Math.round(pair.rttMs);\n}\n\n/** Estimated uplink of the carrying pair, in kbps. */\nfunction availableOf(collected: CollectedReport): number | null {\n const { pair } = carryingPair(collected);\n return pair?.availableKbps ?? null;\n}\n\n/** Whether the pair the browser named travels through a relay. */\nfunction relayedOf(collected: CollectedReport): boolean {\n const { pair, named } = carryingPair(collected);\n if (!named || pair === null || pair.localCandidateId === null) return false;\n return collected.relayCandidateIds.has(pair.localCandidateId);\n}\n\n/** The strongest limitation any sender reports, with bandwidth winning. */\nfunction limitationOf(collected: CollectedReport): RTCQualityLimitationReason | null {\n let found: RTCQualityLimitationReason | null = null;\n for (const sender of collected.senders) {\n if (sender.limitedBy === null) continue;\n if (sender.limitedBy === \"bandwidth\") return \"bandwidth\";\n found = found ?? sender.limitedBy;\n }\n return found;\n}\n\n/**\n * Track one link's throughput, resolution and round trip across samples.\n *\n * Every rate here is a **delta**. `bytesSent` is cumulative since the connection\n * opened, so dividing it by the session length gives the historical average —\n * a number that only ever falls and never shows what is happening now. The\n * previous reading is kept on the sampler and subtracted, which is the part\n * every hand-rolled copy of this ends up rewriting.\n *\n * Bytes are summed across every matching sender, because a peer publishing a\n * camera and a screen at once occupies one uplink with both — and the uplink is\n * what runs out. Resolution and frame rate come from the **largest** stream by\n * area, which is the one that dominates that bandwidth and the one somebody\n * watching the call is looking at.\n *\n * @param options - See {@link LinkStatsSamplerOptions}.\n * @returns A sampler. Use one per `RTCPeerConnection`.\n *\n * @example\n * const sampler = createLinkStatsSampler();\n *\n * setInterval(async () => {\n * const stats = await sampler.sample(pc);\n * badge.textContent = `${stats.kbps} kbps · ${stats.rttMs ?? \"—\"} ms`;\n * }, 2000);\n */\nexport function createLinkStatsSampler(options: LinkStatsSamplerOptions = {}): LinkStatsSampler {\n const kind: LinkStatsKind = options.kind ?? \"video\";\n let lastBytes: number | null = null;\n let lastSampleAt = 0;\n let last: LinkStats = {\n kbps: 0,\n width: 0,\n height: 0,\n fps: 0,\n rttMs: null,\n availableKbps: null,\n limitedBy: null,\n relayed: false,\n };\n\n function read(report: RTCStatsReport): LinkStats {\n const now = performance.now();\n const collected = collect(report);\n const path = {\n rttMs: roundTripOf(collected),\n availableKbps: availableOf(collected),\n limitedBy: limitationOf(collected),\n relayed: relayedOf(collected),\n };\n\n let bytes = 0;\n let sawSender = false;\n let bestArea = 0;\n let width = 0;\n let height = 0;\n let fps = 0;\n\n for (const sender of collected.senders) {\n if (kind !== \"all\" && sender.kind !== kind) continue;\n sawSender = true;\n bytes += sender.bytes;\n\n const area = sender.width * sender.height;\n if (area < bestArea) continue;\n bestArea = area;\n width = sender.width;\n height = sender.height;\n fps = sender.fps;\n }\n\n if (!sawSender) {\n last = { ...last, ...path };\n return last;\n }\n\n const elapsed = lastBytes === null ? 0 : (now - lastSampleAt) / 1000;\n const delta = lastBytes === null ? 0 : bytes - lastBytes;\n const kbps = elapsed > 0 && delta > 0 ? Math.round((delta * 8) / 1000 / elapsed) : 0;\n\n lastBytes = bytes;\n lastSampleAt = now;\n last = {\n kbps,\n width: width > 0 ? width : last.width,\n height: height > 0 ? height : last.height,\n fps: fps > 0 ? fps : last.fps,\n ...path,\n };\n return last;\n }\n\n return {\n read,\n sample: async (connection: RTCPeerConnection): Promise<LinkStats> =>\n read(await connection.getStats()),\n reset: (): void => {\n lastBytes = null;\n lastSampleAt = 0;\n last = { ...last, kbps: 0 };\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"link-stats.js","names":[],"sources":["../../src/webrtc/link-stats.ts"],"sourcesContent":["/**\n * One link's outbound picture, in the shape a call UI actually renders.\n *\n * Everything here is derived, not reported: WebRTC hands out cumulative\n * counters and a graph of candidate pairs, and turning that into\n * `\"1,2 Mbps · 42 ms · 1080p60\"` is the work {@link createLinkStatsSampler}\n * does.\n */\nexport interface LinkStats {\n /** Throughput since the previous sample. `0` on the first one — there is no delta yet. */\n kbps: number;\n /** Width of the largest stream being sent, or `0` before one is reported. */\n width: number;\n /** Height of the largest stream being sent, or `0` before one is reported. */\n height: number;\n /** Frame rate of the largest stream being sent, or `0` when the browser omits it. */\n fps: number;\n /** Round trip to the peer in milliseconds, or `null` before the first reading. */\n rttMs: number | null;\n /**\n * Uplink the transport estimates for this path, in kbps, or `null`.\n *\n * `null` and not `0`, because the two mean opposite things: no estimate yet\n * is the normal state for the first seconds of every call and the permanent\n * state on an engine that publishes none, while `0` is indistinguishable\n * from a path that died. A consumer that reads absence as zero drops the\n * quality at the start of every call.\n *\n * This is the field that separates \"healthy at 2.5 Mbps\" from \"capped at\n * 2.5 Mbps and drowning\" — `kbps` reports the cap being honoured either\n * way, while the queue behind it grows.\n */\n availableKbps: number | null;\n /**\n * What the encoder says is holding the picture back, or `null` for nothing.\n *\n * `\"bandwidth\"` wins over the other values when senders disagree, because\n * it is the only one a lower cap answers. Reacting to bandwidth on a\n * machine that is actually CPU-bound buys a worse picture and no relief.\n *\n * The spec's `\"none\"` is reported as `null`: a consumer should not have to\n * know that one of the truthy strings means \"nothing\".\n */\n limitedBy: RTCQualityLimitationReason | null;\n /**\n * Whether the link is travelling through a TURN relay.\n *\n * On a self-hosted mesh this is the hosting bill: a relayed stream goes up\n * and down through the machine somebody is paying for, and the person who\n * picked 4K is not that somebody.\n *\n * Resolved only from the pair the transport **names**, never from a merely\n * `succeeded` one — guessing the route from a pair that carries nothing\n * would report a cost nobody is paying.\n */\n relayed: boolean;\n}\n\n/** Which media a sampler counts. */\nexport type LinkStatsKind = \"video\" | \"audio\" | \"all\";\n\n/** Options for {@link createLinkStatsSampler}. */\nexport interface LinkStatsSamplerOptions {\n /**\n * Which media the throughput counts. Default `\"video\"`.\n *\n * Video is the default because it is what saturates an uplink — audio is an\n * order of magnitude cheaper, and mixing it in moves the number by less than\n * the noise between two samples. Use `\"all\"` when the figure is meant to be\n * the connection's real cost rather than the picture's.\n */\n kind?: LinkStatsKind;\n}\n\n/**\n * A sampler bound to one connection.\n *\n * Holds the previous byte counter and timestamp, which is the whole reason this\n * is an object rather than a function: the rate is a delta, so somebody has to\n * remember the last reading. One sampler per link — sharing one across peers\n * subtracts one connection's counter from another's and reports nonsense.\n */\nexport interface LinkStatsSampler {\n /**\n * Reduce a report you already have.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The stats for this sample.\n */\n read: (report: RTCStatsReport) => LinkStats;\n /**\n * Fetch a report and reduce it.\n *\n * @param connection - The connection to sample.\n * @returns The stats for this sample.\n */\n sample: (connection: RTCPeerConnection) => Promise<LinkStats>;\n /**\n * Drop the baseline the rate is derived from.\n *\n * Call it after an ICE restart, a reconnect, or a pause — otherwise the next\n * sample divides the bytes of the whole gap by the whole gap and reports the\n * average of a period nobody is asking about. The next reading comes back at\n * `0` kbps and starts a fresh baseline; the resolution and round trip already\n * on screen are kept, so the badge does not blank out.\n */\n reset: () => void;\n}\n\n/**\n * The three field readers below take an entry the collector has already\n * established is an object.\n *\n * A report is a `Map` whose values the browser fills, and nothing says they\n * have to be objects — a polyfill or a mock can put anything in there. That\n * check belongs at the door of the one loop that walks the report, not repeated\n * in each reader: three copies of the same guard means three branches no test\n * can reach past the first, and a reader that silently returns `null` for a\n * primitive hides the case instead of skipping it.\n */\ntype StatsEntry = Record<string, unknown>;\n\nfunction numberField(entry: StatsEntry, key: string): number | null {\n const value: unknown = entry[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction stringField(entry: StatsEntry, key: string): string | null {\n const value: unknown = entry[key];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction booleanField(entry: StatsEntry, key: string): boolean | null {\n const value: unknown = entry[key];\n return typeof value === \"boolean\" ? value : null;\n}\n\n/**\n * Whether a string is one of the reasons the spec defines.\n *\n * `\"none\"` is deliberately not one of them here: the field it feeds reports\n * \"nothing is limiting\" as `null`, so a consumer never has to know that one of\n * the truthy strings means no.\n */\nfunction isLimitationReason(value: string | null): value is RTCQualityLimitationReason {\n return value === \"bandwidth\" || value === \"cpu\" || value === \"other\";\n}\n\n/**\n * Resolve the media an RTP entry carries.\n *\n * `kind` is the standard field and `mediaType` is what older Chrome reported;\n * both are still in the wild, and a sampler that reads only one of them\n * silently counts nothing on the browser that uses the other.\n */\nfunction entryKind(entry: StatsEntry): string | null {\n return stringField(entry, \"kind\") ?? stringField(entry, \"mediaType\");\n}\n\n/** What one candidate pair says about the path it describes. */\ninterface PairFacts {\n id: string;\n /** `true` when the browser marks this pair as the chosen one non-standardly. */\n selected: boolean;\n state: string | null;\n rttMs: number | null;\n availableKbps: number | null;\n localCandidateId: string | null;\n}\n\n/** What one sender says about what it is sending. */\ninterface SenderFacts {\n kind: string | null;\n bytes: number;\n width: number;\n height: number;\n fps: number;\n limitedBy: RTCQualityLimitationReason | null;\n}\n\n/** Everything a single walk over a report yields. */\ninterface CollectedReport {\n namedPairId: string | null;\n pairs: PairFacts[];\n relayCandidateIds: Set<string>;\n senders: SenderFacts[];\n}\n\n/**\n * Reduce a report in **one** pass.\n *\n * One pass is the point rather than tidiness. Every field below lives in the\n * same report, and the pair the transport selected has to be resolved before\n * any of the path fields can be read — so a consumer that asks for round trip,\n * then throughput headroom, then whether the route is relayed, walks the same\n * report three times and resolves the same pair three times, per link, on every\n * tick. On a mesh of eight at one sample every two seconds that is the most\n * expensive recurring work in the call, on the device least able to pay it.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The entries that matter, grouped.\n */\nfunction collect(report: RTCStatsReport): CollectedReport {\n let namedPairId: string | null = null;\n const pairs: PairFacts[] = [];\n const relayCandidateIds = new Set<string>();\n const senders: SenderFacts[] = [];\n\n report.forEach((raw: unknown) => {\n if (typeof raw !== \"object\" || raw === null) return;\n const entry = raw as StatsEntry;\n const type = stringField(entry, \"type\");\n if (type === \"transport\") {\n namedPairId = stringField(entry, \"selectedCandidatePairId\") ?? namedPairId;\n return;\n }\n if (type === \"candidate-pair\") {\n const seconds = numberField(entry, \"currentRoundTripTime\");\n const bps = numberField(entry, \"availableOutgoingBitrate\");\n pairs.push({\n id: stringField(entry, \"id\") ?? \"\",\n selected: booleanField(entry, \"selected\") === true,\n state: stringField(entry, \"state\"),\n rttMs: seconds === null ? null : seconds * 1000,\n availableKbps: bps === null ? null : Math.round(bps / 1000),\n localCandidateId: stringField(entry, \"localCandidateId\"),\n });\n return;\n }\n if (type === \"local-candidate\") {\n const id = stringField(entry, \"id\");\n if (id !== null && stringField(entry, \"candidateType\") === \"relay\") {\n relayCandidateIds.add(id);\n }\n return;\n }\n if (type !== \"outbound-rtp\") return;\n const reason = stringField(entry, \"qualityLimitationReason\");\n senders.push({\n kind: entryKind(entry),\n bytes: numberField(entry, \"bytesSent\") ?? 0,\n width: numberField(entry, \"frameWidth\") ?? 0,\n height: numberField(entry, \"frameHeight\") ?? 0,\n fps: Math.round(numberField(entry, \"framesPerSecond\") ?? 0),\n limitedBy: isLimitationReason(reason) ? reason : null,\n });\n });\n\n return { namedPairId, pairs, relayCandidateIds, senders };\n}\n\n/**\n * The candidate pair carrying the link, and how sure we are that it is.\n *\n * A connection routinely keeps several viable pairs alive at once — host,\n * server-reflexive, relayed — and only one of them carries traffic. Reading the\n * first `succeeded` pair makes a reading jump between paths that are not being\n * travelled: 8 ms on an idle host pair alternating with 180 ms on the TURN pair\n * doing the work.\n *\n * The chain is `transport.selectedCandidatePairId` → a pair flagged\n * `selected: true` → the first `succeeded` one. The middle step is not in the\n * spec and is there because an engine that fills neither the transport field\n * nor it does not appear to exist, while one that fills only the flag does: a\n * reader that skips straight to `succeeded` silently answers about the wrong\n * path there. The last step is a guess, and `named` says so — the fields where\n * guessing would report something false refuse it.\n *\n * @param collected - A collected report.\n * @returns The pair and whether the browser actually named it.\n */\nfunction carryingPair(collected: CollectedReport): { pair: PairFacts | null; named: boolean } {\n const byId =\n collected.namedPairId === null\n ? undefined\n : collected.pairs.find((pair) => pair.id === collected.namedPairId);\n if (byId !== undefined) return { pair: byId, named: true };\n\n const flagged = collected.pairs.find((pair) => pair.selected);\n if (flagged !== undefined) return { pair: flagged, named: true };\n\n const succeeded = collected.pairs.find((pair) => pair.state === \"succeeded\");\n return { pair: succeeded ?? null, named: false };\n}\n\n/**\n * Read the round trip of the candidate pair actually carrying the link.\n *\n * A `succeeded` pair is kept as a last resort because not every browser names\n * the selected one — losing the reading entirely is worse than an occasionally\n * optimistic one. See {@link carryingPair} for the chain and why the middle\n * step exists.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns Round trip in milliseconds, rounded, or `null` when nothing reported\n * one — which is the normal state before the connection settles.\n *\n * @example\n * const rttMs = readRoundTripMs(await pc.getStats());\n */\nexport function readRoundTripMs(report: RTCStatsReport): number | null {\n return roundTripOf(collect(report));\n}\n\n/**\n * Read the uplink the transport estimates for this path, in kbps.\n *\n * This is the field that tells a cap being honoured apart from a cap that is\n * drowning: `bytesSent` reports the same 2500 kbps whether the path has room\n * for it or the queue behind it is growing. No fixed budget can stand in for it\n * — a domestic uplink of 1 Mbps and a fibre link differ by an order of\n * magnitude, and in Brazil the upload routinely is a tenth of the download\n * beside it.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The estimate in kbps, or `null` while there is none. Every reader\n * needs a fallback for that, not a default of zero.\n *\n * @example\n * const headroom = readAvailableOutgoingKbps(await pc.getStats());\n * if (headroom !== null && headroom < asked) lowerTheCap(headroom);\n */\nexport function readAvailableOutgoingKbps(report: RTCStatsReport): number | null {\n return availableOf(collect(report));\n}\n\n/**\n * Read what the encoder says is holding the picture back.\n *\n * `\"bandwidth\"` wins when senders disagree, because it is the only reason a\n * lower cap answers. The spec's `\"none\"` comes back as `null`.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The reason, or `null` when nothing is limiting the picture.\n *\n * @example\n * if (readQualityLimitation(await pc.getStats()) === \"cpu\") stopBlurringTheBackground();\n */\nexport function readQualityLimitation(report: RTCStatsReport): RTCQualityLimitationReason | null {\n return limitationOf(collect(report));\n}\n\n/**\n * Read whether the link is travelling through a TURN relay.\n *\n * Resolved only from the pair the browser names, never from a merely\n * `succeeded` one: a relayed route is somebody's hosting bill, and reporting\n * one from a pair that carries nothing bills a cost nobody is paying.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns `true` when the carrying pair's local candidate is a relay.\n *\n * @example\n * if (readRelayed(await pc.getStats())) capTheStreamThatCostsMoney();\n */\nexport function readRelayed(report: RTCStatsReport): boolean {\n return relayedOf(collect(report));\n}\n\n/** Round trip of the carrying pair, in whole milliseconds. */\nfunction roundTripOf(collected: CollectedReport): number | null {\n const withTiming: CollectedReport = {\n ...collected,\n pairs: collected.pairs.filter((pair) => pair.rttMs !== null),\n };\n const { pair } = carryingPair(withTiming);\n return pair?.rttMs === undefined || pair.rttMs === null ? null : Math.round(pair.rttMs);\n}\n\n/** Estimated uplink of the carrying pair, in kbps. */\nfunction availableOf(collected: CollectedReport): number | null {\n const { pair } = carryingPair(collected);\n return pair?.availableKbps ?? null;\n}\n\n/** Whether the pair the browser named travels through a relay. */\nfunction relayedOf(collected: CollectedReport): boolean {\n const { pair, named } = carryingPair(collected);\n if (!named || pair === null || pair.localCandidateId === null) return false;\n return collected.relayCandidateIds.has(pair.localCandidateId);\n}\n\n/** The strongest limitation any sender reports, with bandwidth winning. */\nfunction limitationOf(collected: CollectedReport): RTCQualityLimitationReason | null {\n let found: RTCQualityLimitationReason | null = null;\n for (const sender of collected.senders) {\n if (sender.limitedBy === null) continue;\n if (sender.limitedBy === \"bandwidth\") return \"bandwidth\";\n found = found ?? sender.limitedBy;\n }\n return found;\n}\n\n/**\n * Track one link's throughput, resolution and round trip across samples.\n *\n * Every rate here is a **delta**. `bytesSent` is cumulative since the connection\n * opened, so dividing it by the session length gives the historical average —\n * a number that only ever falls and never shows what is happening now. The\n * previous reading is kept on the sampler and subtracted, which is the part\n * every hand-rolled copy of this ends up rewriting.\n *\n * Bytes are summed across every matching sender, because a peer publishing a\n * camera and a screen at once occupies one uplink with both — and the uplink is\n * what runs out. Resolution and frame rate come from the **largest** stream by\n * area, which is the one that dominates that bandwidth and the one somebody\n * watching the call is looking at.\n *\n * @param options - See {@link LinkStatsSamplerOptions}.\n * @returns A sampler. Use one per `RTCPeerConnection`.\n *\n * @example\n * const sampler = createLinkStatsSampler();\n *\n * setInterval(async () => {\n * const stats = await sampler.sample(pc);\n * badge.textContent = `${stats.kbps} kbps · ${stats.rttMs ?? \"—\"} ms`;\n * }, 2000);\n */\nexport function createLinkStatsSampler(options: LinkStatsSamplerOptions = {}): LinkStatsSampler {\n const kind: LinkStatsKind = options.kind ?? \"video\";\n let lastBytes: number | null = null;\n let lastSampleAt = 0;\n let last: LinkStats = {\n kbps: 0,\n width: 0,\n height: 0,\n fps: 0,\n rttMs: null,\n availableKbps: null,\n limitedBy: null,\n relayed: false,\n };\n\n function read(report: RTCStatsReport): LinkStats {\n const now = performance.now();\n const collected = collect(report);\n const path = {\n rttMs: roundTripOf(collected),\n availableKbps: availableOf(collected),\n limitedBy: limitationOf(collected),\n relayed: relayedOf(collected),\n };\n\n let bytes = 0;\n let sawSender = false;\n let bestArea = 0;\n let width = 0;\n let height = 0;\n let fps = 0;\n\n for (const sender of collected.senders) {\n if (kind !== \"all\" && sender.kind !== kind) continue;\n sawSender = true;\n bytes += sender.bytes;\n\n const area = sender.width * sender.height;\n if (area < bestArea) continue;\n bestArea = area;\n width = sender.width;\n height = sender.height;\n fps = sender.fps;\n }\n\n if (!sawSender) {\n last = { ...last, ...path };\n return last;\n }\n\n const elapsed = lastBytes === null ? 0 : (now - lastSampleAt) / 1000;\n const delta = lastBytes === null ? 0 : bytes - lastBytes;\n const kbps = elapsed > 0 && delta > 0 ? Math.round((delta * 8) / 1000 / elapsed) : 0;\n\n lastBytes = bytes;\n lastSampleAt = now;\n last = {\n kbps,\n width: width > 0 ? width : last.width,\n height: height > 0 ? height : last.height,\n fps: fps > 0 ? fps : last.fps,\n ...path,\n };\n return last;\n }\n\n return {\n read,\n sample: async (connection: RTCPeerConnection): Promise<LinkStats> =>\n read(await connection.getStats()),\n reset: (): void => {\n lastBytes = null;\n lastSampleAt = 0;\n last = { ...last, kbps: 0 };\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"link-stats.js","names":["numberField","value","entry","key","stringField","booleanField","isLimitationReason","entryKind","collect","namedPairId","pairs","relayCandidateIds","senders","report","raw","type","seconds","bps","id","reason","carryingPair","byId","collected","pair","flagged","readRoundTripMs","roundTripOf","readAvailableOutgoingKbps","availableOf","readQualityLimitation","limitationOf","readRelayed","relayedOf","named","found","sender","createLinkStatsSampler","kind","options","lastBytes","lastSampleAt","last","read","now","path","bytes","sawSender","bestArea","width","height","fps","area","elapsed","delta","kbps","connection"],"sources":["../../src/webrtc/link-stats.ts"],"sourcesContent":["/**\n * One link's outbound picture, in the shape a call UI actually renders.\n *\n * Everything here is derived, not reported: WebRTC hands out cumulative\n * counters and a graph of candidate pairs, and turning that into\n * `\"1,2 Mbps · 42 ms · 1080p60\"` is the work {@link createLinkStatsSampler}\n * does.\n */\nexport interface LinkStats {\n /** Throughput since the previous sample. `0` on the first one — there is no delta yet. */\n kbps: number;\n /** Width of the largest stream being sent, or `0` before one is reported. */\n width: number;\n /** Height of the largest stream being sent, or `0` before one is reported. */\n height: number;\n /** Frame rate of the largest stream being sent, or `0` when the browser omits it. */\n fps: number;\n /** Round trip to the peer in milliseconds, or `null` before the first reading. */\n rttMs: number | null;\n /**\n * Uplink the transport estimates for this path, in kbps, or `null`.\n *\n * `null` and not `0`, because the two mean opposite things: no estimate yet\n * is the normal state for the first seconds of every call and the permanent\n * state on an engine that publishes none, while `0` is indistinguishable\n * from a path that died. A consumer that reads absence as zero drops the\n * quality at the start of every call.\n *\n * This is the field that separates \"healthy at 2.5 Mbps\" from \"capped at\n * 2.5 Mbps and drowning\" — `kbps` reports the cap being honoured either\n * way, while the queue behind it grows.\n */\n availableKbps: number | null;\n /**\n * What the encoder says is holding the picture back, or `null` for nothing.\n *\n * `\"bandwidth\"` wins over the other values when senders disagree, because\n * it is the only one a lower cap answers. Reacting to bandwidth on a\n * machine that is actually CPU-bound buys a worse picture and no relief.\n *\n * The spec's `\"none\"` is reported as `null`: a consumer should not have to\n * know that one of the truthy strings means \"nothing\".\n */\n limitedBy: RTCQualityLimitationReason | null;\n /**\n * Whether the link is travelling through a TURN relay.\n *\n * On a self-hosted mesh this is the hosting bill: a relayed stream goes up\n * and down through the machine somebody is paying for, and the person who\n * picked 4K is not that somebody.\n *\n * Resolved only from the pair the transport **names**, never from a merely\n * `succeeded` one — guessing the route from a pair that carries nothing\n * would report a cost nobody is paying.\n */\n relayed: boolean;\n}\n\n/** Which media a sampler counts. */\nexport type LinkStatsKind = \"video\" | \"audio\" | \"all\";\n\n/** Options for {@link createLinkStatsSampler}. */\nexport interface LinkStatsSamplerOptions {\n /**\n * Which media the throughput counts. Default `\"video\"`.\n *\n * Video is the default because it is what saturates an uplink — audio is an\n * order of magnitude cheaper, and mixing it in moves the number by less than\n * the noise between two samples. Use `\"all\"` when the figure is meant to be\n * the connection's real cost rather than the picture's.\n */\n kind?: LinkStatsKind;\n}\n\n/**\n * A sampler bound to one connection.\n *\n * Holds the previous byte counter and timestamp, which is the whole reason this\n * is an object rather than a function: the rate is a delta, so somebody has to\n * remember the last reading. One sampler per link — sharing one across peers\n * subtracts one connection's counter from another's and reports nonsense.\n */\nexport interface LinkStatsSampler {\n /**\n * Reduce a report you already have.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The stats for this sample.\n */\n read: (report: RTCStatsReport) => LinkStats;\n /**\n * Fetch a report and reduce it.\n *\n * @param connection - The connection to sample.\n * @returns The stats for this sample.\n */\n sample: (connection: RTCPeerConnection) => Promise<LinkStats>;\n /**\n * Drop the baseline the rate is derived from.\n *\n * Call it after an ICE restart, a reconnect, or a pause — otherwise the next\n * sample divides the bytes of the whole gap by the whole gap and reports the\n * average of a period nobody is asking about. The next reading comes back at\n * `0` kbps and starts a fresh baseline; the resolution and round trip already\n * on screen are kept, so the badge does not blank out.\n */\n reset: () => void;\n}\n\n/**\n * The three field readers below take an entry the collector has already\n * established is an object.\n *\n * A report is a `Map` whose values the browser fills, and nothing says they\n * have to be objects — a polyfill or a mock can put anything in there. That\n * check belongs at the door of the one loop that walks the report, not repeated\n * in each reader: three copies of the same guard means three branches no test\n * can reach past the first, and a reader that silently returns `null` for a\n * primitive hides the case instead of skipping it.\n */\ntype StatsEntry = Record<string, unknown>;\n\nfunction numberField(entry: StatsEntry, key: string): number | null {\n const value: unknown = entry[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction stringField(entry: StatsEntry, key: string): string | null {\n const value: unknown = entry[key];\n return typeof value === \"string\" ? value : null;\n}\n\nfunction booleanField(entry: StatsEntry, key: string): boolean | null {\n const value: unknown = entry[key];\n return typeof value === \"boolean\" ? value : null;\n}\n\n/**\n * Whether a string is one of the reasons the spec defines.\n *\n * `\"none\"` is deliberately not one of them here: the field it feeds reports\n * \"nothing is limiting\" as `null`, so a consumer never has to know that one of\n * the truthy strings means no.\n */\nfunction isLimitationReason(value: string | null): value is RTCQualityLimitationReason {\n return value === \"bandwidth\" || value === \"cpu\" || value === \"other\";\n}\n\n/**\n * Resolve the media an RTP entry carries.\n *\n * `kind` is the standard field and `mediaType` is what older Chrome reported;\n * both are still in the wild, and a sampler that reads only one of them\n * silently counts nothing on the browser that uses the other.\n */\nfunction entryKind(entry: StatsEntry): string | null {\n return stringField(entry, \"kind\") ?? stringField(entry, \"mediaType\");\n}\n\n/** What one candidate pair says about the path it describes. */\ninterface PairFacts {\n id: string;\n /** `true` when the browser marks this pair as the chosen one non-standardly. */\n selected: boolean;\n state: string | null;\n rttMs: number | null;\n availableKbps: number | null;\n localCandidateId: string | null;\n}\n\n/** What one sender says about what it is sending. */\ninterface SenderFacts {\n kind: string | null;\n bytes: number;\n width: number;\n height: number;\n fps: number;\n limitedBy: RTCQualityLimitationReason | null;\n}\n\n/** Everything a single walk over a report yields. */\ninterface CollectedReport {\n namedPairId: string | null;\n pairs: PairFacts[];\n relayCandidateIds: Set<string>;\n senders: SenderFacts[];\n}\n\n/**\n * Reduce a report in **one** pass.\n *\n * One pass is the point rather than tidiness. Every field below lives in the\n * same report, and the pair the transport selected has to be resolved before\n * any of the path fields can be read — so a consumer that asks for round trip,\n * then throughput headroom, then whether the route is relayed, walks the same\n * report three times and resolves the same pair three times, per link, on every\n * tick. On a mesh of eight at one sample every two seconds that is the most\n * expensive recurring work in the call, on the device least able to pay it.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The entries that matter, grouped.\n */\nfunction collect(report: RTCStatsReport): CollectedReport {\n let namedPairId: string | null = null;\n const pairs: PairFacts[] = [];\n const relayCandidateIds = new Set<string>();\n const senders: SenderFacts[] = [];\n\n report.forEach((raw: unknown) => {\n if (typeof raw !== \"object\" || raw === null) return;\n const entry = raw as StatsEntry;\n const type = stringField(entry, \"type\");\n if (type === \"transport\") {\n namedPairId = stringField(entry, \"selectedCandidatePairId\") ?? namedPairId;\n return;\n }\n if (type === \"candidate-pair\") {\n const seconds = numberField(entry, \"currentRoundTripTime\");\n const bps = numberField(entry, \"availableOutgoingBitrate\");\n pairs.push({\n id: stringField(entry, \"id\") ?? \"\",\n selected: booleanField(entry, \"selected\") === true,\n state: stringField(entry, \"state\"),\n rttMs: seconds === null ? null : seconds * 1000,\n availableKbps: bps === null ? null : Math.round(bps / 1000),\n localCandidateId: stringField(entry, \"localCandidateId\"),\n });\n return;\n }\n if (type === \"local-candidate\") {\n const id = stringField(entry, \"id\");\n if (id !== null && stringField(entry, \"candidateType\") === \"relay\") {\n relayCandidateIds.add(id);\n }\n return;\n }\n if (type !== \"outbound-rtp\") return;\n const reason = stringField(entry, \"qualityLimitationReason\");\n senders.push({\n kind: entryKind(entry),\n bytes: numberField(entry, \"bytesSent\") ?? 0,\n width: numberField(entry, \"frameWidth\") ?? 0,\n height: numberField(entry, \"frameHeight\") ?? 0,\n fps: Math.round(numberField(entry, \"framesPerSecond\") ?? 0),\n limitedBy: isLimitationReason(reason) ? reason : null,\n });\n });\n\n return { namedPairId, pairs, relayCandidateIds, senders };\n}\n\n/**\n * The candidate pair carrying the link, and how sure we are that it is.\n *\n * A connection routinely keeps several viable pairs alive at once — host,\n * server-reflexive, relayed — and only one of them carries traffic. Reading the\n * first `succeeded` pair makes a reading jump between paths that are not being\n * travelled: 8 ms on an idle host pair alternating with 180 ms on the TURN pair\n * doing the work.\n *\n * The chain is `transport.selectedCandidatePairId` → a pair flagged\n * `selected: true` → the first `succeeded` one. The middle step is not in the\n * spec and is there because an engine that fills neither the transport field\n * nor it does not appear to exist, while one that fills only the flag does: a\n * reader that skips straight to `succeeded` silently answers about the wrong\n * path there. The last step is a guess, and `named` says so — the fields where\n * guessing would report something false refuse it.\n *\n * @param collected - A collected report.\n * @returns The pair and whether the browser actually named it.\n */\nfunction carryingPair(collected: CollectedReport): { pair: PairFacts | null; named: boolean } {\n const byId =\n collected.namedPairId === null\n ? undefined\n : collected.pairs.find((pair) => pair.id === collected.namedPairId);\n if (byId !== undefined) return { pair: byId, named: true };\n\n const flagged = collected.pairs.find((pair) => pair.selected);\n if (flagged !== undefined) return { pair: flagged, named: true };\n\n const succeeded = collected.pairs.find((pair) => pair.state === \"succeeded\");\n return { pair: succeeded ?? null, named: false };\n}\n\n/**\n * Read the round trip of the candidate pair actually carrying the link.\n *\n * A `succeeded` pair is kept as a last resort because not every browser names\n * the selected one — losing the reading entirely is worse than an occasionally\n * optimistic one. See {@link carryingPair} for the chain and why the middle\n * step exists.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns Round trip in milliseconds, rounded, or `null` when nothing reported\n * one — which is the normal state before the connection settles.\n *\n * @example\n * const rttMs = readRoundTripMs(await pc.getStats());\n */\nexport function readRoundTripMs(report: RTCStatsReport): number | null {\n return roundTripOf(collect(report));\n}\n\n/**\n * Read the uplink the transport estimates for this path, in kbps.\n *\n * This is the field that tells a cap being honoured apart from a cap that is\n * drowning: `bytesSent` reports the same 2500 kbps whether the path has room\n * for it or the queue behind it is growing. No fixed budget can stand in for it\n * — a domestic uplink of 1 Mbps and a fibre link differ by an order of\n * magnitude, and in Brazil the upload routinely is a tenth of the download\n * beside it.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The estimate in kbps, or `null` while there is none. Every reader\n * needs a fallback for that, not a default of zero.\n *\n * @example\n * const headroom = readAvailableOutgoingKbps(await pc.getStats());\n * if (headroom !== null && headroom < asked) lowerTheCap(headroom);\n */\nexport function readAvailableOutgoingKbps(report: RTCStatsReport): number | null {\n return availableOf(collect(report));\n}\n\n/**\n * Read what the encoder says is holding the picture back.\n *\n * `\"bandwidth\"` wins when senders disagree, because it is the only reason a\n * lower cap answers. The spec's `\"none\"` comes back as `null`.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns The reason, or `null` when nothing is limiting the picture.\n *\n * @example\n * if (readQualityLimitation(await pc.getStats()) === \"cpu\") stopBlurringTheBackground();\n */\nexport function readQualityLimitation(report: RTCStatsReport): RTCQualityLimitationReason | null {\n return limitationOf(collect(report));\n}\n\n/**\n * Read whether the link is travelling through a TURN relay.\n *\n * Resolved only from the pair the browser names, never from a merely\n * `succeeded` one: a relayed route is somebody's hosting bill, and reporting\n * one from a pair that carries nothing bills a cost nobody is paying.\n *\n * @param report - A report from `RTCPeerConnection.getStats()`.\n * @returns `true` when the carrying pair's local candidate is a relay.\n *\n * @example\n * if (readRelayed(await pc.getStats())) capTheStreamThatCostsMoney();\n */\nexport function readRelayed(report: RTCStatsReport): boolean {\n return relayedOf(collect(report));\n}\n\n/** Round trip of the carrying pair, in whole milliseconds. */\nfunction roundTripOf(collected: CollectedReport): number | null {\n const withTiming: CollectedReport = {\n ...collected,\n pairs: collected.pairs.filter((pair) => pair.rttMs !== null),\n };\n const { pair } = carryingPair(withTiming);\n return pair?.rttMs === undefined || pair.rttMs === null ? null : Math.round(pair.rttMs);\n}\n\n/** Estimated uplink of the carrying pair, in kbps. */\nfunction availableOf(collected: CollectedReport): number | null {\n const { pair } = carryingPair(collected);\n return pair?.availableKbps ?? null;\n}\n\n/** Whether the pair the browser named travels through a relay. */\nfunction relayedOf(collected: CollectedReport): boolean {\n const { pair, named } = carryingPair(collected);\n if (!named || pair === null || pair.localCandidateId === null) return false;\n return collected.relayCandidateIds.has(pair.localCandidateId);\n}\n\n/** The strongest limitation any sender reports, with bandwidth winning. */\nfunction limitationOf(collected: CollectedReport): RTCQualityLimitationReason | null {\n let found: RTCQualityLimitationReason | null = null;\n for (const sender of collected.senders) {\n if (sender.limitedBy === null) continue;\n if (sender.limitedBy === \"bandwidth\") return \"bandwidth\";\n found = found ?? sender.limitedBy;\n }\n return found;\n}\n\n/**\n * Track one link's throughput, resolution and round trip across samples.\n *\n * Every rate here is a **delta**. `bytesSent` is cumulative since the connection\n * opened, so dividing it by the session length gives the historical average —\n * a number that only ever falls and never shows what is happening now. The\n * previous reading is kept on the sampler and subtracted, which is the part\n * every hand-rolled copy of this ends up rewriting.\n *\n * Bytes are summed across every matching sender, because a peer publishing a\n * camera and a screen at once occupies one uplink with both — and the uplink is\n * what runs out. Resolution and frame rate come from the **largest** stream by\n * area, which is the one that dominates that bandwidth and the one somebody\n * watching the call is looking at.\n *\n * @param options - See {@link LinkStatsSamplerOptions}.\n * @returns A sampler. Use one per `RTCPeerConnection`.\n *\n * @example\n * const sampler = createLinkStatsSampler();\n *\n * setInterval(async () => {\n * const stats = await sampler.sample(pc);\n * badge.textContent = `${stats.kbps} kbps · ${stats.rttMs ?? \"—\"} ms`;\n * }, 2000);\n */\nexport function createLinkStatsSampler(options: LinkStatsSamplerOptions = {}): LinkStatsSampler {\n const kind: LinkStatsKind = options.kind ?? \"video\";\n let lastBytes: number | null = null;\n let lastSampleAt = 0;\n let last: LinkStats = {\n kbps: 0,\n width: 0,\n height: 0,\n fps: 0,\n rttMs: null,\n availableKbps: null,\n limitedBy: null,\n relayed: false,\n };\n\n function read(report: RTCStatsReport): LinkStats {\n const now = performance.now();\n const collected = collect(report);\n const path = {\n rttMs: roundTripOf(collected),\n availableKbps: availableOf(collected),\n limitedBy: limitationOf(collected),\n relayed: relayedOf(collected),\n };\n\n let bytes = 0;\n let sawSender = false;\n let bestArea = 0;\n let width = 0;\n let height = 0;\n let fps = 0;\n\n for (const sender of collected.senders) {\n if (kind !== \"all\" && sender.kind !== kind) continue;\n sawSender = true;\n bytes += sender.bytes;\n\n const area = sender.width * sender.height;\n if (area < bestArea) continue;\n bestArea = area;\n width = sender.width;\n height = sender.height;\n fps = sender.fps;\n }\n\n if (!sawSender) {\n last = { ...last, ...path };\n return last;\n }\n\n const elapsed = lastBytes === null ? 0 : (now - lastSampleAt) / 1000;\n const delta = lastBytes === null ? 0 : bytes - lastBytes;\n const kbps = elapsed > 0 && delta > 0 ? Math.round((delta * 8) / 1000 / elapsed) : 0;\n\n lastBytes = bytes;\n lastSampleAt = now;\n last = {\n kbps,\n width: width > 0 ? width : last.width,\n height: height > 0 ? height : last.height,\n fps: fps > 0 ? fps : last.fps,\n ...path,\n };\n return last;\n }\n\n return {\n read,\n sample: async (connection: RTCPeerConnection): Promise<LinkStats> =>\n read(await connection.getStats()),\n reset: (): void => {\n lastBytes = null;\n lastSampleAt = 0;\n last = { ...last, kbps: 0 };\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"mesh-quality.cjs","names":[],"sources":["../../src/webrtc/mesh-quality.ts"],"sourcesContent":["import { setSenderBitrate } from \"./sender-bitrate\";\nimport type { MeshQuality, MeshSlot } from \"./mesh-types\";\n\n/** Uplink assumed available when the caller names no budget, in kbps. */\nconst DEFAULT_UPLINK_BUDGET_KBPS = 6000;\n\n/** Floor a video slot keeps after the division, in kbps. */\nconst DEFAULT_MIN_VIDEO_KBPS = 300;\n\n/** Budget below which `maintain-framerate` stops being worth holding, in kbps. */\nconst DEFAULT_FLUID_FLOOR_KBPS = 900;\n\n/**\n * Divide the video caps by the size of the room.\n *\n * A mesh sends one copy of everything per participant, so the uplink is the\n * shared resource and the caps are what compete for it. Nothing is divided while\n * the caller is alone with one peer — that is the case the largest sizes exist\n * for, and dividing a budget that is not being shared would make them\n * unreachable in the only call where they fit.\n *\n * The floor is what keeps the division honest: without it a busy room allocates\n * tens of kbps per stream, and everybody loses the picture instead of the excess\n * giving way.\n *\n * @param quality - The caps as asked for.\n * @param peers - How many links are live.\n * @returns The caps to actually apply. The input is never mutated, so a room\n * that empties out climbs back to what was asked for.\n */\nexport function scaleForRoom(quality: MeshQuality, peers: number): MeshQuality {\n const video = quality.video ?? {};\n if (peers <= 1) return quality;\n\n const asked = Object.values(video).reduce<number>((sum, cap) => sum + (cap ?? 0), 0);\n if (asked === 0) return quality;\n\n const perPeer = (quality.uplinkBudgetKbps ?? DEFAULT_UPLINK_BUDGET_KBPS) / peers;\n if (asked <= perPeer) return quality;\n\n const floor = quality.minVideoKbps ?? DEFAULT_MIN_VIDEO_KBPS;\n const factor = perPeer / asked;\n const scaled: Record<string, number | null> = {};\n for (const [slot, cap] of Object.entries(video)) {\n scaled[slot] = cap === null ? null : Math.max(floor, Math.round(cap * factor));\n }\n return { ...quality, video: scaled };\n}\n\n/**\n * Decide what the encoder gives up, letting physics override the preference.\n *\n * `maintain-framerate` is honoured while there are bits enough for the frames to\n * be worth keeping. Once the room's division has taken the budget below the\n * fluid floor, holding the rate halves what each frame receives and the picture\n * is worse than the lower rate it replaced — so below that line the preference\n * is overridden rather than obeyed.\n *\n * Two things decide *which* budget answers that question:\n *\n * - **`null` is the most generous case, not the absent one.** A slot with no cap\n * is unbounded, which is exactly where fluidity should hold. Reading it as\n * missing — and then deciding from a modest camera beside it — is the answer\n * backwards.\n * - **{@link MeshQuality.degradationAnchor} names the slot the choice was\n * about.** Without it the largest cap across the video slots answers, which\n * is right when the slots are interchangeable and wrong when they are not.\n *\n * @param quality - What was asked for, including the anchor and the floor.\n * @param effective - What the room's share actually allows.\n * @returns The degradation preference to write onto the senders, or `undefined`\n * when the caller expressed no preference.\n */\nexport function resolveDegradation(\n quality: MeshQuality,\n effective: MeshQuality,\n): RTCDegradationPreference | undefined {\n const asked = quality.degradationPreference;\n if (asked !== \"maintain-framerate\") return asked;\n\n const video = effective.video ?? {};\n const floor = quality.fluidFloorKbps ?? DEFAULT_FLUID_FLOOR_KBPS;\n const anchor = quality.degradationAnchor;\n\n if (anchor !== undefined) {\n if (!(anchor in video)) return asked;\n const cap = video[anchor];\n if (cap === null) return asked;\n return cap >= floor ? \"maintain-framerate\" : \"maintain-resolution\";\n }\n\n const caps = Object.values(video);\n if (caps.length === 0) return asked;\n if (caps.some((cap) => cap === null)) return asked;\n\n const budget = Math.max(...caps.filter((cap): cap is number => cap !== null));\n return budget >= floor ? \"maintain-framerate\" : \"maintain-resolution\";\n}\n\n/**\n * Read a video sender's parameters back with the motion settings written on.\n *\n * Read fresh on every call because `setParameters` only accepts the object the\n * **same** sender's `getParameters` returned, so a rejected attempt cannot be\n * retried with the object that was rejected.\n *\n * @param sender - The video sender to read from.\n * @param degradation - What to give up first, or `undefined` to leave it alone.\n * @param fps - Frame ceiling, or `undefined` to leave it alone.\n * @returns Parameters ready to be written back to `sender`.\n */\nfunction videoParameters(\n sender: RTCRtpSender,\n degradation: RTCDegradationPreference | undefined,\n fps: number | undefined,\n): RTCRtpSendParameters {\n const params = sender.getParameters();\n if (degradation !== undefined) params.degradationPreference = degradation;\n if (fps !== undefined) {\n for (const encoding of params.encodings ?? []) encoding.maxFramerate = fps;\n }\n return params;\n}\n\n/**\n * Write the quality settings onto one link's senders.\n *\n * `degradationPreference` goes only on video: it describes trading resolution\n * against frame rate, which an audio sender has no analogue for, and some\n * browsers reject it outright there.\n *\n * The retry without `degradationPreference` is for Firefox, which rejects the\n * member entirely — sending both together would lose the frame-rate cap to an\n * objection about something else.\n *\n * @param transceivers - The link's transceivers, in slot order.\n * @param slots - The slot list those transceivers were allocated from.\n * @param effective - Caps after the room's division.\n * @param degradation - Already resolved against the fluid floor.\n */\nexport async function applyQualityToLink(\n transceivers: readonly RTCRtpTransceiver[],\n slots: readonly MeshSlot[],\n effective: MeshQuality,\n degradation: RTCDegradationPreference | undefined,\n): Promise<void> {\n for (const [index, slot] of slots.entries()) {\n const sender = transceivers[index]?.sender;\n if (!sender) continue;\n\n if (slot.kind === \"audio\") {\n const bps = effective.audio?.[slot.name];\n if (bps !== undefined) await setSenderBitrate(sender, bps);\n continue;\n }\n\n const kbps = effective.video?.[slot.name];\n if (kbps !== undefined) await setSenderBitrate(sender, kbps === null ? null : kbps * 1000);\n\n if (degradation === undefined && effective.maxFramerate === undefined) continue;\n try {\n await sender.setParameters(\n videoParameters(sender, degradation, effective.maxFramerate),\n );\n } catch {\n try {\n await sender.setParameters(\n videoParameters(sender, undefined, effective.maxFramerate),\n );\n } catch {\n /* the sender refused both; the bitrate cap above still applies */\n }\n }\n }\n}\n"],"mappings":"wCAIA,IAAM,EAA6B,IAG7B,EAAyB,IAGzB,EAA2B,IAoBjC,SAAgB,EAAa,EAAsB,EAA4B,CAC3E,IAAM,EAAQ,EAAQ,OAAS,CAAC,EAChC,GAAI,GAAS,EAAG,OAAO,EAEvB,IAAM,EAAQ,OAAO,OAAO,CAAK,CAAC,CAAC,QAAgB,EAAK,IAAQ,GAAO,GAAO,GAAI,CAAC,EACnF,GAAI,IAAU,EAAG,OAAO,EAExB,IAAM,GAAW,EAAQ,kBAAoB,GAA8B,EAC3E,GAAI,GAAS,EAAS,OAAO,EAE7B,IAAM,EAAQ,EAAQ,cAAgB,EAChC,EAAS,EAAU,EACnB,EAAwC,CAAC,EAC/C,IAAK,GAAM,CAAC,EAAM,KAAQ,OAAO,QAAQ,CAAK,EAC1C,EAAO,GAAQ,IAAQ,KAAO,KAAO,KAAK,IAAI,EAAO,KAAK,MAAM,EAAM,CAAM,CAAC,EAEjF,MAAO,CAAE,GAAG,EAAS,MAAO,CAAO,CACvC,CA0BA,SAAgB,EACZ,EACA,EACoC,CACpC,IAAM,EAAQ,EAAQ,sBACtB,GAAI,IAAU,qBAAsB,OAAO,EAE3C,IAAM,EAAQ,EAAU,OAAS,CAAC,EAC5B,EAAQ,EAAQ,gBAAkB,EAClC,EAAS,EAAQ,kBAEvB,GAAI,IAAW,IAAA,GAAW,CACtB,GAAI,EAAE,KAAU,GAAQ,OAAO,EAC/B,IAAM,EAAM,EAAM,GAElB,OADI,IAAQ,KAAa,EAClB,GAAO,EAAQ,qBAAuB,qBACjD,CAEA,IAAM,EAAO,OAAO,OAAO,CAAK,EAKhC,OAJI,EAAK,SAAW,GAChB,EAAK,KAAM,GAAQ,IAAQ,IAAI,EAAU,EAE9B,KAAK,IAAI,GAAG,EAAK,OAAQ,GAAuB,IAAQ,IAAI,CACpE,GAAU,EAAQ,qBAAuB,qBACpD,CAcA,SAAS,EACL,EACA,EACA,EACoB,CACpB,IAAM,EAAS,EAAO,cAAc,EAEpC,GADI,IAAgB,IAAA,KAAW,EAAO,sBAAwB,GAC1D,IAAQ,IAAA,GACR,IAAK,IAAM,KAAY,EAAO,WAAa,CAAC,EAAG,EAAS,aAAe,EAE3E,OAAO,CACX,CAkBA,eAAsB,EAClB,EACA,EACA,EACA,EACa,CACb,IAAK,GAAM,CAAC,EAAO,KAAS,EAAM,QAAQ,EAAG,CACzC,IAAM,EAAS,EAAa,EAAM,EAAE,OACpC,GAAI,CAAC,EAAQ,SAEb,GAAI,EAAK,OAAS,QAAS,CACvB,IAAM,EAAM,EAAU,QAAQ,EAAK,MAC/B,IAAQ,IAAA,IAAW,MAAM,EAAA,iBAAiB,EAAQ,CAAG,EACzD,QACJ,CAEA,IAAM,EAAO,EAAU,QAAQ,EAAK,MACpC,GAAI,IAAS,IAAA,IAAW,MAAM,EAAA,iBAAiB,EAAQ,IAAS,KAAO,KAAO,EAAO,GAAI,EAErF,IAAgB,IAAA,IAAa,EAAU,eAAiB,IAAA,GAC5D,GAAI,CACA,MAAM,EAAO,cACT,EAAgB,EAAQ,EAAa,EAAU,YAAY,CAC/D,CACJ,MAAQ,CACJ,GAAI,CACA,MAAM,EAAO,cACT,EAAgB,EAAQ,IAAA,GAAW,EAAU,YAAY,CAC7D,CACJ,MAAQ,CAER,CACJ,CACJ,CACJ"}
1
+ {"version":3,"file":"mesh-quality.cjs","names":["setSenderBitrate","require_sender_bitrate","DEFAULT_UPLINK_BUDGET_KBPS","DEFAULT_MIN_VIDEO_KBPS","DEFAULT_FLUID_FLOOR_KBPS","scaleForRoom","video","quality","peers","asked","sum","cap","perPeer","floor","factor","scaled","slot","resolveDegradation","effective","anchor","caps","videoParameters","params","sender","degradation","fps","encoding","applyQualityToLink","index","slots","transceivers","bps","kbps"],"sources":["../../src/webrtc/mesh-quality.ts"],"sourcesContent":["import { setSenderBitrate } from \"./sender-bitrate\";\nimport type { MeshQuality, MeshSlot } from \"./mesh-types\";\n\n/** Uplink assumed available when the caller names no budget, in kbps. */\nconst DEFAULT_UPLINK_BUDGET_KBPS = 6000;\n\n/** Floor a video slot keeps after the division, in kbps. */\nconst DEFAULT_MIN_VIDEO_KBPS = 300;\n\n/** Budget below which `maintain-framerate` stops being worth holding, in kbps. */\nconst DEFAULT_FLUID_FLOOR_KBPS = 900;\n\n/**\n * Divide the video caps by the size of the room.\n *\n * A mesh sends one copy of everything per participant, so the uplink is the\n * shared resource and the caps are what compete for it. Nothing is divided while\n * the caller is alone with one peer — that is the case the largest sizes exist\n * for, and dividing a budget that is not being shared would make them\n * unreachable in the only call where they fit.\n *\n * The floor is what keeps the division honest: without it a busy room allocates\n * tens of kbps per stream, and everybody loses the picture instead of the excess\n * giving way.\n *\n * @param quality - The caps as asked for.\n * @param peers - How many links are live.\n * @returns The caps to actually apply. The input is never mutated, so a room\n * that empties out climbs back to what was asked for.\n */\nexport function scaleForRoom(quality: MeshQuality, peers: number): MeshQuality {\n const video = quality.video ?? {};\n if (peers <= 1) return quality;\n\n const asked = Object.values(video).reduce<number>((sum, cap) => sum + (cap ?? 0), 0);\n if (asked === 0) return quality;\n\n const perPeer = (quality.uplinkBudgetKbps ?? DEFAULT_UPLINK_BUDGET_KBPS) / peers;\n if (asked <= perPeer) return quality;\n\n const floor = quality.minVideoKbps ?? DEFAULT_MIN_VIDEO_KBPS;\n const factor = perPeer / asked;\n const scaled: Record<string, number | null> = {};\n for (const [slot, cap] of Object.entries(video)) {\n scaled[slot] = cap === null ? null : Math.max(floor, Math.round(cap * factor));\n }\n return { ...quality, video: scaled };\n}\n\n/**\n * Decide what the encoder gives up, letting physics override the preference.\n *\n * `maintain-framerate` is honoured while there are bits enough for the frames to\n * be worth keeping. Once the room's division has taken the budget below the\n * fluid floor, holding the rate halves what each frame receives and the picture\n * is worse than the lower rate it replaced — so below that line the preference\n * is overridden rather than obeyed.\n *\n * Two things decide *which* budget answers that question:\n *\n * - **`null` is the most generous case, not the absent one.** A slot with no cap\n * is unbounded, which is exactly where fluidity should hold. Reading it as\n * missing — and then deciding from a modest camera beside it — is the answer\n * backwards.\n * - **{@link MeshQuality.degradationAnchor} names the slot the choice was\n * about.** Without it the largest cap across the video slots answers, which\n * is right when the slots are interchangeable and wrong when they are not.\n *\n * @param quality - What was asked for, including the anchor and the floor.\n * @param effective - What the room's share actually allows.\n * @returns The degradation preference to write onto the senders, or `undefined`\n * when the caller expressed no preference.\n */\nexport function resolveDegradation(\n quality: MeshQuality,\n effective: MeshQuality,\n): RTCDegradationPreference | undefined {\n const asked = quality.degradationPreference;\n if (asked !== \"maintain-framerate\") return asked;\n\n const video = effective.video ?? {};\n const floor = quality.fluidFloorKbps ?? DEFAULT_FLUID_FLOOR_KBPS;\n const anchor = quality.degradationAnchor;\n\n if (anchor !== undefined) {\n if (!(anchor in video)) return asked;\n const cap = video[anchor];\n if (cap === null) return asked;\n return cap >= floor ? \"maintain-framerate\" : \"maintain-resolution\";\n }\n\n const caps = Object.values(video);\n if (caps.length === 0) return asked;\n if (caps.some((cap) => cap === null)) return asked;\n\n const budget = Math.max(...caps.filter((cap): cap is number => cap !== null));\n return budget >= floor ? \"maintain-framerate\" : \"maintain-resolution\";\n}\n\n/**\n * Read a video sender's parameters back with the motion settings written on.\n *\n * Read fresh on every call because `setParameters` only accepts the object the\n * **same** sender's `getParameters` returned, so a rejected attempt cannot be\n * retried with the object that was rejected.\n *\n * @param sender - The video sender to read from.\n * @param degradation - What to give up first, or `undefined` to leave it alone.\n * @param fps - Frame ceiling, or `undefined` to leave it alone.\n * @returns Parameters ready to be written back to `sender`.\n */\nfunction videoParameters(\n sender: RTCRtpSender,\n degradation: RTCDegradationPreference | undefined,\n fps: number | undefined,\n): RTCRtpSendParameters {\n const params = sender.getParameters();\n if (degradation !== undefined) params.degradationPreference = degradation;\n if (fps !== undefined) {\n for (const encoding of params.encodings ?? []) encoding.maxFramerate = fps;\n }\n return params;\n}\n\n/**\n * Write the quality settings onto one link's senders.\n *\n * `degradationPreference` goes only on video: it describes trading resolution\n * against frame rate, which an audio sender has no analogue for, and some\n * browsers reject it outright there.\n *\n * The retry without `degradationPreference` is for Firefox, which rejects the\n * member entirely — sending both together would lose the frame-rate cap to an\n * objection about something else.\n *\n * @param transceivers - The link's transceivers, in slot order.\n * @param slots - The slot list those transceivers were allocated from.\n * @param effective - Caps after the room's division.\n * @param degradation - Already resolved against the fluid floor.\n */\nexport async function applyQualityToLink(\n transceivers: readonly RTCRtpTransceiver[],\n slots: readonly MeshSlot[],\n effective: MeshQuality,\n degradation: RTCDegradationPreference | undefined,\n): Promise<void> {\n for (const [index, slot] of slots.entries()) {\n const sender = transceivers[index]?.sender;\n if (!sender) continue;\n\n if (slot.kind === \"audio\") {\n const bps = effective.audio?.[slot.name];\n if (bps !== undefined) await setSenderBitrate(sender, bps);\n continue;\n }\n\n const kbps = effective.video?.[slot.name];\n if (kbps !== undefined) await setSenderBitrate(sender, kbps === null ? null : kbps * 1000);\n\n if (degradation === undefined && effective.maxFramerate === undefined) continue;\n try {\n await sender.setParameters(\n videoParameters(sender, degradation, effective.maxFramerate),\n );\n } catch {\n try {\n await sender.setParameters(\n videoParameters(sender, undefined, effective.maxFramerate),\n );\n } catch {\n /* the sender refused both; the bitrate cap above still applies */\n }\n }\n }\n}\n"],"mappings":"A,wCAIA,IAAME,EAA6B,IAG7BC,EAAyB,IAGzBC,EAA2B,IAoBjC,SAAgBC,EAAa,EAAsB,EAA4B,CAC3E,IAAMC,EAAQC,EAAQ,OAAS,CAAC,EAChC,GAAIC,GAAS,EAAG,OAAOD,EAEvB,IAAME,EAAQ,OAAO,OAAOH,CAAK,CAAC,CAAC,QAAgB,EAAK,IAAQI,GAAOC,GAAO,GAAI,CAAC,EACnF,GAAIF,IAAU,EAAG,OAAOF,EAExB,IAAMK,GAAWL,EAAQ,kBAAoBL,GAA8BM,EAC3E,GAAIC,GAASG,EAAS,OAAOL,EAE7B,IAAMM,EAAQN,EAAQ,cAAgBJ,EAChCW,EAASF,EAAUH,EACnBM,EAAwC,CAAC,EAC/C,IAAK,GAAM,CAACC,EAAML,KAAQ,OAAO,QAAQL,CAAK,EAC1C,EAAOU,GAAQL,IAAQ,KAAO,KAAO,KAAK,IAAIE,EAAO,KAAK,MAAMF,EAAMG,CAAM,CAAC,EAEjF,MAAO,CAAE,GAAGP,EAAS,MAAOQ,CAAO,CACvC,CA0BA,SAAgBE,EACZ,EACA,EACoC,CACpC,IAAMR,EAAQF,EAAQ,sBACtB,GAAIE,IAAU,qBAAsB,OAAOA,EAE3C,IAAMH,EAAQY,EAAU,OAAS,CAAC,EAC5BL,EAAQN,EAAQ,gBAAkBH,EAClCe,EAASZ,EAAQ,kBAEvB,GAAIY,IAAW,IAAA,GAAW,CACtB,GAAI,EAAEA,KAAUb,GAAQ,OAAOG,EAC/B,IAAME,EAAML,EAAMa,GAElB,OADIR,IAAQ,KAAaF,EAClBE,GAAOE,EAAQ,qBAAuB,qBACjD,CAEA,IAAMO,EAAO,OAAO,OAAOd,CAAK,EAKhC,OAJIc,EAAK,SAAW,GAChBA,EAAK,KAAM,GAAQT,IAAQ,IAAI,EAAUF,EAE9B,KAAK,IAAI,GAAGW,EAAK,OAAQ,GAAuBT,IAAQ,IAAI,CACpE,GAAUE,EAAQ,qBAAuB,qBACpD,CAcA,SAASQ,EACL,EACA,EACA,EACoB,CACpB,IAAMC,EAASC,EAAO,cAAc,EAEpC,GADIC,IAAgB,IAAA,KAAW,EAAO,sBAAwBA,GAC1DC,IAAQ,IAAA,GACR,IAAK,IAAMC,KAAYJ,EAAO,WAAa,CAAC,EAAG,EAAS,aAAeG,EAE3E,OAAOH,CACX,CAkBA,eAAsBK,EAClB,EACA,EACA,EACA,EACa,CACb,IAAK,GAAM,CAACC,EAAOZ,KAASa,EAAM,QAAQ,EAAG,CACzC,IAAMN,EAASO,EAAaF,EAAM,EAAE,OACpC,GAAI,CAACL,EAAQ,SAEb,GAAIP,EAAK,OAAS,QAAS,CACvB,IAAMe,EAAMb,EAAU,QAAQF,EAAK,MAC/Be,IAAQ,IAAA,IAAW,MAAM/B,EAAAA,iBAAiBuB,EAAQQ,CAAG,EACzD,QACJ,CAEA,IAAMC,EAAOd,EAAU,QAAQF,EAAK,MAGpC,GAFIgB,IAAS,IAAA,IAAW,MAAMhC,EAAAA,iBAAiBuB,EAAQS,IAAS,KAAO,KAAOA,EAAO,GAAI,EAErFR,IAAgB,IAAA,IAAaN,EAAU,eAAiB,IAAA,GAC5D,GAAI,CACA,MAAMK,EAAO,cACTF,EAAgBE,EAAQC,EAAaN,EAAU,YAAY,CAC/D,CACJ,MAAQ,CACJ,GAAI,CACA,MAAMK,EAAO,cACTF,EAAgBE,EAAQ,IAAA,GAAWL,EAAU,YAAY,CAC7D,CACJ,MAAQ,CAER,CACJ,CACJ,CACJ,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"mesh-quality.js","names":[],"sources":["../../src/webrtc/mesh-quality.ts"],"sourcesContent":["import { setSenderBitrate } from \"./sender-bitrate\";\nimport type { MeshQuality, MeshSlot } from \"./mesh-types\";\n\n/** Uplink assumed available when the caller names no budget, in kbps. */\nconst DEFAULT_UPLINK_BUDGET_KBPS = 6000;\n\n/** Floor a video slot keeps after the division, in kbps. */\nconst DEFAULT_MIN_VIDEO_KBPS = 300;\n\n/** Budget below which `maintain-framerate` stops being worth holding, in kbps. */\nconst DEFAULT_FLUID_FLOOR_KBPS = 900;\n\n/**\n * Divide the video caps by the size of the room.\n *\n * A mesh sends one copy of everything per participant, so the uplink is the\n * shared resource and the caps are what compete for it. Nothing is divided while\n * the caller is alone with one peer — that is the case the largest sizes exist\n * for, and dividing a budget that is not being shared would make them\n * unreachable in the only call where they fit.\n *\n * The floor is what keeps the division honest: without it a busy room allocates\n * tens of kbps per stream, and everybody loses the picture instead of the excess\n * giving way.\n *\n * @param quality - The caps as asked for.\n * @param peers - How many links are live.\n * @returns The caps to actually apply. The input is never mutated, so a room\n * that empties out climbs back to what was asked for.\n */\nexport function scaleForRoom(quality: MeshQuality, peers: number): MeshQuality {\n const video = quality.video ?? {};\n if (peers <= 1) return quality;\n\n const asked = Object.values(video).reduce<number>((sum, cap) => sum + (cap ?? 0), 0);\n if (asked === 0) return quality;\n\n const perPeer = (quality.uplinkBudgetKbps ?? DEFAULT_UPLINK_BUDGET_KBPS) / peers;\n if (asked <= perPeer) return quality;\n\n const floor = quality.minVideoKbps ?? DEFAULT_MIN_VIDEO_KBPS;\n const factor = perPeer / asked;\n const scaled: Record<string, number | null> = {};\n for (const [slot, cap] of Object.entries(video)) {\n scaled[slot] = cap === null ? null : Math.max(floor, Math.round(cap * factor));\n }\n return { ...quality, video: scaled };\n}\n\n/**\n * Decide what the encoder gives up, letting physics override the preference.\n *\n * `maintain-framerate` is honoured while there are bits enough for the frames to\n * be worth keeping. Once the room's division has taken the budget below the\n * fluid floor, holding the rate halves what each frame receives and the picture\n * is worse than the lower rate it replaced — so below that line the preference\n * is overridden rather than obeyed.\n *\n * Two things decide *which* budget answers that question:\n *\n * - **`null` is the most generous case, not the absent one.** A slot with no cap\n * is unbounded, which is exactly where fluidity should hold. Reading it as\n * missing — and then deciding from a modest camera beside it — is the answer\n * backwards.\n * - **{@link MeshQuality.degradationAnchor} names the slot the choice was\n * about.** Without it the largest cap across the video slots answers, which\n * is right when the slots are interchangeable and wrong when they are not.\n *\n * @param quality - What was asked for, including the anchor and the floor.\n * @param effective - What the room's share actually allows.\n * @returns The degradation preference to write onto the senders, or `undefined`\n * when the caller expressed no preference.\n */\nexport function resolveDegradation(\n quality: MeshQuality,\n effective: MeshQuality,\n): RTCDegradationPreference | undefined {\n const asked = quality.degradationPreference;\n if (asked !== \"maintain-framerate\") return asked;\n\n const video = effective.video ?? {};\n const floor = quality.fluidFloorKbps ?? DEFAULT_FLUID_FLOOR_KBPS;\n const anchor = quality.degradationAnchor;\n\n if (anchor !== undefined) {\n if (!(anchor in video)) return asked;\n const cap = video[anchor];\n if (cap === null) return asked;\n return cap >= floor ? \"maintain-framerate\" : \"maintain-resolution\";\n }\n\n const caps = Object.values(video);\n if (caps.length === 0) return asked;\n if (caps.some((cap) => cap === null)) return asked;\n\n const budget = Math.max(...caps.filter((cap): cap is number => cap !== null));\n return budget >= floor ? \"maintain-framerate\" : \"maintain-resolution\";\n}\n\n/**\n * Read a video sender's parameters back with the motion settings written on.\n *\n * Read fresh on every call because `setParameters` only accepts the object the\n * **same** sender's `getParameters` returned, so a rejected attempt cannot be\n * retried with the object that was rejected.\n *\n * @param sender - The video sender to read from.\n * @param degradation - What to give up first, or `undefined` to leave it alone.\n * @param fps - Frame ceiling, or `undefined` to leave it alone.\n * @returns Parameters ready to be written back to `sender`.\n */\nfunction videoParameters(\n sender: RTCRtpSender,\n degradation: RTCDegradationPreference | undefined,\n fps: number | undefined,\n): RTCRtpSendParameters {\n const params = sender.getParameters();\n if (degradation !== undefined) params.degradationPreference = degradation;\n if (fps !== undefined) {\n for (const encoding of params.encodings ?? []) encoding.maxFramerate = fps;\n }\n return params;\n}\n\n/**\n * Write the quality settings onto one link's senders.\n *\n * `degradationPreference` goes only on video: it describes trading resolution\n * against frame rate, which an audio sender has no analogue for, and some\n * browsers reject it outright there.\n *\n * The retry without `degradationPreference` is for Firefox, which rejects the\n * member entirely — sending both together would lose the frame-rate cap to an\n * objection about something else.\n *\n * @param transceivers - The link's transceivers, in slot order.\n * @param slots - The slot list those transceivers were allocated from.\n * @param effective - Caps after the room's division.\n * @param degradation - Already resolved against the fluid floor.\n */\nexport async function applyQualityToLink(\n transceivers: readonly RTCRtpTransceiver[],\n slots: readonly MeshSlot[],\n effective: MeshQuality,\n degradation: RTCDegradationPreference | undefined,\n): Promise<void> {\n for (const [index, slot] of slots.entries()) {\n const sender = transceivers[index]?.sender;\n if (!sender) continue;\n\n if (slot.kind === \"audio\") {\n const bps = effective.audio?.[slot.name];\n if (bps !== undefined) await setSenderBitrate(sender, bps);\n continue;\n }\n\n const kbps = effective.video?.[slot.name];\n if (kbps !== undefined) await setSenderBitrate(sender, kbps === null ? null : kbps * 1000);\n\n if (degradation === undefined && effective.maxFramerate === undefined) continue;\n try {\n await sender.setParameters(\n videoParameters(sender, degradation, effective.maxFramerate),\n );\n } catch {\n try {\n await sender.setParameters(\n videoParameters(sender, undefined, effective.maxFramerate),\n );\n } catch {\n /* the sender refused both; the bitrate cap above still applies */\n }\n }\n }\n}\n"],"mappings":";;AAIA,IAAM,IAA6B,KAG7B,IAAyB,KAGzB,IAA2B;AAoBjC,SAAgB,EAAa,GAAsB,GAA4B;CAC3E,IAAM,IAAQ,EAAQ,SAAS,CAAC;CAChC,IAAI,KAAS,GAAG,OAAO;CAEvB,IAAM,IAAQ,OAAO,OAAO,CAAK,CAAC,CAAC,QAAgB,GAAK,MAAQ,KAAO,KAAO,IAAI,CAAC;CACnF,IAAI,MAAU,GAAG,OAAO;CAExB,IAAM,KAAW,EAAQ,oBAAoB,KAA8B;CAC3E,IAAI,KAAS,GAAS,OAAO;CAE7B,IAAM,IAAQ,EAAQ,gBAAgB,GAChC,IAAS,IAAU,GACnB,IAAwC,CAAC;CAC/C,KAAK,IAAM,CAAC,GAAM,MAAQ,OAAO,QAAQ,CAAK,GAC1C,EAAO,KAAQ,MAAQ,OAAO,OAAO,KAAK,IAAI,GAAO,KAAK,MAAM,IAAM,CAAM,CAAC;CAEjF,OAAO;EAAE,GAAG;EAAS,OAAO;CAAO;AACvC;AA0BA,SAAgB,EACZ,GACA,GACoC;CACpC,IAAM,IAAQ,EAAQ;CACtB,IAAI,MAAU,sBAAsB,OAAO;CAE3C,IAAM,IAAQ,EAAU,SAAS,CAAC,GAC5B,IAAQ,EAAQ,kBAAkB,GAClC,IAAS,EAAQ;CAEvB,IAAI,MAAW,KAAA,GAAW;EACtB,IAAI,EAAE,KAAU,IAAQ,OAAO;EAC/B,IAAM,IAAM,EAAM;EAElB,OADI,MAAQ,OAAa,IAClB,KAAO,IAAQ,uBAAuB;CACjD;CAEA,IAAM,IAAO,OAAO,OAAO,CAAK;CAKhC,OAJI,EAAK,WAAW,KAChB,EAAK,MAAM,MAAQ,MAAQ,IAAI,IAAU,IAE9B,KAAK,IAAI,GAAG,EAAK,QAAQ,MAAuB,MAAQ,IAAI,CACpE,KAAU,IAAQ,uBAAuB;AACpD;AAcA,SAAS,EACL,GACA,GACA,GACoB;CACpB,IAAM,IAAS,EAAO,cAAc;CAEpC,IADI,MAAgB,KAAA,MAAW,EAAO,wBAAwB,IAC1D,MAAQ,KAAA,GACR,KAAK,IAAM,KAAY,EAAO,aAAa,CAAC,GAAG,EAAS,eAAe;CAE3E,OAAO;AACX;AAkBA,eAAsB,EAClB,GACA,GACA,GACA,GACa;CACb,KAAK,IAAM,CAAC,GAAO,MAAS,EAAM,QAAQ,GAAG;EACzC,IAAM,IAAS,EAAa,EAAM,EAAE;EACpC,IAAI,CAAC,GAAQ;EAEb,IAAI,EAAK,SAAS,SAAS;GACvB,IAAM,IAAM,EAAU,QAAQ,EAAK;GACnC,AAAI,MAAQ,KAAA,KAAW,MAAM,EAAiB,GAAQ,CAAG;GACzD;EACJ;EAEA,IAAM,IAAO,EAAU,QAAQ,EAAK;EACpC,IAAI,MAAS,KAAA,KAAW,MAAM,EAAiB,GAAQ,MAAS,OAAO,OAAO,IAAO,GAAI,GAErF,MAAgB,KAAA,KAAa,EAAU,iBAAiB,KAAA,GAC5D,IAAI;GACA,MAAM,EAAO,cACT,EAAgB,GAAQ,GAAa,EAAU,YAAY,CAC/D;EACJ,QAAQ;GACJ,IAAI;IACA,MAAM,EAAO,cACT,EAAgB,GAAQ,KAAA,GAAW,EAAU,YAAY,CAC7D;GACJ,QAAQ,CAER;EACJ;CACJ;AACJ"}
1
+ {"version":3,"file":"mesh-quality.js","names":["DEFAULT_UPLINK_BUDGET_KBPS","DEFAULT_MIN_VIDEO_KBPS","DEFAULT_FLUID_FLOOR_KBPS","scaleForRoom","video","quality","peers","asked","sum","cap","perPeer","floor","factor","scaled","slot","resolveDegradation","effective","anchor","caps","videoParameters","params","sender","degradation","fps","encoding","applyQualityToLink","index","slots","transceivers","bps","setSenderBitrate","kbps"],"sources":["../../src/webrtc/mesh-quality.ts"],"sourcesContent":["import { setSenderBitrate } from \"./sender-bitrate\";\nimport type { MeshQuality, MeshSlot } from \"./mesh-types\";\n\n/** Uplink assumed available when the caller names no budget, in kbps. */\nconst DEFAULT_UPLINK_BUDGET_KBPS = 6000;\n\n/** Floor a video slot keeps after the division, in kbps. */\nconst DEFAULT_MIN_VIDEO_KBPS = 300;\n\n/** Budget below which `maintain-framerate` stops being worth holding, in kbps. */\nconst DEFAULT_FLUID_FLOOR_KBPS = 900;\n\n/**\n * Divide the video caps by the size of the room.\n *\n * A mesh sends one copy of everything per participant, so the uplink is the\n * shared resource and the caps are what compete for it. Nothing is divided while\n * the caller is alone with one peer — that is the case the largest sizes exist\n * for, and dividing a budget that is not being shared would make them\n * unreachable in the only call where they fit.\n *\n * The floor is what keeps the division honest: without it a busy room allocates\n * tens of kbps per stream, and everybody loses the picture instead of the excess\n * giving way.\n *\n * @param quality - The caps as asked for.\n * @param peers - How many links are live.\n * @returns The caps to actually apply. The input is never mutated, so a room\n * that empties out climbs back to what was asked for.\n */\nexport function scaleForRoom(quality: MeshQuality, peers: number): MeshQuality {\n const video = quality.video ?? {};\n if (peers <= 1) return quality;\n\n const asked = Object.values(video).reduce<number>((sum, cap) => sum + (cap ?? 0), 0);\n if (asked === 0) return quality;\n\n const perPeer = (quality.uplinkBudgetKbps ?? DEFAULT_UPLINK_BUDGET_KBPS) / peers;\n if (asked <= perPeer) return quality;\n\n const floor = quality.minVideoKbps ?? DEFAULT_MIN_VIDEO_KBPS;\n const factor = perPeer / asked;\n const scaled: Record<string, number | null> = {};\n for (const [slot, cap] of Object.entries(video)) {\n scaled[slot] = cap === null ? null : Math.max(floor, Math.round(cap * factor));\n }\n return { ...quality, video: scaled };\n}\n\n/**\n * Decide what the encoder gives up, letting physics override the preference.\n *\n * `maintain-framerate` is honoured while there are bits enough for the frames to\n * be worth keeping. Once the room's division has taken the budget below the\n * fluid floor, holding the rate halves what each frame receives and the picture\n * is worse than the lower rate it replaced — so below that line the preference\n * is overridden rather than obeyed.\n *\n * Two things decide *which* budget answers that question:\n *\n * - **`null` is the most generous case, not the absent one.** A slot with no cap\n * is unbounded, which is exactly where fluidity should hold. Reading it as\n * missing — and then deciding from a modest camera beside it — is the answer\n * backwards.\n * - **{@link MeshQuality.degradationAnchor} names the slot the choice was\n * about.** Without it the largest cap across the video slots answers, which\n * is right when the slots are interchangeable and wrong when they are not.\n *\n * @param quality - What was asked for, including the anchor and the floor.\n * @param effective - What the room's share actually allows.\n * @returns The degradation preference to write onto the senders, or `undefined`\n * when the caller expressed no preference.\n */\nexport function resolveDegradation(\n quality: MeshQuality,\n effective: MeshQuality,\n): RTCDegradationPreference | undefined {\n const asked = quality.degradationPreference;\n if (asked !== \"maintain-framerate\") return asked;\n\n const video = effective.video ?? {};\n const floor = quality.fluidFloorKbps ?? DEFAULT_FLUID_FLOOR_KBPS;\n const anchor = quality.degradationAnchor;\n\n if (anchor !== undefined) {\n if (!(anchor in video)) return asked;\n const cap = video[anchor];\n if (cap === null) return asked;\n return cap >= floor ? \"maintain-framerate\" : \"maintain-resolution\";\n }\n\n const caps = Object.values(video);\n if (caps.length === 0) return asked;\n if (caps.some((cap) => cap === null)) return asked;\n\n const budget = Math.max(...caps.filter((cap): cap is number => cap !== null));\n return budget >= floor ? \"maintain-framerate\" : \"maintain-resolution\";\n}\n\n/**\n * Read a video sender's parameters back with the motion settings written on.\n *\n * Read fresh on every call because `setParameters` only accepts the object the\n * **same** sender's `getParameters` returned, so a rejected attempt cannot be\n * retried with the object that was rejected.\n *\n * @param sender - The video sender to read from.\n * @param degradation - What to give up first, or `undefined` to leave it alone.\n * @param fps - Frame ceiling, or `undefined` to leave it alone.\n * @returns Parameters ready to be written back to `sender`.\n */\nfunction videoParameters(\n sender: RTCRtpSender,\n degradation: RTCDegradationPreference | undefined,\n fps: number | undefined,\n): RTCRtpSendParameters {\n const params = sender.getParameters();\n if (degradation !== undefined) params.degradationPreference = degradation;\n if (fps !== undefined) {\n for (const encoding of params.encodings ?? []) encoding.maxFramerate = fps;\n }\n return params;\n}\n\n/**\n * Write the quality settings onto one link's senders.\n *\n * `degradationPreference` goes only on video: it describes trading resolution\n * against frame rate, which an audio sender has no analogue for, and some\n * browsers reject it outright there.\n *\n * The retry without `degradationPreference` is for Firefox, which rejects the\n * member entirely — sending both together would lose the frame-rate cap to an\n * objection about something else.\n *\n * @param transceivers - The link's transceivers, in slot order.\n * @param slots - The slot list those transceivers were allocated from.\n * @param effective - Caps after the room's division.\n * @param degradation - Already resolved against the fluid floor.\n */\nexport async function applyQualityToLink(\n transceivers: readonly RTCRtpTransceiver[],\n slots: readonly MeshSlot[],\n effective: MeshQuality,\n degradation: RTCDegradationPreference | undefined,\n): Promise<void> {\n for (const [index, slot] of slots.entries()) {\n const sender = transceivers[index]?.sender;\n if (!sender) continue;\n\n if (slot.kind === \"audio\") {\n const bps = effective.audio?.[slot.name];\n if (bps !== undefined) await setSenderBitrate(sender, bps);\n continue;\n }\n\n const kbps = effective.video?.[slot.name];\n if (kbps !== undefined) await setSenderBitrate(sender, kbps === null ? null : kbps * 1000);\n\n if (degradation === undefined && effective.maxFramerate === undefined) continue;\n try {\n await sender.setParameters(\n videoParameters(sender, degradation, effective.maxFramerate),\n );\n } catch {\n try {\n await sender.setParameters(\n videoParameters(sender, undefined, effective.maxFramerate),\n );\n } catch {\n /* the sender refused both; the bitrate cap above still applies */\n }\n }\n }\n}\n"],"mappings":";;AAIA,IAAMA,IAA6B,KAG7BC,IAAyB,KAGzBC,IAA2B;AAoBjC,SAAgBC,EAAa,GAAsB,GAA4B;CAC3E,IAAMC,IAAQC,EAAQ,SAAS,CAAC;CAChC,IAAIC,KAAS,GAAG,OAAOD;CAEvB,IAAME,IAAQ,OAAO,OAAOH,CAAK,CAAC,CAAC,QAAgB,GAAK,MAAQI,KAAOC,KAAO,IAAI,CAAC;CACnF,IAAIF,MAAU,GAAG,OAAOF;CAExB,IAAMK,KAAWL,EAAQ,oBAAoBL,KAA8BM;CAC3E,IAAIC,KAASG,GAAS,OAAOL;CAE7B,IAAMM,IAAQN,EAAQ,gBAAgBJ,GAChCW,IAASF,IAAUH,GACnBM,IAAwC,CAAC;CAC/C,KAAK,IAAM,CAACC,GAAML,MAAQ,OAAO,QAAQL,CAAK,GAC1C,EAAOU,KAAQL,MAAQ,OAAO,OAAO,KAAK,IAAIE,GAAO,KAAK,MAAMF,IAAMG,CAAM,CAAC;CAEjF,OAAO;EAAE,GAAGP;EAAS,OAAOQ;CAAO;AACvC;AA0BA,SAAgBE,EACZ,GACA,GACoC;CACpC,IAAMR,IAAQF,EAAQ;CACtB,IAAIE,MAAU,sBAAsB,OAAOA;CAE3C,IAAMH,IAAQY,EAAU,SAAS,CAAC,GAC5BL,IAAQN,EAAQ,kBAAkBH,GAClCe,IAASZ,EAAQ;CAEvB,IAAIY,MAAW,KAAA,GAAW;EACtB,IAAI,EAAEA,KAAUb,IAAQ,OAAOG;EAC/B,IAAME,IAAML,EAAMa;EAElB,OADIR,MAAQ,OAAaF,IAClBE,KAAOE,IAAQ,uBAAuB;CACjD;CAEA,IAAMO,IAAO,OAAO,OAAOd,CAAK;CAKhC,OAJIc,EAAK,WAAW,KAChBA,EAAK,MAAM,MAAQT,MAAQ,IAAI,IAAUF,IAE9B,KAAK,IAAI,GAAGW,EAAK,QAAQ,MAAuBT,MAAQ,IAAI,CACpE,KAAUE,IAAQ,uBAAuB;AACpD;AAcA,SAASQ,EACL,GACA,GACA,GACoB;CACpB,IAAMC,IAASC,EAAO,cAAc;CAEpC,IADIC,MAAgB,KAAA,MAAW,EAAO,wBAAwBA,IAC1DC,MAAQ,KAAA,GACR,KAAK,IAAMC,KAAYJ,EAAO,aAAa,CAAC,GAAG,EAAS,eAAeG;CAE3E,OAAOH;AACX;AAkBA,eAAsBK,EAClB,GACA,GACA,GACA,GACa;CACb,KAAK,IAAM,CAACC,GAAOZ,MAASa,EAAM,QAAQ,GAAG;EACzC,IAAMN,IAASO,EAAaF,EAAM,EAAE;EACpC,IAAI,CAACL,GAAQ;EAEb,IAAIP,EAAK,SAAS,SAAS;GACvB,IAAMe,IAAMb,EAAU,QAAQF,EAAK;GACnC,AAAIe,MAAQ,KAAA,KAAW,MAAMC,EAAiBT,GAAQQ,CAAG;GACzD;EACJ;EAEA,IAAME,IAAOf,EAAU,QAAQF,EAAK;EAGpC,IAFIiB,MAAS,KAAA,KAAW,MAAMD,EAAiBT,GAAQU,MAAS,OAAO,OAAOA,IAAO,GAAI,GAErFT,MAAgB,KAAA,KAAaN,EAAU,iBAAiB,KAAA,GAC5D,IAAI;GACA,MAAMK,EAAO,cACTF,EAAgBE,GAAQC,GAAaN,EAAU,YAAY,CAC/D;EACJ,QAAQ;GACJ,IAAI;IACA,MAAMK,EAAO,cACTF,EAAgBE,GAAQ,KAAA,GAAWL,EAAU,YAAY,CAC7D;GACJ,QAAQ,CAER;EACJ;CACJ;AACJ"}