tempest-react-sdk 0.35.0 → 0.36.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 (211) hide show
  1. package/README.md +72 -70
  2. package/dist/audio/audio-output.cjs +2 -0
  3. package/dist/audio/audio-output.cjs.map +1 -0
  4. package/dist/audio/audio-output.js +17 -0
  5. package/dist/audio/audio-output.js.map +1 -0
  6. package/dist/audio/audio-player.cjs +1 -1
  7. package/dist/audio/audio-player.cjs.map +1 -1
  8. package/dist/audio/audio-player.js +21 -20
  9. package/dist/audio/audio-player.js.map +1 -1
  10. package/dist/audio/audio-recorder.cjs +2 -0
  11. package/dist/audio/audio-recorder.cjs.map +1 -0
  12. package/dist/audio/audio-recorder.js +30 -0
  13. package/dist/audio/audio-recorder.js.map +1 -0
  14. package/dist/audio/duration.cjs +2 -0
  15. package/dist/audio/duration.cjs.map +1 -0
  16. package/dist/audio/duration.js +10 -0
  17. package/dist/audio/duration.js.map +1 -0
  18. package/dist/audio/level-meter.cjs +2 -0
  19. package/dist/audio/level-meter.cjs.map +1 -0
  20. package/dist/audio/level-meter.js +34 -0
  21. package/dist/audio/level-meter.js.map +1 -0
  22. package/dist/audio/media-access.cjs +2 -0
  23. package/dist/audio/media-access.cjs.map +1 -0
  24. package/dist/audio/media-access.js +51 -0
  25. package/dist/audio/media-access.js.map +1 -0
  26. package/dist/audio/use-audio-recorder.cjs +2 -0
  27. package/dist/audio/use-audio-recorder.cjs.map +1 -0
  28. package/dist/audio/use-audio-recorder.js +94 -0
  29. package/dist/audio/use-audio-recorder.js.map +1 -0
  30. package/dist/audio/use-audio.cjs.map +1 -1
  31. package/dist/audio/use-audio.js.map +1 -1
  32. package/dist/audio/use-microphone.cjs +2 -0
  33. package/dist/audio/use-microphone.cjs.map +1 -0
  34. package/dist/audio/use-microphone.js +54 -0
  35. package/dist/audio/use-microphone.js.map +1 -0
  36. package/dist/audio/wav.cjs +2 -0
  37. package/dist/audio/wav.cjs.map +1 -0
  38. package/dist/audio/wav.js +49 -0
  39. package/dist/audio/wav.js.map +1 -0
  40. package/dist/auth/passkey.cjs +2 -0
  41. package/dist/auth/passkey.cjs.map +1 -0
  42. package/dist/auth/passkey.js +171 -0
  43. package/dist/auth/passkey.js.map +1 -0
  44. package/dist/auth/use-passkey.cjs +2 -0
  45. package/dist/auth/use-passkey.cjs.map +1 -0
  46. package/dist/auth/use-passkey.js +120 -0
  47. package/dist/auth/use-passkey.js.map +1 -0
  48. package/dist/br/PixQRCode.cjs +2 -0
  49. package/dist/br/PixQRCode.cjs.map +1 -0
  50. package/dist/br/PixQRCode.js +70 -0
  51. package/dist/br/PixQRCode.js.map +1 -0
  52. package/dist/br/PixQRCode.module.cjs +2 -0
  53. package/dist/br/PixQRCode.module.cjs.map +1 -0
  54. package/dist/br/PixQRCode.module.js +15 -0
  55. package/dist/br/PixQRCode.module.js.map +1 -0
  56. package/dist/br/boleto.cjs +2 -0
  57. package/dist/br/boleto.cjs.map +1 -0
  58. package/dist/br/boleto.js +225 -0
  59. package/dist/br/boleto.js.map +1 -0
  60. package/dist/br/holidays.cjs +2 -0
  61. package/dist/br/holidays.cjs.map +1 -0
  62. package/dist/br/holidays.js +148 -0
  63. package/dist/br/holidays.js.map +1 -0
  64. package/dist/br/nfe.cjs +2 -0
  65. package/dist/br/nfe.cjs.map +1 -0
  66. package/dist/br/nfe.js +104 -0
  67. package/dist/br/nfe.js.map +1 -0
  68. package/dist/br/pix.cjs +2 -0
  69. package/dist/br/pix.cjs.map +1 -0
  70. package/dist/br/pix.js +148 -0
  71. package/dist/br/pix.js.map +1 -0
  72. package/dist/br.cjs +1 -1
  73. package/dist/br.cjs.map +1 -1
  74. package/dist/br.d.ts +878 -0
  75. package/dist/br.js +7 -2
  76. package/dist/br.js.map +1 -1
  77. package/dist/capture/barcode.cjs +2 -0
  78. package/dist/capture/barcode.cjs.map +1 -0
  79. package/dist/capture/barcode.js +59 -0
  80. package/dist/capture/barcode.js.map +1 -0
  81. package/dist/capture/media-recorder.cjs +2 -0
  82. package/dist/capture/media-recorder.cjs.map +1 -0
  83. package/dist/capture/media-recorder.js +64 -0
  84. package/dist/capture/media-recorder.js.map +1 -0
  85. package/dist/capture/use-barcode-scanner.cjs +2 -0
  86. package/dist/capture/use-barcode-scanner.cjs.map +1 -0
  87. package/dist/capture/use-barcode-scanner.js +86 -0
  88. package/dist/capture/use-barcode-scanner.js.map +1 -0
  89. package/dist/capture/use-screen-capture.cjs +2 -0
  90. package/dist/capture/use-screen-capture.cjs.map +1 -0
  91. package/dist/capture/use-screen-capture.js +79 -0
  92. package/dist/capture/use-screen-capture.js.map +1 -0
  93. package/dist/capture/use-speech-recognition.cjs +2 -0
  94. package/dist/capture/use-speech-recognition.cjs.map +1 -0
  95. package/dist/capture/use-speech-recognition.js +118 -0
  96. package/dist/capture/use-speech-recognition.js.map +1 -0
  97. package/dist/capture/use-torch.cjs +2 -0
  98. package/dist/capture/use-torch.cjs.map +1 -0
  99. package/dist/capture/use-torch.js +33 -0
  100. package/dist/capture/use-torch.js.map +1 -0
  101. package/dist/capture/use-video-recorder.cjs +2 -0
  102. package/dist/capture/use-video-recorder.cjs.map +1 -0
  103. package/dist/capture/use-video-recorder.js +90 -0
  104. package/dist/capture/use-video-recorder.js.map +1 -0
  105. package/dist/capture/video-recorder.cjs +2 -0
  106. package/dist/capture/video-recorder.cjs.map +1 -0
  107. package/dist/capture/video-recorder.js +31 -0
  108. package/dist/capture/video-recorder.js.map +1 -0
  109. package/dist/components/AIChat/AIChat.cjs +1 -1
  110. package/dist/components/AIChat/AIChat.cjs.map +1 -1
  111. package/dist/components/AIChat/AIChat.js +93 -97
  112. package/dist/components/AIChat/AIChat.js.map +1 -1
  113. package/dist/components/AIChat/AIChatComposer.cjs +1 -1
  114. package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
  115. package/dist/components/AIChat/AIChatComposer.js +2 -1
  116. package/dist/components/AIChat/AIChatComposer.js.map +1 -1
  117. package/dist/components/AIChat/AIChatTurn.cjs +2 -2
  118. package/dist/components/AIChat/AIChatTurn.js +6 -6
  119. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  120. package/dist/components/Alert/Alert.module.cjs.map +1 -1
  121. package/dist/components/Alert/Alert.module.js.map +1 -1
  122. package/dist/components/AudioPlayer/AudioPlayer.cjs +2 -0
  123. package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -0
  124. package/dist/components/AudioPlayer/AudioPlayer.js +123 -0
  125. package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -0
  126. package/dist/components/AudioPlayer/AudioPlayer.module.cjs +2 -0
  127. package/dist/components/AudioPlayer/AudioPlayer.module.cjs.map +1 -0
  128. package/dist/components/AudioPlayer/AudioPlayer.module.js +12 -0
  129. package/dist/components/AudioPlayer/AudioPlayer.module.js.map +1 -0
  130. package/dist/components/AudioRecorder/AudioRecorder.cjs +2 -0
  131. package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -0
  132. package/dist/components/AudioRecorder/AudioRecorder.js +179 -0
  133. package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -0
  134. package/dist/components/AudioRecorder/AudioRecorder.module.cjs +2 -0
  135. package/dist/components/AudioRecorder/AudioRecorder.module.cjs.map +1 -0
  136. package/dist/components/AudioRecorder/AudioRecorder.module.js +19 -0
  137. package/dist/components/AudioRecorder/AudioRecorder.module.js.map +1 -0
  138. package/dist/components/Badge/Badge.module.cjs.map +1 -1
  139. package/dist/components/Badge/Badge.module.js.map +1 -1
  140. package/dist/components/BarcodeScanner/BarcodeScanner.cjs +2 -0
  141. package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -0
  142. package/dist/components/BarcodeScanner/BarcodeScanner.js +123 -0
  143. package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -0
  144. package/dist/components/BarcodeScanner/BarcodeScanner.module.cjs +2 -0
  145. package/dist/components/BarcodeScanner/BarcodeScanner.module.cjs.map +1 -0
  146. package/dist/components/BarcodeScanner/BarcodeScanner.module.js +22 -0
  147. package/dist/components/BarcodeScanner/BarcodeScanner.module.js.map +1 -0
  148. package/dist/components/BottomNavigation/BottomNavigation.module.cjs.map +1 -1
  149. package/dist/components/BottomNavigation/BottomNavigation.module.js.map +1 -1
  150. package/dist/components/Button/Button.module.cjs.map +1 -1
  151. package/dist/components/Button/Button.module.js.map +1 -1
  152. package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
  153. package/dist/components/Combobox/Combobox.module.js.map +1 -1
  154. package/dist/components/DataTable/DataTable.cjs +1 -1
  155. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  156. package/dist/components/DataTable/DataTable.js +203 -66
  157. package/dist/components/DataTable/DataTable.js.map +1 -1
  158. package/dist/components/DataTable/DataTable.module.cjs +1 -1
  159. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  160. package/dist/components/DataTable/DataTable.module.js +9 -3
  161. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  162. package/dist/components/DataTable/EditableCell.cjs +2 -0
  163. package/dist/components/DataTable/EditableCell.cjs.map +1 -0
  164. package/dist/components/DataTable/EditableCell.js +77 -0
  165. package/dist/components/DataTable/EditableCell.js.map +1 -0
  166. package/dist/components/DataTable/edit-labels.cjs +2 -0
  167. package/dist/components/DataTable/edit-labels.cjs.map +1 -0
  168. package/dist/components/DataTable/edit-labels.js +11 -0
  169. package/dist/components/DataTable/edit-labels.js.map +1 -0
  170. package/dist/components/DateRangePicker/DateRangePicker.module.cjs.map +1 -1
  171. package/dist/components/DateRangePicker/DateRangePicker.module.js.map +1 -1
  172. package/dist/components/MultiSelect/MultiSelect.module.cjs.map +1 -1
  173. package/dist/components/MultiSelect/MultiSelect.module.js.map +1 -1
  174. package/dist/components/NavigationRail/NavigationRail.module.cjs.map +1 -1
  175. package/dist/components/NavigationRail/NavigationRail.module.js.map +1 -1
  176. package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs +1 -1
  177. package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
  178. package/dist/components/SyncStatusBadge/SyncStatusBadge.js +33 -32
  179. package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
  180. package/dist/components/Toast/ToastProvider.cjs +1 -1
  181. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  182. package/dist/components/Toast/ToastProvider.js +1 -2
  183. package/dist/components/Toast/ToastProvider.js.map +1 -1
  184. package/dist/hooks/use-announce.cjs +2 -0
  185. package/dist/hooks/use-announce.cjs.map +1 -0
  186. package/dist/hooks/use-announce.js +50 -0
  187. package/dist/hooks/use-announce.js.map +1 -0
  188. package/dist/hooks/use-media-devices.cjs +2 -0
  189. package/dist/hooks/use-media-devices.cjs.map +1 -0
  190. package/dist/hooks/use-media-devices.js +46 -0
  191. package/dist/hooks/use-media-devices.js.map +1 -0
  192. package/dist/hooks/use-media-permission.cjs +2 -0
  193. package/dist/hooks/use-media-permission.cjs.map +1 -0
  194. package/dist/hooks/use-media-permission.js +33 -0
  195. package/dist/hooks/use-media-permission.js.map +1 -0
  196. package/dist/http/resumable-upload.cjs +2 -0
  197. package/dist/http/resumable-upload.cjs.map +1 -0
  198. package/dist/http/resumable-upload.js +249 -0
  199. package/dist/http/resumable-upload.js.map +1 -0
  200. package/dist/styles.css +1 -1
  201. package/dist/tempest-react-sdk.cjs +1 -1
  202. package/dist/tempest-react-sdk.d.ts +2485 -29
  203. package/dist/tempest-react-sdk.js +268 -244
  204. package/dist/vision/use-camera-stream.cjs +1 -1
  205. package/dist/vision/use-camera-stream.cjs.map +1 -1
  206. package/dist/vision/use-camera-stream.js +40 -57
  207. package/dist/vision/use-camera-stream.js.map +1 -1
  208. package/dist/vision.cjs +1 -1
  209. package/dist/vision.d.ts +25 -3
  210. package/dist/vision.js +19 -19
  211. package/package.json +1 -1
package/dist/br.js CHANGED
@@ -9,11 +9,16 @@ import { BrazilStateCitySelect as x } from "./br/BrazilStateCitySelect.js";
9
9
  import { MunicipalitySearch as S } from "./br/MunicipalitySearch.js";
10
10
  import { DIVERGING_RDBU as C, SEQUENTIAL_BLUES as w, SEQUENTIAL_GREENS as T, SEQUENTIAL_VIRIDIS as E, interpolatePalette as D, quantizeScale as O, sequentialScale as k, thresholdScale as A } from "./br/scales.js";
11
11
  import { MapLegend as j } from "./br/MapLegend.js";
12
+ import { PixError as M, normalizePixKey as N, parsePixPayload as P, pixCrc16 as F, pixKeyType as I, pixPayload as L } from "./br/pix.js";
13
+ import { PixQRCode as R } from "./br/PixQRCode.js";
14
+ import { BoletoError as z, boletoDueDate as B, boletoKind as V, codigoBarrasToLinhaDigitavel as H, fatorVencimento as U, formatLinhaDigitavel as W, linhaDigitavelToCodigoBarras as G, mod10Dac as K, mod11DacArrecadacao as q, mod11DacCobranca as J, parseCodigoBarras as Y, parseLinhaDigitavel as X, validateBoleto as Z } from "./br/boleto.js";
15
+ import { ChaveNFeError as Q, chaveNFeCheckDigit as $, formatChaveNFe as ee, parseChaveNFe as te, validateChaveNFe as ne } from "./br/nfe.js";
16
+ import { addBusinessDays as re, easterSunday as ie, holidaysFor as ae, isBusinessDay as oe, isHoliday as se, nextBusinessDay as ce } from "./br/holidays.js";
12
17
  //#region src/br/index.ts
13
- async function M() {
18
+ async function le() {
14
19
  return (await import("./br/br-geo.js")).BR_UF_GEOJSON;
15
20
  }
16
21
  //#endregion
17
- export { y as BrazilMap, x as BrazilStateCitySelect, b as BrazilStateMap, C as DIVERGING_RDBU, j as MapLegend, g as MapMarkers, S as MunicipalitySearch, _ as REGION_COLORS, w as SEQUENTIAL_BLUES, T as SEQUENTIAL_GREENS, E as SEQUENTIAL_VIRIDIS, e as citiesByUf, t as cityChoices, u as geocodeMunicipality, n as getState, D as interpolatePalette, r as isValidCity, i as isValidUf, a as listStates, M as loadBrUfGeoJson, l as loadStateMunicipalities, d as municipalityCentroid, f as nearestMunicipality, o as normalizeUf, O as quantizeScale, v as regionLegendItems, p as reverseGeocode, m as searchMunicipalities, k as sequentialScale, h as stateCentroid, s as statesByRegion, A as thresholdScale, c as ufChoices };
22
+ export { z as BoletoError, y as BrazilMap, x as BrazilStateCitySelect, b as BrazilStateMap, Q as ChaveNFeError, C as DIVERGING_RDBU, j as MapLegend, g as MapMarkers, S as MunicipalitySearch, M as PixError, R as PixQRCode, _ as REGION_COLORS, w as SEQUENTIAL_BLUES, T as SEQUENTIAL_GREENS, E as SEQUENTIAL_VIRIDIS, re as addBusinessDays, B as boletoDueDate, V as boletoKind, $ as chaveNFeCheckDigit, e as citiesByUf, t as cityChoices, H as codigoBarrasToLinhaDigitavel, ie as easterSunday, U as fatorVencimento, ee as formatChaveNFe, W as formatLinhaDigitavel, u as geocodeMunicipality, n as getState, ae as holidaysFor, D as interpolatePalette, oe as isBusinessDay, se as isHoliday, r as isValidCity, i as isValidUf, G as linhaDigitavelToCodigoBarras, a as listStates, le as loadBrUfGeoJson, l as loadStateMunicipalities, K as mod10Dac, q as mod11DacArrecadacao, J as mod11DacCobranca, d as municipalityCentroid, f as nearestMunicipality, ce as nextBusinessDay, N as normalizePixKey, o as normalizeUf, te as parseChaveNFe, Y as parseCodigoBarras, X as parseLinhaDigitavel, P as parsePixPayload, F as pixCrc16, I as pixKeyType, L as pixPayload, O as quantizeScale, v as regionLegendItems, p as reverseGeocode, m as searchMunicipalities, k as sequentialScale, h as stateCentroid, s as statesByRegion, A as thresholdScale, c as ufChoices, Z as validateBoleto, ne as validateChaveNFe };
18
23
 
19
24
  //# sourceMappingURL=br.js.map
package/dist/br.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"br.js","names":[],"sources":["../src/br/index.ts"],"sourcesContent":["// Brazilian locations data (states + cities) — mirrors the `utils/locations`\n// module of tempest-fastapi-sdk.\nexport {\n listStates,\n getState,\n citiesByUf,\n statesByRegion,\n isValidUf,\n normalizeUf,\n isValidCity,\n ufChoices,\n cityChoices,\n} from \"./locations\";\nexport type { UF, BrRegion, BrazilState, Choice } from \"./locations\";\n\n// UF GeoJSON types (the geometry itself is lazy-loaded by BrazilMap).\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nexport type { BrUfFeature, BrUfFeatureCollection, BrUfGeometry, Ring } from \"./br-geo\";\n\n/**\n * Lazily load the bundled simplified UF GeoJSON (~36 KB gzip). Kept out of the\n * synchronous barrel so a data-only import never pulls the geometry.\n */\nexport async function loadBrUfGeoJson(): Promise<BrUfFeatureCollection> {\n const mod = await import(\"./br-geo\");\n return mod.BR_UF_GEOJSON;\n}\n\n// Per-state municipality geometry (lazy, one chunk per UF).\nexport { loadStateMunicipalities } from \"./state-geo\";\nexport type { MunicipalityFeature, StateMunicipalities } from \"./state-geo\";\n\n// Offline geocoding (lazy centroid index).\nexport {\n nearestMunicipality,\n reverseGeocode,\n geocodeMunicipality,\n searchMunicipalities,\n municipalityCentroid,\n stateCentroid,\n} from \"./geocode\";\nexport type { MunicipalityCentroid, NearestMunicipality, ReverseGeocodeResult } from \"./geocode\";\n\n// Components\nexport { BrazilMap } from \"./BrazilMap\";\nexport type { BrazilMapProps, BrazilMapTooltipData } from \"./BrazilMap\";\nexport { BrazilStateMap } from \"./BrazilStateMap\";\nexport type {\n BrazilStateMapProps,\n BrazilStateMapTooltipData,\n Municipality,\n} from \"./BrazilStateMap\";\nexport { BrazilStateCitySelect } from \"./BrazilStateCitySelect\";\nexport type { BrazilStateCitySelectProps, BrazilStateCitySelection } from \"./BrazilStateCitySelect\";\nexport { MunicipalitySearch } from \"./MunicipalitySearch\";\nexport type { MunicipalitySearchProps } from \"./MunicipalitySearch\";\nexport type { GeoMarker } from \"@/geo/types\";\nexport { MapMarkers } from \"./MapMarkers\";\nexport type { MapMarkersProps } from \"./MapMarkers\";\nexport { MapLegend } from \"./MapLegend\";\nexport type { MapLegendProps, LegendItem } from \"./MapLegend\";\n\n// Color scales + palettes for choropleths.\nexport {\n interpolatePalette,\n sequentialScale,\n quantizeScale,\n thresholdScale,\n SEQUENTIAL_BLUES,\n SEQUENTIAL_GREENS,\n SEQUENTIAL_VIRIDIS,\n DIVERGING_RDBU,\n} from \"./scales\";\nexport type { ColorScale } from \"./scales\";\nexport { REGION_COLORS, regionLegendItems } from \"./regions\";\n"],"mappings":";;;;;;;;;;;;AAuBA,eAAsB,IAAkD;CAEpE,QAAO,MADW,OAAO,kBAAA,CACd;AACf"}
1
+ {"version":3,"file":"br.js","names":[],"sources":["../src/br/index.ts"],"sourcesContent":["// Brazilian locations data (states + cities) — mirrors the `utils/locations`\n// module of tempest-fastapi-sdk.\nexport {\n listStates,\n getState,\n citiesByUf,\n statesByRegion,\n isValidUf,\n normalizeUf,\n isValidCity,\n ufChoices,\n cityChoices,\n} from \"./locations\";\nexport type { UF, BrRegion, BrazilState, Choice } from \"./locations\";\n\n// UF GeoJSON types (the geometry itself is lazy-loaded by BrazilMap).\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nexport type { BrUfFeature, BrUfFeatureCollection, BrUfGeometry, Ring } from \"./br-geo\";\n\n/**\n * Lazily load the bundled simplified UF GeoJSON (~36 KB gzip). Kept out of the\n * synchronous barrel so a data-only import never pulls the geometry.\n */\nexport async function loadBrUfGeoJson(): Promise<BrUfFeatureCollection> {\n const mod = await import(\"./br-geo\");\n return mod.BR_UF_GEOJSON;\n}\n\n// Per-state municipality geometry (lazy, one chunk per UF).\nexport { loadStateMunicipalities } from \"./state-geo\";\nexport type { MunicipalityFeature, StateMunicipalities } from \"./state-geo\";\n\n// Offline geocoding (lazy centroid index).\nexport {\n nearestMunicipality,\n reverseGeocode,\n geocodeMunicipality,\n searchMunicipalities,\n municipalityCentroid,\n stateCentroid,\n} from \"./geocode\";\nexport type { MunicipalityCentroid, NearestMunicipality, ReverseGeocodeResult } from \"./geocode\";\n\n// Components\nexport { BrazilMap } from \"./BrazilMap\";\nexport type { BrazilMapProps, BrazilMapTooltipData } from \"./BrazilMap\";\nexport { BrazilStateMap } from \"./BrazilStateMap\";\nexport type {\n BrazilStateMapProps,\n BrazilStateMapTooltipData,\n Municipality,\n} from \"./BrazilStateMap\";\nexport { BrazilStateCitySelect } from \"./BrazilStateCitySelect\";\nexport type { BrazilStateCitySelectProps, BrazilStateCitySelection } from \"./BrazilStateCitySelect\";\nexport { MunicipalitySearch } from \"./MunicipalitySearch\";\nexport type { MunicipalitySearchProps } from \"./MunicipalitySearch\";\nexport type { GeoMarker } from \"@/geo/types\";\nexport { MapMarkers } from \"./MapMarkers\";\nexport type { MapMarkersProps } from \"./MapMarkers\";\nexport { MapLegend } from \"./MapLegend\";\nexport type { MapLegendProps, LegendItem } from \"./MapLegend\";\n\n// Pix — BR Code (EMV MPM) payload + the QR that carries it.\nexport {\n normalizePixKey,\n parsePixPayload,\n PixError,\n pixCrc16,\n pixKeyType,\n pixPayload,\n} from \"./pix\";\nexport type {\n NormalizedPixKey,\n ParsePixOptions,\n PixData,\n PixDynamicInput,\n PixField,\n PixInput,\n PixKeyType,\n PixStaticInput,\n} from \"./pix\";\nexport { PixQRCode } from \"./PixQRCode\";\nexport type { PixQRCodeLabels, PixQRCodeProps } from \"./PixQRCode\";\n\n// Boleto — 47/48-digit typed line, 44-digit barcode, FEBRABAN check digits.\nexport {\n boletoDueDate,\n BoletoError,\n boletoKind,\n codigoBarrasToLinhaDigitavel,\n fatorVencimento,\n formatLinhaDigitavel,\n linhaDigitavelToCodigoBarras,\n mod10Dac,\n mod11DacArrecadacao,\n mod11DacCobranca,\n parseCodigoBarras,\n parseLinhaDigitavel,\n validateBoleto,\n} from \"./boleto\";\nexport type {\n Boleto,\n BoletoArrecadacao,\n BoletoBanco,\n BoletoEpoch,\n BoletoKind,\n BoletoOptions,\n} from \"./boleto\";\n\n// NF-e / NFC-e / CT-e access key.\nexport {\n chaveNFeCheckDigit,\n ChaveNFeError,\n formatChaveNFe,\n parseChaveNFe,\n validateChaveNFe,\n} from \"./nfe\";\nexport type { ChaveNFe } from \"./nfe\";\n\n// National holidays + business-day arithmetic.\nexport {\n addBusinessDays,\n easterSunday,\n holidaysFor,\n isBusinessDay,\n isHoliday,\n nextBusinessDay,\n} from \"./holidays\";\nexport type { BusinessDayOptions, DateInput, Holiday, HolidayKind } from \"./holidays\";\n\n// Color scales + palettes for choropleths.\nexport {\n interpolatePalette,\n sequentialScale,\n quantizeScale,\n thresholdScale,\n SEQUENTIAL_BLUES,\n SEQUENTIAL_GREENS,\n SEQUENTIAL_VIRIDIS,\n DIVERGING_RDBU,\n} from \"./scales\";\nexport type { ColorScale } from \"./scales\";\nexport { REGION_COLORS, regionLegendItems } from \"./regions\";\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,eAAsB,KAAkD;CAEpE,QAAO,MADW,OAAO,kBAAA,CACd;AACf"}
@@ -0,0 +1,2 @@
1
+ var e=[`aztec`,`codabar`,`code_128`,`code_39`,`code_93`,`data_matrix`,`ean_13`,`ean_8`,`itf`,`pdf417`,`qr_code`,`upc_a`,`upc_e`,`unknown`],t=[`qr_code`,`ean_13`,`code_128`];function n(){let e=globalThis.BarcodeDetector;return typeof e==`function`?e:null}function r(){return n()!==null}async function i(){let t=n();if(!t||typeof t.getSupportedFormats!=`function`)return[];try{return(await t.getSupportedFormats()).filter(t=>e.includes(t))}catch{return[]}}function a(e=t){let r=n();if(!r)return null;try{return new r({formats:[...e]})}catch{return null}}function o(t,n=e){let r=t.format;return{rawValue:t.rawValue??``,format:r!==void 0&&n.includes(r)?r:`unknown`,boundingBox:t.boundingBox??null,cornerPoints:t.cornerPoints??[]}}exports.ALL_BARCODE_FORMATS=e,exports.DEFAULT_BARCODE_FORMATS=t,exports.createBarcodeDetector=a,exports.getSupportedBarcodeFormats=i,exports.isBarcodeDetectionSupported=r,exports.normalizeBarcode=o;
2
+ //# sourceMappingURL=barcode.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barcode.cjs","names":[],"sources":["../../src/capture/barcode.ts"],"sourcesContent":["/**\n * Symbologies the `BarcodeDetector` API names.\n *\n * Three of them carry the weight in Brazil: `ean_13` is the retail barcode on every\n * packaged product, `qr_code` is what a Pix \"copia e cola\" payload travels in, and\n * `code_128` is the label on a shipment. The rest are here because the spec has them\n * and asking for one costs nothing.\n */\nexport type BarcodeFormat =\n | \"aztec\"\n | \"codabar\"\n | \"code_128\"\n | \"code_39\"\n | \"code_93\"\n | \"data_matrix\"\n | \"ean_13\"\n | \"ean_8\"\n | \"itf\"\n | \"pdf417\"\n | \"qr_code\"\n | \"upc_a\"\n | \"upc_e\"\n | \"unknown\";\n\n/** Every symbology in the spec, used to validate what a detector reports back. */\nexport const ALL_BARCODE_FORMATS: readonly BarcodeFormat[] = [\n \"aztec\",\n \"codabar\",\n \"code_128\",\n \"code_39\",\n \"code_93\",\n \"data_matrix\",\n \"ean_13\",\n \"ean_8\",\n \"itf\",\n \"pdf417\",\n \"qr_code\",\n \"upc_a\",\n \"upc_e\",\n \"unknown\",\n];\n\n/**\n * What a scanner looks for when you do not say.\n *\n * Deliberately three formats, not all fourteen. Every extra symbology is more work\n * per frame, and on a mid-range phone the difference between three and fourteen is the\n * difference between a scanner that locks on instantly and one that feels broken.\n */\nexport const DEFAULT_BARCODE_FORMATS: readonly BarcodeFormat[] = [\"qr_code\", \"ean_13\", \"code_128\"];\n\n/** A corner of a detected symbol, in the source's pixel coordinates. */\nexport interface BarcodePoint {\n x: number;\n y: number;\n}\n\n/** One decoded symbol, normalised. */\nexport interface BarcodeScanResult {\n /** The decoded payload — a GTIN, a URL, a Pix BR Code. */\n rawValue: string;\n /** Which symbology it was read as. `\"unknown\"` when the engine does not say. */\n format: BarcodeFormat;\n /** Box in source pixels, or `null` when the engine reports none. */\n boundingBox: DOMRectReadOnly | null;\n /** Corners in source pixels, clockwise from top-left. Empty when unreported. */\n cornerPoints: readonly BarcodePoint[];\n}\n\n/**\n * The shape a detector resolves with, before normalisation.\n *\n * Every field is optional because a polyfill is allowed to report the value and no\n * geometry at all, and the native engines differ on `cornerPoints`.\n */\nexport interface DetectedBarcodeLike {\n rawValue?: string;\n format?: string;\n boundingBox?: DOMRectReadOnly;\n cornerPoints?: readonly BarcodePoint[];\n}\n\n/**\n * The slice of `BarcodeDetector` this SDK uses.\n *\n * Exported so a consumer can **inject a polyfill** where the native API is missing —\n * see {@link isBarcodeDetectionSupported} for why that matters — and so tests can hand\n * in a decoder without a camera. Anything with a `detect()` that resolves to objects\n * carrying a `rawValue` will do.\n */\nexport interface BarcodeDetectorLike {\n /**\n * Decode every symbol visible in the source.\n *\n * @param source - A `<video>`, a `<canvas>`, an `ImageBitmap`, a `Blob`…\n * @returns Every symbol found, or an empty array — finding nothing is not an error.\n */\n detect: (source: ImageBitmapSource) => Promise<readonly DetectedBarcodeLike[]>;\n}\n\n/** The constructor, as Chromium exposes it on the global scope. */\ninterface BarcodeDetectorConstructorLike {\n new (options?: { formats?: readonly string[] }): BarcodeDetectorLike;\n getSupportedFormats?: () => Promise<readonly string[]>;\n}\n\n/**\n * Read the constructor off the global scope, or `null` when the engine has none.\n *\n * Kept private: everything a consumer needs is covered by\n * {@link isBarcodeDetectionSupported}, {@link getSupportedBarcodeFormats} and\n * {@link createBarcodeDetector}.\n */\nfunction barcodeDetectorConstructor(): BarcodeDetectorConstructorLike | null {\n const candidate = (globalThis as { BarcodeDetector?: unknown }).BarcodeDetector;\n return typeof candidate === \"function\" ? (candidate as BarcodeDetectorConstructorLike) : null;\n}\n\n/**\n * Whether this browser can decode barcodes on its own.\n *\n * **Expect `false` on a lot of real devices, and design for it.** `BarcodeDetector` is\n * a Chromium-only API backed by a platform decoder, so it is there on Android and\n * ChromeOS, usually there on macOS, and **absent** on Chromium for Windows and Linux,\n * in Firefox, and in every browser on iOS (all of which are WebKit underneath,\n * including Chrome for iOS).\n *\n * This SDK ships **no** decoder of its own and no bundled fallback: a QR reader is\n * Reed–Solomon error correction plus perspective correction plus a finder-pattern\n * search, and the honest options are a WASM build every consumer of this SDK would pay\n * for, or nothing. So the escape hatch is injection instead — pass any\n * {@link BarcodeDetectorLike} (the `barcode-detector` polyfill, your own `zxing-wasm`\n * wrapper) as `detector` to `useBarcodeScanner`, and the SDK drives it exactly like\n * the native one.\n *\n * @returns `true` when `new BarcodeDetector()` will work.\n */\nexport function isBarcodeDetectionSupported(): boolean {\n return barcodeDetectorConstructor() !== null;\n}\n\n/**\n * Which symbologies this engine will actually decode.\n *\n * Worth asking rather than assuming: the format list belongs to the platform decoder,\n * not to the browser, so two Chromium builds on two operating systems answer\n * differently — and asking for a format the engine does not have makes the constructor\n * throw `NotSupportedError`, which reads like a bug in your code.\n *\n * @returns The supported formats, or an empty array when there is no detector.\n */\nexport async function getSupportedBarcodeFormats(): Promise<readonly BarcodeFormat[]> {\n const constructor = barcodeDetectorConstructor();\n if (!constructor || typeof constructor.getSupportedFormats !== \"function\") return [];\n try {\n const formats = await constructor.getSupportedFormats();\n return formats.filter((format): format is BarcodeFormat =>\n ALL_BARCODE_FORMATS.includes(format as BarcodeFormat),\n );\n } catch {\n return [];\n }\n}\n\n/**\n * Build a native detector for the given formats.\n *\n * @param formats - Symbologies to look for. Must be ones the engine supports.\n * @returns The detector, or `null` when the API is missing or refused the formats.\n */\nexport function createBarcodeDetector(\n formats: readonly BarcodeFormat[] = DEFAULT_BARCODE_FORMATS,\n): BarcodeDetectorLike | null {\n const constructor = barcodeDetectorConstructor();\n if (!constructor) return null;\n try {\n return new constructor({ formats: [...formats] });\n } catch {\n return null;\n }\n}\n\n/**\n * Turn one raw detection into a {@link BarcodeScanResult}.\n *\n * An unrecognised `format` string becomes `\"unknown\"` rather than being passed through\n * as a lie about the union, and missing geometry becomes `null`/`[]` rather than\n * `undefined`, so a consumer never has to branch on three kinds of absence.\n *\n * @param raw - What the detector resolved with.\n * @param known - The formats considered valid. Defaults to the whole spec list.\n * @returns The normalised result.\n */\nexport function normalizeBarcode(\n raw: DetectedBarcodeLike,\n known: readonly BarcodeFormat[] = ALL_BARCODE_FORMATS,\n): BarcodeScanResult {\n const format = raw.format as BarcodeFormat | undefined;\n return {\n rawValue: raw.rawValue ?? \"\",\n format: format !== undefined && known.includes(format) ? format : \"unknown\",\n boundingBox: raw.boundingBox ?? null,\n cornerPoints: raw.cornerPoints ?? [],\n };\n}\n"],"mappings":"AAyBA,IAAa,EAAgD,CACzD,QACA,UACA,WACA,UACA,UACA,cACA,SACA,QACA,MACA,SACA,UACA,QACA,QACA,SACJ,EASa,EAAoD,CAAC,UAAW,SAAU,UAAU,EAgEjG,SAAS,GAAoE,CACzE,IAAM,EAAa,WAA6C,gBAChE,OAAO,OAAO,GAAc,WAAc,EAA+C,IAC7F,CAqBA,SAAgB,GAAuC,CACnD,OAAO,EAA2B,IAAM,IAC5C,CAYA,eAAsB,GAAgE,CAClF,IAAM,EAAc,EAA2B,EAC/C,GAAI,CAAC,GAAe,OAAO,EAAY,qBAAwB,WAAY,MAAO,CAAC,EACnF,GAAI,CAEA,OAAO,MADe,EAAY,oBAAoB,EAAA,CACvC,OAAQ,GACnB,EAAoB,SAAS,CAAuB,CACxD,CACJ,MAAQ,CACJ,MAAO,CAAC,CACZ,CACJ,CAQA,SAAgB,EACZ,EAAoC,EACV,CAC1B,IAAM,EAAc,EAA2B,EAC/C,GAAI,CAAC,EAAa,OAAO,KACzB,GAAI,CACA,OAAO,IAAI,EAAY,CAAE,QAAS,CAAC,GAAG,CAAO,CAAE,CAAC,CACpD,MAAQ,CACJ,OAAO,IACX,CACJ,CAaA,SAAgB,EACZ,EACA,EAAkC,EACjB,CACjB,IAAM,EAAS,EAAI,OACnB,MAAO,CACH,SAAU,EAAI,UAAY,GAC1B,OAAQ,IAAW,IAAA,IAAa,EAAM,SAAS,CAAM,EAAI,EAAS,UAClE,YAAa,EAAI,aAAe,KAChC,aAAc,EAAI,cAAgB,CAAC,CACvC,CACJ"}
@@ -0,0 +1,59 @@
1
+ //#region src/capture/barcode.ts
2
+ var e = [
3
+ "aztec",
4
+ "codabar",
5
+ "code_128",
6
+ "code_39",
7
+ "code_93",
8
+ "data_matrix",
9
+ "ean_13",
10
+ "ean_8",
11
+ "itf",
12
+ "pdf417",
13
+ "qr_code",
14
+ "upc_a",
15
+ "upc_e",
16
+ "unknown"
17
+ ], t = [
18
+ "qr_code",
19
+ "ean_13",
20
+ "code_128"
21
+ ];
22
+ function n() {
23
+ let e = globalThis.BarcodeDetector;
24
+ return typeof e == "function" ? e : null;
25
+ }
26
+ function r() {
27
+ return n() !== null;
28
+ }
29
+ async function i() {
30
+ let t = n();
31
+ if (!t || typeof t.getSupportedFormats != "function") return [];
32
+ try {
33
+ return (await t.getSupportedFormats()).filter((t) => e.includes(t));
34
+ } catch {
35
+ return [];
36
+ }
37
+ }
38
+ function a(e = t) {
39
+ let r = n();
40
+ if (!r) return null;
41
+ try {
42
+ return new r({ formats: [...e] });
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function o(t, n = e) {
48
+ let r = t.format;
49
+ return {
50
+ rawValue: t.rawValue ?? "",
51
+ format: r !== void 0 && n.includes(r) ? r : "unknown",
52
+ boundingBox: t.boundingBox ?? null,
53
+ cornerPoints: t.cornerPoints ?? []
54
+ };
55
+ }
56
+ //#endregion
57
+ export { e as ALL_BARCODE_FORMATS, t as DEFAULT_BARCODE_FORMATS, a as createBarcodeDetector, i as getSupportedBarcodeFormats, r as isBarcodeDetectionSupported, o as normalizeBarcode };
58
+
59
+ //# sourceMappingURL=barcode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"barcode.js","names":[],"sources":["../../src/capture/barcode.ts"],"sourcesContent":["/**\n * Symbologies the `BarcodeDetector` API names.\n *\n * Three of them carry the weight in Brazil: `ean_13` is the retail barcode on every\n * packaged product, `qr_code` is what a Pix \"copia e cola\" payload travels in, and\n * `code_128` is the label on a shipment. The rest are here because the spec has them\n * and asking for one costs nothing.\n */\nexport type BarcodeFormat =\n | \"aztec\"\n | \"codabar\"\n | \"code_128\"\n | \"code_39\"\n | \"code_93\"\n | \"data_matrix\"\n | \"ean_13\"\n | \"ean_8\"\n | \"itf\"\n | \"pdf417\"\n | \"qr_code\"\n | \"upc_a\"\n | \"upc_e\"\n | \"unknown\";\n\n/** Every symbology in the spec, used to validate what a detector reports back. */\nexport const ALL_BARCODE_FORMATS: readonly BarcodeFormat[] = [\n \"aztec\",\n \"codabar\",\n \"code_128\",\n \"code_39\",\n \"code_93\",\n \"data_matrix\",\n \"ean_13\",\n \"ean_8\",\n \"itf\",\n \"pdf417\",\n \"qr_code\",\n \"upc_a\",\n \"upc_e\",\n \"unknown\",\n];\n\n/**\n * What a scanner looks for when you do not say.\n *\n * Deliberately three formats, not all fourteen. Every extra symbology is more work\n * per frame, and on a mid-range phone the difference between three and fourteen is the\n * difference between a scanner that locks on instantly and one that feels broken.\n */\nexport const DEFAULT_BARCODE_FORMATS: readonly BarcodeFormat[] = [\"qr_code\", \"ean_13\", \"code_128\"];\n\n/** A corner of a detected symbol, in the source's pixel coordinates. */\nexport interface BarcodePoint {\n x: number;\n y: number;\n}\n\n/** One decoded symbol, normalised. */\nexport interface BarcodeScanResult {\n /** The decoded payload — a GTIN, a URL, a Pix BR Code. */\n rawValue: string;\n /** Which symbology it was read as. `\"unknown\"` when the engine does not say. */\n format: BarcodeFormat;\n /** Box in source pixels, or `null` when the engine reports none. */\n boundingBox: DOMRectReadOnly | null;\n /** Corners in source pixels, clockwise from top-left. Empty when unreported. */\n cornerPoints: readonly BarcodePoint[];\n}\n\n/**\n * The shape a detector resolves with, before normalisation.\n *\n * Every field is optional because a polyfill is allowed to report the value and no\n * geometry at all, and the native engines differ on `cornerPoints`.\n */\nexport interface DetectedBarcodeLike {\n rawValue?: string;\n format?: string;\n boundingBox?: DOMRectReadOnly;\n cornerPoints?: readonly BarcodePoint[];\n}\n\n/**\n * The slice of `BarcodeDetector` this SDK uses.\n *\n * Exported so a consumer can **inject a polyfill** where the native API is missing —\n * see {@link isBarcodeDetectionSupported} for why that matters — and so tests can hand\n * in a decoder without a camera. Anything with a `detect()` that resolves to objects\n * carrying a `rawValue` will do.\n */\nexport interface BarcodeDetectorLike {\n /**\n * Decode every symbol visible in the source.\n *\n * @param source - A `<video>`, a `<canvas>`, an `ImageBitmap`, a `Blob`…\n * @returns Every symbol found, or an empty array — finding nothing is not an error.\n */\n detect: (source: ImageBitmapSource) => Promise<readonly DetectedBarcodeLike[]>;\n}\n\n/** The constructor, as Chromium exposes it on the global scope. */\ninterface BarcodeDetectorConstructorLike {\n new (options?: { formats?: readonly string[] }): BarcodeDetectorLike;\n getSupportedFormats?: () => Promise<readonly string[]>;\n}\n\n/**\n * Read the constructor off the global scope, or `null` when the engine has none.\n *\n * Kept private: everything a consumer needs is covered by\n * {@link isBarcodeDetectionSupported}, {@link getSupportedBarcodeFormats} and\n * {@link createBarcodeDetector}.\n */\nfunction barcodeDetectorConstructor(): BarcodeDetectorConstructorLike | null {\n const candidate = (globalThis as { BarcodeDetector?: unknown }).BarcodeDetector;\n return typeof candidate === \"function\" ? (candidate as BarcodeDetectorConstructorLike) : null;\n}\n\n/**\n * Whether this browser can decode barcodes on its own.\n *\n * **Expect `false` on a lot of real devices, and design for it.** `BarcodeDetector` is\n * a Chromium-only API backed by a platform decoder, so it is there on Android and\n * ChromeOS, usually there on macOS, and **absent** on Chromium for Windows and Linux,\n * in Firefox, and in every browser on iOS (all of which are WebKit underneath,\n * including Chrome for iOS).\n *\n * This SDK ships **no** decoder of its own and no bundled fallback: a QR reader is\n * Reed–Solomon error correction plus perspective correction plus a finder-pattern\n * search, and the honest options are a WASM build every consumer of this SDK would pay\n * for, or nothing. So the escape hatch is injection instead — pass any\n * {@link BarcodeDetectorLike} (the `barcode-detector` polyfill, your own `zxing-wasm`\n * wrapper) as `detector` to `useBarcodeScanner`, and the SDK drives it exactly like\n * the native one.\n *\n * @returns `true` when `new BarcodeDetector()` will work.\n */\nexport function isBarcodeDetectionSupported(): boolean {\n return barcodeDetectorConstructor() !== null;\n}\n\n/**\n * Which symbologies this engine will actually decode.\n *\n * Worth asking rather than assuming: the format list belongs to the platform decoder,\n * not to the browser, so two Chromium builds on two operating systems answer\n * differently — and asking for a format the engine does not have makes the constructor\n * throw `NotSupportedError`, which reads like a bug in your code.\n *\n * @returns The supported formats, or an empty array when there is no detector.\n */\nexport async function getSupportedBarcodeFormats(): Promise<readonly BarcodeFormat[]> {\n const constructor = barcodeDetectorConstructor();\n if (!constructor || typeof constructor.getSupportedFormats !== \"function\") return [];\n try {\n const formats = await constructor.getSupportedFormats();\n return formats.filter((format): format is BarcodeFormat =>\n ALL_BARCODE_FORMATS.includes(format as BarcodeFormat),\n );\n } catch {\n return [];\n }\n}\n\n/**\n * Build a native detector for the given formats.\n *\n * @param formats - Symbologies to look for. Must be ones the engine supports.\n * @returns The detector, or `null` when the API is missing or refused the formats.\n */\nexport function createBarcodeDetector(\n formats: readonly BarcodeFormat[] = DEFAULT_BARCODE_FORMATS,\n): BarcodeDetectorLike | null {\n const constructor = barcodeDetectorConstructor();\n if (!constructor) return null;\n try {\n return new constructor({ formats: [...formats] });\n } catch {\n return null;\n }\n}\n\n/**\n * Turn one raw detection into a {@link BarcodeScanResult}.\n *\n * An unrecognised `format` string becomes `\"unknown\"` rather than being passed through\n * as a lie about the union, and missing geometry becomes `null`/`[]` rather than\n * `undefined`, so a consumer never has to branch on three kinds of absence.\n *\n * @param raw - What the detector resolved with.\n * @param known - The formats considered valid. Defaults to the whole spec list.\n * @returns The normalised result.\n */\nexport function normalizeBarcode(\n raw: DetectedBarcodeLike,\n known: readonly BarcodeFormat[] = ALL_BARCODE_FORMATS,\n): BarcodeScanResult {\n const format = raw.format as BarcodeFormat | undefined;\n return {\n rawValue: raw.rawValue ?? \"\",\n format: format !== undefined && known.includes(format) ? format : \"unknown\",\n boundingBox: raw.boundingBox ?? null,\n cornerPoints: raw.cornerPoints ?? [],\n };\n}\n"],"mappings":";AAyBA,IAAa,IAAgD;CACzD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,GASa,IAAoD;CAAC;CAAW;CAAU;AAAU;AAgEjG,SAAS,IAAoE;CACzE,IAAM,IAAa,WAA6C;CAChE,OAAO,OAAO,KAAc,aAAc,IAA+C;AAC7F;AAqBA,SAAgB,IAAuC;CACnD,OAAO,EAA2B,MAAM;AAC5C;AAYA,eAAsB,IAAgE;CAClF,IAAM,IAAc,EAA2B;CAC/C,IAAI,CAAC,KAAe,OAAO,EAAY,uBAAwB,YAAY,OAAO,CAAC;CACnF,IAAI;EAEA,QAAO,MADe,EAAY,oBAAoB,EAAA,CACvC,QAAQ,MACnB,EAAoB,SAAS,CAAuB,CACxD;CACJ,QAAQ;EACJ,OAAO,CAAC;CACZ;AACJ;AAQA,SAAgB,EACZ,IAAoC,GACV;CAC1B,IAAM,IAAc,EAA2B;CAC/C,IAAI,CAAC,GAAa,OAAO;CACzB,IAAI;EACA,OAAO,IAAI,EAAY,EAAE,SAAS,CAAC,GAAG,CAAO,EAAE,CAAC;CACpD,QAAQ;EACJ,OAAO;CACX;AACJ;AAaA,SAAgB,EACZ,GACA,IAAkC,GACjB;CACjB,IAAM,IAAS,EAAI;CACnB,OAAO;EACH,UAAU,EAAI,YAAY;EAC1B,QAAQ,MAAW,KAAA,KAAa,EAAM,SAAS,CAAM,IAAI,IAAS;EAClE,aAAa,EAAI,eAAe;EAChC,cAAc,EAAI,gBAAgB,CAAC;CACvC;AACJ"}
@@ -0,0 +1,2 @@
1
+ function e(e){return typeof MediaRecorder>`u`?null:typeof MediaRecorder.isTypeSupported==`function`?e.find(e=>MediaRecorder.isTypeSupported(e))??null:e[0]??null}function t(t,n){let{candidates:r,kind:i,mimeType:a,audioBitsPerSecond:o,videoBitsPerSecond:s,timesliceMs:c,onChunk:l,onError:u}=n;if(typeof MediaRecorder>`u`)throw Error(`MediaRecorder is not available in this environment.`);let d=a??e(r);if(d===null)throw Error(`This browser cannot record any supported ${i} container.`);if(a!==void 0&&typeof MediaRecorder.isTypeSupported==`function`&&!MediaRecorder.isTypeSupported(a))throw Error(`This browser cannot record "${a}".`);let f=new MediaRecorder(t,{mimeType:d,...o===void 0?{}:{audioBitsPerSecond:o},...s===void 0?{}:{videoBitsPerSecond:s}}),p=[],m=`idle`,h=0,g=0,_=null,v=!1,y=()=>h+(m===`recording`?Date.now()-g:0);return f.ondataavailable=e=>{e.data.size!==0&&(v||(p.push(e.data),l?.(e.data)))},f.onerror=e=>{u?.(e.error??e)},f.onstop=()=>{let e=y();m=`stopped`;let t=_;if(_=null,v){p=[];return}let n=f.mimeType||d;t?.({blob:new Blob(p,{type:n}),mimeType:n,durationMs:e}),p=[]},{mimeType:d,status:()=>m,durationMs:y,start(){m===`recording`||m===`paused`||(p=[],h=0,v=!1,g=Date.now(),m=`recording`,c===void 0?f.start():f.start(c))},pause(){m===`recording`&&(h+=Date.now()-g,m=`paused`,f.pause())},resume(){m===`paused`&&(g=Date.now(),m=`recording`,f.resume())},stop(){return m===`idle`||m===`stopped`?Promise.resolve({blob:new Blob([],{type:d}),mimeType:d,durationMs:0}):new Promise(e=>{_=e,f.stop()})},cancel(){m===`idle`||m===`stopped`||(v=!0,_=null,f.stop())}}}exports.createMediaRecorder=t,exports.pickRecordingMimeType=e;
2
+ //# sourceMappingURL=media-recorder.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-recorder.cjs","names":[],"sources":["../../src/capture/media-recorder.ts"],"sourcesContent":["/** Lifecycle of a recording. */\nexport type MediaRecorderStatus = \"idle\" | \"recording\" | \"paused\" | \"stopped\";\n\n/** What a track carries — used only to word the errors this engine throws. */\nexport type MediaRecordingKind = \"audio\" | \"video\";\n\n/** A finished recording, whatever it was made of. */\nexport interface MediaRecording {\n /** The bytes. Wrap with `useObjectUrl` to play it, or POST it as-is. */\n blob: Blob;\n /** What the browser actually produced — not necessarily what you asked for. */\n mimeType: string;\n /** Recorded length, excluding time spent paused. */\n durationMs: number;\n}\n\n/** Options for {@link createMediaRecorder}. */\nexport interface MediaRecordingOptions {\n /**\n * Container candidates, best first. Required: this engine has no opinion about\n * codecs — the audio and video wrappers own that list.\n */\n candidates: readonly string[];\n /** Wording for the thrown messages (\"cannot record any supported *video* container\"). */\n kind: MediaRecordingKind;\n /** Force a container, bypassing the negotiation. Throws when unsupported. */\n mimeType?: string;\n /** Target audio bitrate. */\n audioBitsPerSecond?: number;\n /** Target video bitrate. Ignored by the browser on an audio-only stream. */\n videoBitsPerSecond?: number;\n /**\n * Emit a chunk every N ms through `onChunk`, for streaming upload.\n *\n * Without it the whole recording is buffered in memory until `stop()`.\n */\n timesliceMs?: number;\n /** Receives each chunk when `timesliceMs` is set. Chunks are **not** independently playable. */\n onChunk?: (chunk: Blob) => void;\n /** Recorder-level failure (device unplugged mid-recording, encoder error). */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative recorder over one `MediaStream`. */\nexport interface MediaRecorderHandle {\n /** Begin recording. No-op when already recording or paused. */\n start: () => void;\n /** Pause. The clock stops; `durationMs` freezes. */\n pause: () => void;\n /** Resume after `pause()`. */\n resume: () => void;\n /** Stop and resolve with the assembled recording. */\n stop: () => Promise<MediaRecording>;\n /** Stop and throw the bytes away. */\n cancel: () => void;\n status: () => MediaRecorderStatus;\n /** Recorded length so far, excluding paused time. */\n durationMs: () => number;\n /** The negotiated container. */\n mimeType: string;\n}\n\n/**\n * First container in `preferred` the browser can actually produce, or `null`.\n *\n * @param preferred - Candidates, best first.\n * @returns A supported MIME type, or `null` when none is.\n */\nexport function pickRecordingMimeType(preferred: readonly string[]): string | null {\n if (typeof MediaRecorder === \"undefined\") return null;\n // Older WebViews ship `MediaRecorder` without the static probe. Assume the first\n // candidate rather than refusing outright — the constructor will tell us.\n if (typeof MediaRecorder.isTypeSupported !== \"function\") return preferred[0] ?? null;\n return preferred.find((type) => MediaRecorder.isTypeSupported(type)) ?? null;\n}\n\n/**\n * Wrap a `MediaStream` in a recorder with a real state machine and an honest clock.\n *\n * This is the engine behind both `createAudioRecorder` and `createVideoRecorder`; the\n * only thing it does not decide is which containers to try, because that is the one\n * part that genuinely differs between the two.\n *\n * Two things `MediaRecorder` does not give you:\n *\n * - **A duration.** It reports none, and the `Blob` has no reliable one either —\n * WebM written by `MediaRecorder` carries no duration in its header, which is why\n * `<audio>`/`<video>` shows `Infinity` for a fresh recording. So the clock is kept\n * here, and it subtracts paused time: a recorder that counts wall-clock through a\n * pause reports a 30-second note as two minutes.\n * - **A promise from `stop()`.** The last chunk arrives *after* `stop()` returns, in\n * a `dataavailable` event that fires before `onstop`. Assembling the blob in\n * `onstop` is the only point where every chunk is in hand.\n *\n * The stream is **not** owned here: `stop()` leaves the device open so a retake does\n * not need a second permission round-trip. Release it with the owning hook's `stop()`.\n *\n * @param stream - A live stream, from `getUserMedia` or `getDisplayMedia`.\n * @param options - See {@link MediaRecordingOptions}.\n * @returns The imperative recorder.\n * @throws When `MediaRecorder` is unavailable, or an explicit `mimeType` is unsupported.\n */\nexport function createMediaRecorder(\n stream: MediaStream,\n options: MediaRecordingOptions,\n): MediaRecorderHandle {\n const {\n candidates,\n kind,\n mimeType: forced,\n audioBitsPerSecond,\n videoBitsPerSecond,\n timesliceMs,\n onChunk,\n onError,\n } = options;\n\n if (typeof MediaRecorder === \"undefined\") {\n throw new Error(\"MediaRecorder is not available in this environment.\");\n }\n const negotiated = forced ?? pickRecordingMimeType(candidates);\n if (negotiated === null) {\n throw new Error(`This browser cannot record any supported ${kind} container.`);\n }\n if (\n forced !== undefined &&\n typeof MediaRecorder.isTypeSupported === \"function\" &&\n !MediaRecorder.isTypeSupported(forced)\n ) {\n throw new Error(`This browser cannot record \"${forced}\".`);\n }\n\n const recorder = new MediaRecorder(stream, {\n mimeType: negotiated,\n ...(audioBitsPerSecond !== undefined ? { audioBitsPerSecond } : {}),\n ...(videoBitsPerSecond !== undefined ? { videoBitsPerSecond } : {}),\n });\n\n let chunks: Blob[] = [];\n let status: MediaRecorderStatus = \"idle\";\n let accumulatedMs = 0;\n let segmentStart = 0;\n let settle: ((recording: MediaRecording) => void) | null = null;\n let discard = false;\n\n const elapsed = (): number =>\n accumulatedMs + (status === \"recording\" ? Date.now() - segmentStart : 0);\n\n recorder.ondataavailable = (event: BlobEvent): void => {\n if (event.data.size === 0) return;\n if (discard) return;\n chunks.push(event.data);\n onChunk?.(event.data);\n };\n\n recorder.onerror = (event: Event): void => {\n onError?.((event as unknown as { error?: unknown }).error ?? event);\n };\n\n recorder.onstop = (): void => {\n const durationMs = elapsed();\n status = \"stopped\";\n const resolve = settle;\n settle = null;\n if (discard) {\n chunks = [];\n return;\n }\n // `recorder.mimeType` is the source of truth: a browser handed\n // `video/webm;codecs=vp9,opus` may report plain `video/webm` back.\n const type = recorder.mimeType || negotiated;\n resolve?.({ blob: new Blob(chunks, { type }), mimeType: type, durationMs });\n chunks = [];\n };\n\n return {\n mimeType: negotiated,\n status: () => status,\n durationMs: elapsed,\n\n start(): void {\n if (status === \"recording\" || status === \"paused\") return;\n chunks = [];\n accumulatedMs = 0;\n discard = false;\n segmentStart = Date.now();\n status = \"recording\";\n if (timesliceMs !== undefined) recorder.start(timesliceMs);\n else recorder.start();\n },\n\n pause(): void {\n if (status !== \"recording\") return;\n accumulatedMs += Date.now() - segmentStart;\n status = \"paused\";\n recorder.pause();\n },\n\n resume(): void {\n if (status !== \"paused\") return;\n segmentStart = Date.now();\n status = \"recording\";\n recorder.resume();\n },\n\n stop(): Promise<MediaRecording> {\n if (status === \"idle\" || status === \"stopped\") {\n return Promise.resolve({\n blob: new Blob([], { type: negotiated }),\n mimeType: negotiated,\n durationMs: 0,\n });\n }\n // Stopping while paused needs no clock fix-up: `pause()` already folded\n // the last segment into `accumulatedMs`, and `elapsed()` adds nothing\n // while the status is not `\"recording\"`.\n return new Promise<MediaRecording>((resolve) => {\n settle = resolve;\n recorder.stop();\n });\n },\n\n cancel(): void {\n if (status === \"idle\" || status === \"stopped\") return;\n discard = true;\n settle = null;\n recorder.stop();\n },\n };\n}\n"],"mappings":"AAoEA,SAAgB,EAAsB,EAA6C,CAK/E,OAJI,OAAO,cAAkB,IAAoB,KAG7C,OAAO,cAAc,iBAAoB,WACtC,EAAU,KAAM,GAAS,cAAc,gBAAgB,CAAI,CAAC,GAAK,KADR,EAAU,IAAM,IAEpF,CA4BA,SAAgB,EACZ,EACA,EACmB,CACnB,GAAM,CACF,aACA,OACA,SAAU,EACV,qBACA,qBACA,cACA,UACA,WACA,EAEJ,GAAI,OAAO,cAAkB,IACzB,MAAU,MAAM,qDAAqD,EAEzE,IAAM,EAAa,GAAU,EAAsB,CAAU,EAC7D,GAAI,IAAe,KACf,MAAU,MAAM,4CAA4C,EAAK,YAAY,EAEjF,GACI,IAAW,IAAA,IACX,OAAO,cAAc,iBAAoB,YACzC,CAAC,cAAc,gBAAgB,CAAM,EAErC,MAAU,MAAM,+BAA+B,EAAO,GAAG,EAG7D,IAAM,EAAW,IAAI,cAAc,EAAQ,CACvC,SAAU,EACV,GAAI,IAAuB,IAAA,GAAqC,CAAC,EAA1B,CAAE,oBAAmB,EAC5D,GAAI,IAAuB,IAAA,GAAqC,CAAC,EAA1B,CAAE,oBAAmB,CAChE,CAAC,EAEG,EAAiB,CAAC,EAClB,EAA8B,OAC9B,EAAgB,EAChB,EAAe,EACf,EAAuD,KACvD,EAAU,GAER,MACF,GAAiB,IAAW,YAAc,KAAK,IAAI,EAAI,EAAe,GA6B1E,MA3BA,GAAS,gBAAmB,GAA2B,CAC/C,EAAM,KAAK,OAAS,IACpB,IACJ,EAAO,KAAK,EAAM,IAAI,EACtB,IAAU,EAAM,IAAI,GACxB,EAEA,EAAS,QAAW,GAAuB,CACvC,IAAW,EAAyC,OAAS,CAAK,CACtE,EAEA,EAAS,WAAqB,CAC1B,IAAM,EAAa,EAAQ,EAC3B,EAAS,UACT,IAAM,EAAU,EAEhB,GADA,EAAS,KACL,EAAS,CACT,EAAS,CAAC,EACV,MACJ,CAGA,IAAM,EAAO,EAAS,UAAY,EAClC,IAAU,CAAE,KAAM,IAAI,KAAK,EAAQ,CAAE,MAAK,CAAC,EAAG,SAAU,EAAM,YAAW,CAAC,EAC1E,EAAS,CAAC,CACd,EAEO,CACH,SAAU,EACV,WAAc,EACd,WAAY,EAEZ,OAAc,CACN,IAAW,aAAe,IAAW,WACzC,EAAS,CAAC,EACV,EAAgB,EAChB,EAAU,GACV,EAAe,KAAK,IAAI,EACxB,EAAS,YACL,IAAgB,IAAA,GACf,EAAS,MAAM,EADW,EAAS,MAAM,CAAW,EAE7D,EAEA,OAAc,CACN,IAAW,cACf,GAAiB,KAAK,IAAI,EAAI,EAC9B,EAAS,SACT,EAAS,MAAM,EACnB,EAEA,QAAe,CACP,IAAW,WACf,EAAe,KAAK,IAAI,EACxB,EAAS,YACT,EAAS,OAAO,EACpB,EAEA,MAAgC,CAW5B,OAVI,IAAW,QAAU,IAAW,UACzB,QAAQ,QAAQ,CACnB,KAAM,IAAI,KAAK,CAAC,EAAG,CAAE,KAAM,CAAW,CAAC,EACvC,SAAU,EACV,WAAY,CAChB,CAAC,EAKE,IAAI,QAAyB,GAAY,CAC5C,EAAS,EACT,EAAS,KAAK,CAClB,CAAC,CACL,EAEA,QAAe,CACP,IAAW,QAAU,IAAW,YACpC,EAAU,GACV,EAAS,KACT,EAAS,KAAK,EAClB,CACJ,CACJ"}
@@ -0,0 +1,64 @@
1
+ //#region src/capture/media-recorder.ts
2
+ function e(e) {
3
+ return typeof MediaRecorder > "u" ? null : typeof MediaRecorder.isTypeSupported == "function" ? e.find((e) => MediaRecorder.isTypeSupported(e)) ?? null : e[0] ?? null;
4
+ }
5
+ function t(t, n) {
6
+ let { candidates: r, kind: i, mimeType: a, audioBitsPerSecond: o, videoBitsPerSecond: s, timesliceMs: c, onChunk: l, onError: u } = n;
7
+ if (typeof MediaRecorder > "u") throw Error("MediaRecorder is not available in this environment.");
8
+ let d = a ?? e(r);
9
+ if (d === null) throw Error(`This browser cannot record any supported ${i} container.`);
10
+ if (a !== void 0 && typeof MediaRecorder.isTypeSupported == "function" && !MediaRecorder.isTypeSupported(a)) throw Error(`This browser cannot record "${a}".`);
11
+ let f = new MediaRecorder(t, {
12
+ mimeType: d,
13
+ ...o === void 0 ? {} : { audioBitsPerSecond: o },
14
+ ...s === void 0 ? {} : { videoBitsPerSecond: s }
15
+ }), p = [], m = "idle", h = 0, g = 0, _ = null, v = !1, y = () => h + (m === "recording" ? Date.now() - g : 0);
16
+ return f.ondataavailable = (e) => {
17
+ e.data.size !== 0 && (v || (p.push(e.data), l?.(e.data)));
18
+ }, f.onerror = (e) => {
19
+ u?.(e.error ?? e);
20
+ }, f.onstop = () => {
21
+ let e = y();
22
+ m = "stopped";
23
+ let t = _;
24
+ if (_ = null, v) {
25
+ p = [];
26
+ return;
27
+ }
28
+ let n = f.mimeType || d;
29
+ t?.({
30
+ blob: new Blob(p, { type: n }),
31
+ mimeType: n,
32
+ durationMs: e
33
+ }), p = [];
34
+ }, {
35
+ mimeType: d,
36
+ status: () => m,
37
+ durationMs: y,
38
+ start() {
39
+ m === "recording" || m === "paused" || (p = [], h = 0, v = !1, g = Date.now(), m = "recording", c === void 0 ? f.start() : f.start(c));
40
+ },
41
+ pause() {
42
+ m === "recording" && (h += Date.now() - g, m = "paused", f.pause());
43
+ },
44
+ resume() {
45
+ m === "paused" && (g = Date.now(), m = "recording", f.resume());
46
+ },
47
+ stop() {
48
+ return m === "idle" || m === "stopped" ? Promise.resolve({
49
+ blob: new Blob([], { type: d }),
50
+ mimeType: d,
51
+ durationMs: 0
52
+ }) : new Promise((e) => {
53
+ _ = e, f.stop();
54
+ });
55
+ },
56
+ cancel() {
57
+ m === "idle" || m === "stopped" || (v = !0, _ = null, f.stop());
58
+ }
59
+ };
60
+ }
61
+ //#endregion
62
+ export { t as createMediaRecorder, e as pickRecordingMimeType };
63
+
64
+ //# sourceMappingURL=media-recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-recorder.js","names":[],"sources":["../../src/capture/media-recorder.ts"],"sourcesContent":["/** Lifecycle of a recording. */\nexport type MediaRecorderStatus = \"idle\" | \"recording\" | \"paused\" | \"stopped\";\n\n/** What a track carries — used only to word the errors this engine throws. */\nexport type MediaRecordingKind = \"audio\" | \"video\";\n\n/** A finished recording, whatever it was made of. */\nexport interface MediaRecording {\n /** The bytes. Wrap with `useObjectUrl` to play it, or POST it as-is. */\n blob: Blob;\n /** What the browser actually produced — not necessarily what you asked for. */\n mimeType: string;\n /** Recorded length, excluding time spent paused. */\n durationMs: number;\n}\n\n/** Options for {@link createMediaRecorder}. */\nexport interface MediaRecordingOptions {\n /**\n * Container candidates, best first. Required: this engine has no opinion about\n * codecs — the audio and video wrappers own that list.\n */\n candidates: readonly string[];\n /** Wording for the thrown messages (\"cannot record any supported *video* container\"). */\n kind: MediaRecordingKind;\n /** Force a container, bypassing the negotiation. Throws when unsupported. */\n mimeType?: string;\n /** Target audio bitrate. */\n audioBitsPerSecond?: number;\n /** Target video bitrate. Ignored by the browser on an audio-only stream. */\n videoBitsPerSecond?: number;\n /**\n * Emit a chunk every N ms through `onChunk`, for streaming upload.\n *\n * Without it the whole recording is buffered in memory until `stop()`.\n */\n timesliceMs?: number;\n /** Receives each chunk when `timesliceMs` is set. Chunks are **not** independently playable. */\n onChunk?: (chunk: Blob) => void;\n /** Recorder-level failure (device unplugged mid-recording, encoder error). */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative recorder over one `MediaStream`. */\nexport interface MediaRecorderHandle {\n /** Begin recording. No-op when already recording or paused. */\n start: () => void;\n /** Pause. The clock stops; `durationMs` freezes. */\n pause: () => void;\n /** Resume after `pause()`. */\n resume: () => void;\n /** Stop and resolve with the assembled recording. */\n stop: () => Promise<MediaRecording>;\n /** Stop and throw the bytes away. */\n cancel: () => void;\n status: () => MediaRecorderStatus;\n /** Recorded length so far, excluding paused time. */\n durationMs: () => number;\n /** The negotiated container. */\n mimeType: string;\n}\n\n/**\n * First container in `preferred` the browser can actually produce, or `null`.\n *\n * @param preferred - Candidates, best first.\n * @returns A supported MIME type, or `null` when none is.\n */\nexport function pickRecordingMimeType(preferred: readonly string[]): string | null {\n if (typeof MediaRecorder === \"undefined\") return null;\n // Older WebViews ship `MediaRecorder` without the static probe. Assume the first\n // candidate rather than refusing outright — the constructor will tell us.\n if (typeof MediaRecorder.isTypeSupported !== \"function\") return preferred[0] ?? null;\n return preferred.find((type) => MediaRecorder.isTypeSupported(type)) ?? null;\n}\n\n/**\n * Wrap a `MediaStream` in a recorder with a real state machine and an honest clock.\n *\n * This is the engine behind both `createAudioRecorder` and `createVideoRecorder`; the\n * only thing it does not decide is which containers to try, because that is the one\n * part that genuinely differs between the two.\n *\n * Two things `MediaRecorder` does not give you:\n *\n * - **A duration.** It reports none, and the `Blob` has no reliable one either —\n * WebM written by `MediaRecorder` carries no duration in its header, which is why\n * `<audio>`/`<video>` shows `Infinity` for a fresh recording. So the clock is kept\n * here, and it subtracts paused time: a recorder that counts wall-clock through a\n * pause reports a 30-second note as two minutes.\n * - **A promise from `stop()`.** The last chunk arrives *after* `stop()` returns, in\n * a `dataavailable` event that fires before `onstop`. Assembling the blob in\n * `onstop` is the only point where every chunk is in hand.\n *\n * The stream is **not** owned here: `stop()` leaves the device open so a retake does\n * not need a second permission round-trip. Release it with the owning hook's `stop()`.\n *\n * @param stream - A live stream, from `getUserMedia` or `getDisplayMedia`.\n * @param options - See {@link MediaRecordingOptions}.\n * @returns The imperative recorder.\n * @throws When `MediaRecorder` is unavailable, or an explicit `mimeType` is unsupported.\n */\nexport function createMediaRecorder(\n stream: MediaStream,\n options: MediaRecordingOptions,\n): MediaRecorderHandle {\n const {\n candidates,\n kind,\n mimeType: forced,\n audioBitsPerSecond,\n videoBitsPerSecond,\n timesliceMs,\n onChunk,\n onError,\n } = options;\n\n if (typeof MediaRecorder === \"undefined\") {\n throw new Error(\"MediaRecorder is not available in this environment.\");\n }\n const negotiated = forced ?? pickRecordingMimeType(candidates);\n if (negotiated === null) {\n throw new Error(`This browser cannot record any supported ${kind} container.`);\n }\n if (\n forced !== undefined &&\n typeof MediaRecorder.isTypeSupported === \"function\" &&\n !MediaRecorder.isTypeSupported(forced)\n ) {\n throw new Error(`This browser cannot record \"${forced}\".`);\n }\n\n const recorder = new MediaRecorder(stream, {\n mimeType: negotiated,\n ...(audioBitsPerSecond !== undefined ? { audioBitsPerSecond } : {}),\n ...(videoBitsPerSecond !== undefined ? { videoBitsPerSecond } : {}),\n });\n\n let chunks: Blob[] = [];\n let status: MediaRecorderStatus = \"idle\";\n let accumulatedMs = 0;\n let segmentStart = 0;\n let settle: ((recording: MediaRecording) => void) | null = null;\n let discard = false;\n\n const elapsed = (): number =>\n accumulatedMs + (status === \"recording\" ? Date.now() - segmentStart : 0);\n\n recorder.ondataavailable = (event: BlobEvent): void => {\n if (event.data.size === 0) return;\n if (discard) return;\n chunks.push(event.data);\n onChunk?.(event.data);\n };\n\n recorder.onerror = (event: Event): void => {\n onError?.((event as unknown as { error?: unknown }).error ?? event);\n };\n\n recorder.onstop = (): void => {\n const durationMs = elapsed();\n status = \"stopped\";\n const resolve = settle;\n settle = null;\n if (discard) {\n chunks = [];\n return;\n }\n // `recorder.mimeType` is the source of truth: a browser handed\n // `video/webm;codecs=vp9,opus` may report plain `video/webm` back.\n const type = recorder.mimeType || negotiated;\n resolve?.({ blob: new Blob(chunks, { type }), mimeType: type, durationMs });\n chunks = [];\n };\n\n return {\n mimeType: negotiated,\n status: () => status,\n durationMs: elapsed,\n\n start(): void {\n if (status === \"recording\" || status === \"paused\") return;\n chunks = [];\n accumulatedMs = 0;\n discard = false;\n segmentStart = Date.now();\n status = \"recording\";\n if (timesliceMs !== undefined) recorder.start(timesliceMs);\n else recorder.start();\n },\n\n pause(): void {\n if (status !== \"recording\") return;\n accumulatedMs += Date.now() - segmentStart;\n status = \"paused\";\n recorder.pause();\n },\n\n resume(): void {\n if (status !== \"paused\") return;\n segmentStart = Date.now();\n status = \"recording\";\n recorder.resume();\n },\n\n stop(): Promise<MediaRecording> {\n if (status === \"idle\" || status === \"stopped\") {\n return Promise.resolve({\n blob: new Blob([], { type: negotiated }),\n mimeType: negotiated,\n durationMs: 0,\n });\n }\n // Stopping while paused needs no clock fix-up: `pause()` already folded\n // the last segment into `accumulatedMs`, and `elapsed()` adds nothing\n // while the status is not `\"recording\"`.\n return new Promise<MediaRecording>((resolve) => {\n settle = resolve;\n recorder.stop();\n });\n },\n\n cancel(): void {\n if (status === \"idle\" || status === \"stopped\") return;\n discard = true;\n settle = null;\n recorder.stop();\n },\n };\n}\n"],"mappings":";AAoEA,SAAgB,EAAsB,GAA6C;CAK/E,OAJI,OAAO,gBAAkB,MAAoB,OAG7C,OAAO,cAAc,mBAAoB,aACtC,EAAU,MAAM,MAAS,cAAc,gBAAgB,CAAI,CAAC,KAAK,OADR,EAAU,MAAM;AAEpF;AA4BA,SAAgB,EACZ,GACA,GACmB;CACnB,IAAM,EACF,eACA,SACA,UAAU,GACV,uBACA,uBACA,gBACA,YACA,eACA;CAEJ,IAAI,OAAO,gBAAkB,KACzB,MAAU,MAAM,qDAAqD;CAEzE,IAAM,IAAa,KAAU,EAAsB,CAAU;CAC7D,IAAI,MAAe,MACf,MAAU,MAAM,4CAA4C,EAAK,YAAY;CAEjF,IACI,MAAW,KAAA,KACX,OAAO,cAAc,mBAAoB,cACzC,CAAC,cAAc,gBAAgB,CAAM,GAErC,MAAU,MAAM,+BAA+B,EAAO,GAAG;CAG7D,IAAM,IAAW,IAAI,cAAc,GAAQ;EACvC,UAAU;EACV,GAAI,MAAuB,KAAA,IAAqC,CAAC,IAA1B,EAAE,sBAAmB;EAC5D,GAAI,MAAuB,KAAA,IAAqC,CAAC,IAA1B,EAAE,sBAAmB;CAChE,CAAC,GAEG,IAAiB,CAAC,GAClB,IAA8B,QAC9B,IAAgB,GAChB,IAAe,GACf,IAAuD,MACvD,IAAU,IAER,UACF,KAAiB,MAAW,cAAc,KAAK,IAAI,IAAI,IAAe;CA6B1E,OA3BA,EAAS,mBAAmB,MAA2B;EAC/C,EAAM,KAAK,SAAS,MACpB,MACJ,EAAO,KAAK,EAAM,IAAI,GACtB,IAAU,EAAM,IAAI;CACxB,GAEA,EAAS,WAAW,MAAuB;EACvC,IAAW,EAAyC,SAAS,CAAK;CACtE,GAEA,EAAS,eAAqB;EAC1B,IAAM,IAAa,EAAQ;EAC3B,IAAS;EACT,IAAM,IAAU;EAEhB,IADA,IAAS,MACL,GAAS;GACT,IAAS,CAAC;GACV;EACJ;EAGA,IAAM,IAAO,EAAS,YAAY;EAElC,AADA,IAAU;GAAE,MAAM,IAAI,KAAK,GAAQ,EAAE,QAAK,CAAC;GAAG,UAAU;GAAM;EAAW,CAAC,GAC1E,IAAS,CAAC;CACd,GAEO;EACH,UAAU;EACV,cAAc;EACd,YAAY;EAEZ,QAAc;GACN,MAAW,eAAe,MAAW,aACzC,IAAS,CAAC,GACV,IAAgB,GAChB,IAAU,IACV,IAAe,KAAK,IAAI,GACxB,IAAS,aACL,MAAgB,KAAA,IACf,EAAS,MAAM,IADW,EAAS,MAAM,CAAW;EAE7D;EAEA,QAAc;GACN,MAAW,gBACf,KAAiB,KAAK,IAAI,IAAI,GAC9B,IAAS,UACT,EAAS,MAAM;EACnB;EAEA,SAAe;GACP,MAAW,aACf,IAAe,KAAK,IAAI,GACxB,IAAS,aACT,EAAS,OAAO;EACpB;EAEA,OAAgC;GAW5B,OAVI,MAAW,UAAU,MAAW,YACzB,QAAQ,QAAQ;IACnB,MAAM,IAAI,KAAK,CAAC,GAAG,EAAE,MAAM,EAAW,CAAC;IACvC,UAAU;IACV,YAAY;GAChB,CAAC,IAKE,IAAI,SAAyB,MAAY;IAE5C,AADA,IAAS,GACT,EAAS,KAAK;GAClB,CAAC;EACL;EAEA,SAAe;GACP,MAAW,UAAU,MAAW,cACpC,IAAU,IACV,IAAS,MACT,EAAS,KAAK;EAClB;CACJ;AACJ"}
@@ -0,0 +1,2 @@
1
+ const e=require("../hooks/use-stable-callback.cjs"),t=require("../vision/use-camera-stream.cjs"),n=require("./barcode.cjs"),r=require("./use-torch.cjs");let i=require("react");function a(e){return e.readyState>=2&&e.videoWidth>0&&e.videoHeight>0}function o(o={}){let{formats:s=n.DEFAULT_BARCODE_FORMATS,onScan:c,intervalMs:l=200,repeatDelayMs:u=2500,paused:d=!1,detector:f,constraints:p,onError:m}=o,[h,g]=(0,i.useState)(()=>f!==void 0||n.isBarcodeDetectionSupported()),_=t.useCameraStream({constraints:p,enabled:h}),v=r.useTorch(_.stream),[y,b]=(0,i.useState)(f===void 0?[]:s),[x,S]=(0,i.useState)(!1),[C,w]=(0,i.useState)(null),T=(0,i.useRef)(null),E=(0,i.useRef)(null),D=(0,i.useRef)(0),O=e.useStableCallback(e=>c?.(e)),k=e.useStableCallback(e=>m?.(e));return(0,i.useEffect)(()=>{if(f){T.current=f,g(!0),b(s);return}if(!n.isBarcodeDetectionSupported()){T.current=null,g(!1),b([]);return}let e=!1;return n.getSupportedBarcodeFormats().then(t=>{if(e)return;let r=t.length===0?s:s.filter(e=>t.includes(e)),i=r.length>0?n.createBarcodeDetector(r):null;T.current=i,b(i?r:[]),g(i!==null)}),()=>{e=!0}},[f,s.join(`,`)]),(0,i.useEffect)(()=>{if(!h||d||_.status!==`ready`){S(!1);return}let e=!1,t;S(!0);let r=e=>{let t=Date.now();e.rawValue===E.current&&t-D.current<u||(E.current=e.rawValue,D.current=t,w(e),O(e))},i=async()=>{let t=_.videoRef.current,i=T.current;if(!(!t||!i||!a(t)))try{let a=await i.detect(t);if(e)return;for(let e of a){let t=n.normalizeBarcode(e);t.rawValue!==``&&r(t)}}catch(t){e||k(t)}},o=()=>{i().finally(()=>{e||(t=setTimeout(o,l))})};return o(),()=>{e=!0,t!==void 0&&clearTimeout(t),S(!1)}},[h,d,_.status,_.videoRef,l,u,O,k]),{videoRef:_.videoRef,status:_.status,error:_.error,supported:h,formats:y,scanning:x,result:C,torch:v,retry:_.retry}}exports.useBarcodeScanner=o;
2
+ //# sourceMappingURL=use-barcode-scanner.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-barcode-scanner.cjs","names":[],"sources":["../../src/capture/use-barcode-scanner.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\n\nimport { useStableCallback } from \"@/hooks/use-stable-callback\";\nimport {\n useCameraStream,\n type CameraStreamError,\n type CameraStreamStatus,\n} from \"@/vision/use-camera-stream\";\n\nimport {\n createBarcodeDetector,\n getSupportedBarcodeFormats,\n isBarcodeDetectionSupported,\n normalizeBarcode,\n DEFAULT_BARCODE_FORMATS,\n type BarcodeDetectorLike,\n type BarcodeFormat,\n type BarcodeScanResult,\n} from \"./barcode\";\nimport { useTorch, type UseTorchResult } from \"./use-torch\";\n\n/** Options for {@link useBarcodeScanner}. */\nexport interface UseBarcodeScannerOptions {\n /** Symbologies to look for. Defaults to {@link DEFAULT_BARCODE_FORMATS}. */\n formats?: readonly BarcodeFormat[];\n /** Called for every accepted read — that is, after repeat suppression. */\n onScan?: (result: BarcodeScanResult) => void;\n /**\n * How often a frame is examined, in ms. Default 200.\n *\n * Not `requestAnimationFrame`: decoding is 10–40 ms of main-thread work on a\n * phone, so running it per frame competes with the preview it is reading from and\n * makes the video stutter. Five looks per second is faster than a human can aim.\n */\n intervalMs?: number;\n /**\n * Ignore the **same** value again for this long, in ms. Default 2500.\n *\n * A symbol stays in frame for as long as the user holds the camera there, so a\n * scanner without this fires the same code five times a second — which, wired to\n * \"add item to cart\", is a bug the user pays for. A *different* value is never\n * suppressed.\n */\n repeatDelayMs?: number;\n /** Stop looking without releasing the camera — a confirmation sheet is open. */\n paused?: boolean;\n /**\n * A decoder to use instead of the native one.\n *\n * The way to support Safari and Firefox: hand in a polyfill and everything else\n * here works unchanged. See {@link isBarcodeDetectionSupported} for why the SDK\n * does not bundle one.\n */\n detector?: BarcodeDetectorLike;\n /** Camera constraints, forwarded to `useCameraStream`. Defaults to the rear camera. */\n constraints?: MediaStreamConstraints;\n /**\n * A frame the engine refused to decode.\n *\n * Not \"nothing found\" — that resolves to an empty list and is the normal case.\n * This is the engine itself failing, which the loop survives because it is\n * usually transient (a frame arriving between two resolutions).\n */\n onError?: (error: unknown) => void;\n}\n\n/** Value returned by {@link useBarcodeScanner}. */\nexport interface UseBarcodeScannerResult {\n /** Attach to a `<video ref={…} muted playsInline />`. */\n videoRef: RefObject<HTMLVideoElement | null>;\n /** Camera lifecycle. `\"ready\"` means the preview is running. */\n status: CameraStreamStatus;\n /** Classified camera error, or `null`. */\n error: CameraStreamError | null;\n /** `false` when there is no decoder — no native API and none injected. */\n supported: boolean;\n /**\n * Formats actually in use: the requested ones intersected with what the engine\n * reports. Empty while the probe is in flight, or when nothing matched.\n */\n formats: readonly BarcodeFormat[];\n /** Whether the detect loop is running right now. */\n scanning: boolean;\n /** The most recent accepted read, or `null`. */\n result: BarcodeScanResult | null;\n /** The LED torch of this camera, when it has one. */\n torch: UseTorchResult;\n /** Re-open the camera after an error (the user changed the permission). */\n retry: () => void;\n}\n\n/** A video is decodable once it has data for the current frame and a real size. */\nfunction frameIsReady(video: HTMLVideoElement): boolean {\n return video.readyState >= 2 && video.videoWidth > 0 && video.videoHeight > 0;\n}\n\n/**\n * Read barcodes and QR codes from the camera.\n *\n * The camera and its classified errors come from `useCameraStream`, so this hook is\n * only the decoding half: it drives a `BarcodeDetector` over the preview on an\n * interval, suppresses the same value repeating, and exposes the torch.\n *\n * **Mounting this opens the camera.** It inherits that from `useCameraStream`, which\n * acquires on mount — so mount it *after* the user asks to scan (a button that reveals\n * the scanner), never on a page that merely contains one. A permission prompt nobody\n * provoked is the most reliable way to earn a permanent block, after which\n * `getUserMedia` rejects without ever prompting again.\n *\n * `supported` deserves a branch in the UI, not an assertion: `BarcodeDetector` is\n * Chromium-only and missing on Windows/Linux desktop, Firefox and everything on iOS.\n * Inject a `detector` to cover those, or tell the user to type the code.\n *\n * @param options - See {@link UseBarcodeScannerOptions}.\n * @returns The camera plumbing plus the scan state.\n *\n * @example\n * const scanner = useBarcodeScanner({\n * formats: [\"ean_13\"],\n * onScan: ({ rawValue }) => addToCart(rawValue),\n * });\n * return <video ref={scanner.videoRef} muted playsInline />;\n */\nexport function useBarcodeScanner(options: UseBarcodeScannerOptions = {}): UseBarcodeScannerResult {\n const {\n formats: requested = DEFAULT_BARCODE_FORMATS,\n onScan,\n intervalMs = 200,\n repeatDelayMs = 2500,\n paused = false,\n detector: injected,\n constraints,\n onError,\n } = options;\n\n const [supported, setSupported] = useState(\n () => injected !== undefined || isBarcodeDetectionSupported(),\n );\n\n /**\n * No decoder, no camera.\n *\n * Opening the camera only to report \"this browser cannot decode barcodes\" spends a\n * permission prompt on nothing — and a refusal is permanent, so it also spends the\n * *next* feature that needs the camera.\n */\n const camera = useCameraStream({ constraints, enabled: supported });\n const torch = useTorch(camera.stream);\n\n const [formats, setFormats] = useState<readonly BarcodeFormat[]>(\n injected !== undefined ? requested : [],\n );\n const [scanning, setScanning] = useState(false);\n const [result, setResult] = useState<BarcodeScanResult | null>(null);\n\n const detectorRef = useRef<BarcodeDetectorLike | null>(null);\n const lastValue = useRef<string | null>(null);\n const lastAt = useRef(0);\n\n const emitScan = useStableCallback((scan: BarcodeScanResult) => onScan?.(scan));\n const emitError = useStableCallback((error: unknown) => onError?.(error));\n\n const requestedKey = requested.join(\",\");\n\n /**\n * Resolve which formats the engine will take, then build the detector.\n *\n * The intersection is not defensive coding: `new BarcodeDetector({ formats })`\n * throws `NotSupportedError` when any entry is unknown to the platform decoder, and\n * that list differs between two Chromium builds on two operating systems. Asking\n * for the intersection is the only way one call site works everywhere.\n */\n useEffect(() => {\n if (injected) {\n detectorRef.current = injected;\n setSupported(true);\n setFormats(requested);\n return;\n }\n if (!isBarcodeDetectionSupported()) {\n detectorRef.current = null;\n setSupported(false);\n setFormats([]);\n return;\n }\n let cancelled = false;\n void getSupportedBarcodeFormats().then((available) => {\n if (cancelled) return;\n const usable =\n available.length === 0\n ? requested\n : requested.filter((format) => available.includes(format));\n const detector = usable.length > 0 ? createBarcodeDetector(usable) : null;\n detectorRef.current = detector;\n setFormats(detector ? usable : []);\n setSupported(detector !== null);\n });\n return () => {\n cancelled = true;\n };\n // `requestedKey` stands in for the array identity, so a caller passing an\n // inline `formats={[\"ean_13\"]}` does not rebuild the detector every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, requestedKey]);\n\n /**\n * Look at a frame every `intervalMs`, and never overlap two looks.\n *\n * The loop re-arms itself *after* each `detect()` settles rather than running on a\n * fixed `setInterval`: decoding sometimes takes longer than the interval, and an\n * interval would then queue calls faster than the engine drains them until the tab\n * is unusable.\n */\n useEffect(() => {\n if (!supported || paused || camera.status !== \"ready\") {\n setScanning(false);\n return;\n }\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n setScanning(true);\n\n const accept = (scan: BarcodeScanResult): void => {\n const now = Date.now();\n const isRepeat =\n scan.rawValue === lastValue.current && now - lastAt.current < repeatDelayMs;\n if (isRepeat) return;\n lastValue.current = scan.rawValue;\n lastAt.current = now;\n setResult(scan);\n emitScan(scan);\n };\n\n const look = async (): Promise<void> => {\n const video = camera.videoRef.current;\n const detector = detectorRef.current;\n if (!video || !detector || !frameIsReady(video)) return;\n try {\n const found = await detector.detect(video);\n if (stopped) return;\n for (const raw of found) {\n const scan = normalizeBarcode(raw);\n if (scan.rawValue !== \"\") accept(scan);\n }\n } catch (error) {\n if (!stopped) emitError(error);\n }\n };\n\n const tick = (): void => {\n void look().finally(() => {\n if (!stopped) timer = setTimeout(tick, intervalMs);\n });\n };\n tick();\n\n return () => {\n stopped = true;\n if (timer !== undefined) clearTimeout(timer);\n setScanning(false);\n };\n }, [\n supported,\n paused,\n camera.status,\n camera.videoRef,\n intervalMs,\n repeatDelayMs,\n emitScan,\n emitError,\n ]);\n\n return {\n videoRef: camera.videoRef,\n status: camera.status,\n error: camera.error,\n supported,\n formats,\n scanning,\n result,\n torch,\n retry: camera.retry,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import { useStableCallback as e } from "../hooks/use-stable-callback.js";
2
+ import { useCameraStream as t } from "../vision/use-camera-stream.js";
3
+ import { DEFAULT_BARCODE_FORMATS as n, createBarcodeDetector as r, getSupportedBarcodeFormats as i, isBarcodeDetectionSupported as a, normalizeBarcode as o } from "./barcode.js";
4
+ import { useTorch as s } from "./use-torch.js";
5
+ import { useEffect as c, useRef as l, useState as u } from "react";
6
+ //#region src/capture/use-barcode-scanner.ts
7
+ function d(e) {
8
+ return e.readyState >= 2 && e.videoWidth > 0 && e.videoHeight > 0;
9
+ }
10
+ function f(f = {}) {
11
+ let { formats: p = n, onScan: m, intervalMs: h = 200, repeatDelayMs: g = 2500, paused: _ = !1, detector: v, constraints: y, onError: b } = f, [x, S] = u(() => v !== void 0 || a()), C = t({
12
+ constraints: y,
13
+ enabled: x
14
+ }), w = s(C.stream), [T, E] = u(v === void 0 ? [] : p), [D, O] = u(!1), [k, A] = u(null), j = l(null), M = l(null), N = l(0), P = e((e) => m?.(e)), F = e((e) => b?.(e));
15
+ return c(() => {
16
+ if (v) {
17
+ j.current = v, S(!0), E(p);
18
+ return;
19
+ }
20
+ if (!a()) {
21
+ j.current = null, S(!1), E([]);
22
+ return;
23
+ }
24
+ let e = !1;
25
+ return i().then((t) => {
26
+ if (e) return;
27
+ let n = t.length === 0 ? p : p.filter((e) => t.includes(e)), i = n.length > 0 ? r(n) : null;
28
+ j.current = i, E(i ? n : []), S(i !== null);
29
+ }), () => {
30
+ e = !0;
31
+ };
32
+ }, [v, p.join(",")]), c(() => {
33
+ if (!x || _ || C.status !== "ready") {
34
+ O(!1);
35
+ return;
36
+ }
37
+ let e = !1, t;
38
+ O(!0);
39
+ let n = (e) => {
40
+ let t = Date.now();
41
+ e.rawValue === M.current && t - N.current < g || (M.current = e.rawValue, N.current = t, A(e), P(e));
42
+ }, r = async () => {
43
+ let t = C.videoRef.current, r = j.current;
44
+ if (!(!t || !r || !d(t))) try {
45
+ let i = await r.detect(t);
46
+ if (e) return;
47
+ for (let e of i) {
48
+ let t = o(e);
49
+ t.rawValue !== "" && n(t);
50
+ }
51
+ } catch (t) {
52
+ e || F(t);
53
+ }
54
+ }, i = () => {
55
+ r().finally(() => {
56
+ e || (t = setTimeout(i, h));
57
+ });
58
+ };
59
+ return i(), () => {
60
+ e = !0, t !== void 0 && clearTimeout(t), O(!1);
61
+ };
62
+ }, [
63
+ x,
64
+ _,
65
+ C.status,
66
+ C.videoRef,
67
+ h,
68
+ g,
69
+ P,
70
+ F
71
+ ]), {
72
+ videoRef: C.videoRef,
73
+ status: C.status,
74
+ error: C.error,
75
+ supported: x,
76
+ formats: T,
77
+ scanning: D,
78
+ result: k,
79
+ torch: w,
80
+ retry: C.retry
81
+ };
82
+ }
83
+ //#endregion
84
+ export { f as useBarcodeScanner };
85
+
86
+ //# sourceMappingURL=use-barcode-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-barcode-scanner.js","names":[],"sources":["../../src/capture/use-barcode-scanner.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\n\nimport { useStableCallback } from \"@/hooks/use-stable-callback\";\nimport {\n useCameraStream,\n type CameraStreamError,\n type CameraStreamStatus,\n} from \"@/vision/use-camera-stream\";\n\nimport {\n createBarcodeDetector,\n getSupportedBarcodeFormats,\n isBarcodeDetectionSupported,\n normalizeBarcode,\n DEFAULT_BARCODE_FORMATS,\n type BarcodeDetectorLike,\n type BarcodeFormat,\n type BarcodeScanResult,\n} from \"./barcode\";\nimport { useTorch, type UseTorchResult } from \"./use-torch\";\n\n/** Options for {@link useBarcodeScanner}. */\nexport interface UseBarcodeScannerOptions {\n /** Symbologies to look for. Defaults to {@link DEFAULT_BARCODE_FORMATS}. */\n formats?: readonly BarcodeFormat[];\n /** Called for every accepted read — that is, after repeat suppression. */\n onScan?: (result: BarcodeScanResult) => void;\n /**\n * How often a frame is examined, in ms. Default 200.\n *\n * Not `requestAnimationFrame`: decoding is 10–40 ms of main-thread work on a\n * phone, so running it per frame competes with the preview it is reading from and\n * makes the video stutter. Five looks per second is faster than a human can aim.\n */\n intervalMs?: number;\n /**\n * Ignore the **same** value again for this long, in ms. Default 2500.\n *\n * A symbol stays in frame for as long as the user holds the camera there, so a\n * scanner without this fires the same code five times a second — which, wired to\n * \"add item to cart\", is a bug the user pays for. A *different* value is never\n * suppressed.\n */\n repeatDelayMs?: number;\n /** Stop looking without releasing the camera — a confirmation sheet is open. */\n paused?: boolean;\n /**\n * A decoder to use instead of the native one.\n *\n * The way to support Safari and Firefox: hand in a polyfill and everything else\n * here works unchanged. See {@link isBarcodeDetectionSupported} for why the SDK\n * does not bundle one.\n */\n detector?: BarcodeDetectorLike;\n /** Camera constraints, forwarded to `useCameraStream`. Defaults to the rear camera. */\n constraints?: MediaStreamConstraints;\n /**\n * A frame the engine refused to decode.\n *\n * Not \"nothing found\" — that resolves to an empty list and is the normal case.\n * This is the engine itself failing, which the loop survives because it is\n * usually transient (a frame arriving between two resolutions).\n */\n onError?: (error: unknown) => void;\n}\n\n/** Value returned by {@link useBarcodeScanner}. */\nexport interface UseBarcodeScannerResult {\n /** Attach to a `<video ref={…} muted playsInline />`. */\n videoRef: RefObject<HTMLVideoElement | null>;\n /** Camera lifecycle. `\"ready\"` means the preview is running. */\n status: CameraStreamStatus;\n /** Classified camera error, or `null`. */\n error: CameraStreamError | null;\n /** `false` when there is no decoder — no native API and none injected. */\n supported: boolean;\n /**\n * Formats actually in use: the requested ones intersected with what the engine\n * reports. Empty while the probe is in flight, or when nothing matched.\n */\n formats: readonly BarcodeFormat[];\n /** Whether the detect loop is running right now. */\n scanning: boolean;\n /** The most recent accepted read, or `null`. */\n result: BarcodeScanResult | null;\n /** The LED torch of this camera, when it has one. */\n torch: UseTorchResult;\n /** Re-open the camera after an error (the user changed the permission). */\n retry: () => void;\n}\n\n/** A video is decodable once it has data for the current frame and a real size. */\nfunction frameIsReady(video: HTMLVideoElement): boolean {\n return video.readyState >= 2 && video.videoWidth > 0 && video.videoHeight > 0;\n}\n\n/**\n * Read barcodes and QR codes from the camera.\n *\n * The camera and its classified errors come from `useCameraStream`, so this hook is\n * only the decoding half: it drives a `BarcodeDetector` over the preview on an\n * interval, suppresses the same value repeating, and exposes the torch.\n *\n * **Mounting this opens the camera.** It inherits that from `useCameraStream`, which\n * acquires on mount — so mount it *after* the user asks to scan (a button that reveals\n * the scanner), never on a page that merely contains one. A permission prompt nobody\n * provoked is the most reliable way to earn a permanent block, after which\n * `getUserMedia` rejects without ever prompting again.\n *\n * `supported` deserves a branch in the UI, not an assertion: `BarcodeDetector` is\n * Chromium-only and missing on Windows/Linux desktop, Firefox and everything on iOS.\n * Inject a `detector` to cover those, or tell the user to type the code.\n *\n * @param options - See {@link UseBarcodeScannerOptions}.\n * @returns The camera plumbing plus the scan state.\n *\n * @example\n * const scanner = useBarcodeScanner({\n * formats: [\"ean_13\"],\n * onScan: ({ rawValue }) => addToCart(rawValue),\n * });\n * return <video ref={scanner.videoRef} muted playsInline />;\n */\nexport function useBarcodeScanner(options: UseBarcodeScannerOptions = {}): UseBarcodeScannerResult {\n const {\n formats: requested = DEFAULT_BARCODE_FORMATS,\n onScan,\n intervalMs = 200,\n repeatDelayMs = 2500,\n paused = false,\n detector: injected,\n constraints,\n onError,\n } = options;\n\n const [supported, setSupported] = useState(\n () => injected !== undefined || isBarcodeDetectionSupported(),\n );\n\n /**\n * No decoder, no camera.\n *\n * Opening the camera only to report \"this browser cannot decode barcodes\" spends a\n * permission prompt on nothing — and a refusal is permanent, so it also spends the\n * *next* feature that needs the camera.\n */\n const camera = useCameraStream({ constraints, enabled: supported });\n const torch = useTorch(camera.stream);\n\n const [formats, setFormats] = useState<readonly BarcodeFormat[]>(\n injected !== undefined ? requested : [],\n );\n const [scanning, setScanning] = useState(false);\n const [result, setResult] = useState<BarcodeScanResult | null>(null);\n\n const detectorRef = useRef<BarcodeDetectorLike | null>(null);\n const lastValue = useRef<string | null>(null);\n const lastAt = useRef(0);\n\n const emitScan = useStableCallback((scan: BarcodeScanResult) => onScan?.(scan));\n const emitError = useStableCallback((error: unknown) => onError?.(error));\n\n const requestedKey = requested.join(\",\");\n\n /**\n * Resolve which formats the engine will take, then build the detector.\n *\n * The intersection is not defensive coding: `new BarcodeDetector({ formats })`\n * throws `NotSupportedError` when any entry is unknown to the platform decoder, and\n * that list differs between two Chromium builds on two operating systems. Asking\n * for the intersection is the only way one call site works everywhere.\n */\n useEffect(() => {\n if (injected) {\n detectorRef.current = injected;\n setSupported(true);\n setFormats(requested);\n return;\n }\n if (!isBarcodeDetectionSupported()) {\n detectorRef.current = null;\n setSupported(false);\n setFormats([]);\n return;\n }\n let cancelled = false;\n void getSupportedBarcodeFormats().then((available) => {\n if (cancelled) return;\n const usable =\n available.length === 0\n ? requested\n : requested.filter((format) => available.includes(format));\n const detector = usable.length > 0 ? createBarcodeDetector(usable) : null;\n detectorRef.current = detector;\n setFormats(detector ? usable : []);\n setSupported(detector !== null);\n });\n return () => {\n cancelled = true;\n };\n // `requestedKey` stands in for the array identity, so a caller passing an\n // inline `formats={[\"ean_13\"]}` does not rebuild the detector every render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, requestedKey]);\n\n /**\n * Look at a frame every `intervalMs`, and never overlap two looks.\n *\n * The loop re-arms itself *after* each `detect()` settles rather than running on a\n * fixed `setInterval`: decoding sometimes takes longer than the interval, and an\n * interval would then queue calls faster than the engine drains them until the tab\n * is unusable.\n */\n useEffect(() => {\n if (!supported || paused || camera.status !== \"ready\") {\n setScanning(false);\n return;\n }\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n setScanning(true);\n\n const accept = (scan: BarcodeScanResult): void => {\n const now = Date.now();\n const isRepeat =\n scan.rawValue === lastValue.current && now - lastAt.current < repeatDelayMs;\n if (isRepeat) return;\n lastValue.current = scan.rawValue;\n lastAt.current = now;\n setResult(scan);\n emitScan(scan);\n };\n\n const look = async (): Promise<void> => {\n const video = camera.videoRef.current;\n const detector = detectorRef.current;\n if (!video || !detector || !frameIsReady(video)) return;\n try {\n const found = await detector.detect(video);\n if (stopped) return;\n for (const raw of found) {\n const scan = normalizeBarcode(raw);\n if (scan.rawValue !== \"\") accept(scan);\n }\n } catch (error) {\n if (!stopped) emitError(error);\n }\n };\n\n const tick = (): void => {\n void look().finally(() => {\n if (!stopped) timer = setTimeout(tick, intervalMs);\n });\n };\n tick();\n\n return () => {\n stopped = true;\n if (timer !== undefined) clearTimeout(timer);\n setScanning(false);\n };\n }, [\n supported,\n paused,\n camera.status,\n camera.videoRef,\n intervalMs,\n repeatDelayMs,\n emitScan,\n emitError,\n ]);\n\n return {\n videoRef: camera.videoRef,\n status: camera.status,\n error: camera.error,\n supported,\n formats,\n scanning,\n result,\n torch,\n retry: camera.retry,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../hooks/use-stable-callback.cjs"),t=require("../audio/media-access.cjs");let n=require("react");function r(){return typeof navigator<`u`&&navigator.mediaDevices!==void 0&&typeof navigator.mediaDevices.getDisplayMedia==`function`}function i(e){return e instanceof DOMException&&(e.name===`NotAllowedError`||e.name===`AbortError`)}function a(a={}){let{audio:o=!1,displaySurface:s,preferCurrentTab:c,selfBrowserSurface:l,surfaceSwitching:u,systemAudio:d,options:f,onEnded:p,onCancelled:m}=a,[h,g]=(0,n.useState)(`idle`),[_,v]=(0,n.useState)(null),[y,b]=(0,n.useState)(null),[x,S]=(0,n.useState)(null),[C,w]=(0,n.useState)(!1),[T]=(0,n.useState)(r),E=(0,n.useRef)(null),D=(0,n.useRef)(null),O=(0,n.useRef)(0),k=e.useStableCallback(()=>p?.()),A=e.useStableCallback(e=>m?.(e)),j=(0,n.useCallback)(()=>{D.current?.(),D.current=null,E.current?.getTracks().forEach(e=>e.stop()),E.current=null},[]),M=(0,n.useCallback)(()=>{v(null),S(null),w(!1)},[]),N=(0,n.useCallback)(()=>{O.current+=1,j(),M(),g(`idle`),b(null)},[j,M]),P=(0,n.useCallback)(()=>{if(E.current)return;let e=O.current+=1;if(g(`requesting`),b(null),!r()){b(t.missingCaptureApiError(`screen`)),g(`error`);return}let n=f??{video:s?{displaySurface:s}:{},audio:o,...c===void 0?{}:{preferCurrentTab:c},...l===void 0?{}:{selfBrowserSurface:l},...u===void 0?{}:{surfaceSwitching:u},...d===void 0?{}:{systemAudio:d}};navigator.mediaDevices.getDisplayMedia(n).then(t=>{if(e!==O.current){t.getTracks().forEach(e=>e.stop());return}E.current=t;let n=t.getVideoTracks()[0],r=()=>{e===O.current&&(j(),M(),g(`idle`),k())};n?.addEventListener(`ended`,r),D.current=()=>n?.removeEventListener(`ended`,r),v(t),S(n?.getSettings().displaySurface??null),w(t.getAudioTracks().length>0),g(`sharing`)}).catch(n=>{if(e===O.current){if(i(n)){g(`idle`),A(n);return}b(t.classifyMediaError(n,`screen`)),g(`error`)}})},[o,s,c,l,u,d,f,j,M,k,A]);return(0,n.useEffect)(()=>j,[j]),{status:h,stream:_,error:y,surface:x,hasAudio:C,supported:T,start:P,stop:N}}exports.isScreenCaptureSupported=r,exports.useScreenCapture=a;
2
+ //# sourceMappingURL=use-screen-capture.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-screen-capture.cjs","names":[],"sources":["../../src/capture/use-screen-capture.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport {\n classifyMediaError,\n missingCaptureApiError,\n type MediaAccessError,\n} from \"@/audio/media-access\";\nimport { useStableCallback } from \"@/hooks/use-stable-callback\";\n\n/** Lifecycle of a screen share. */\nexport type ScreenCaptureStatus = \"idle\" | \"requesting\" | \"sharing\" | \"error\";\n\n/** Which surface to put first in the picker. */\nexport type DisplaySurfaceHint = \"monitor\" | \"window\" | \"browser\";\n\n/**\n * Hints `getDisplayMedia` takes that TypeScript's `DisplayMediaStreamOptions` does not\n * list yet.\n */\ntype DisplayMediaOptionsWithHints = DisplayMediaStreamOptions & {\n preferCurrentTab?: boolean;\n selfBrowserSurface?: \"include\" | \"exclude\";\n surfaceSwitching?: \"include\" | \"exclude\";\n systemAudio?: \"include\" | \"exclude\";\n};\n\n/** `displaySurface` is a constrainable property of a display track, but not in the DOM lib. */\ntype DisplayVideoConstraints = MediaTrackConstraints & { displaySurface?: DisplaySurfaceHint };\n\n/** Options for {@link useScreenCapture}. */\nexport interface UseScreenCaptureOptions {\n /**\n * Capture the tab's audio too. Default `false`.\n *\n * Chromium only offers this for a **tab** — sharing a window or a whole screen\n * yields no audio track no matter what you ask for, and Safari has no display\n * audio at all. Ask for it and check what you got.\n */\n audio?: boolean;\n /**\n * Which surface the picker should offer first — `\"browser\"` is a tab.\n *\n * A hint, never a guarantee: the user can always pick something else, and Firefox\n * ignores it. Read `surface` afterwards to learn what actually happened.\n */\n displaySurface?: DisplaySurfaceHint;\n /**\n * Put *this* tab at the top of the picker. Default `false`.\n *\n * The right setting for \"record what you are seeing right now\" in a support flow.\n * Chromium only.\n */\n preferCurrentTab?: boolean;\n /** Offer this tab in the list at all. `\"exclude\"` prevents the hall-of-mirrors capture. */\n selfBrowserSurface?: \"include\" | \"exclude\";\n /** Let the user switch to a different surface mid-share, without a new prompt. */\n surfaceSwitching?: \"include\" | \"exclude\";\n /** Include the system audio when a whole screen is shared. Chromium, Windows only. */\n systemAudio?: \"include\" | \"exclude\";\n /** Escape hatch: full options, replacing everything above. */\n options?: DisplayMediaStreamOptions;\n /**\n * The user stopped the share from the browser's own bar.\n *\n * The single most important callback here — see the note on\n * {@link useScreenCapture}.\n */\n onEnded?: () => void;\n /**\n * The user dismissed the picker. **Not an error.**\n *\n * Receives the rejection so an app that needs to tell a dismissal from an OS-level\n * block (macOS screen-recording permission) can look at the message. Most should\n * simply return the UI to its previous state.\n */\n onCancelled?: (reason: unknown) => void;\n}\n\n/** Value returned by {@link useScreenCapture}. */\nexport interface UseScreenCaptureResult {\n status: ScreenCaptureStatus;\n /** The live stream, or `null`. Feed it to {@link useVideoRecorder} or a `<video>`. */\n stream: MediaStream | null;\n /** Classified error, or `null`. A cancelled picker leaves this `null`. */\n error: MediaAccessError | null;\n /** What the user actually picked, when the browser reports it. */\n surface: string | null;\n /** Whether the stream carries an audio track — ask, do not assume. */\n hasAudio: boolean;\n /** `false` when `getDisplayMedia` is missing (every browser on iOS, insecure pages). */\n supported: boolean;\n /** Open the picker. Must be called from a user gesture. */\n start: () => void;\n /** Stop sharing from the app side. Fires nothing — you asked for it. */\n stop: () => void;\n}\n\n/** Whether `getDisplayMedia` is reachable at all. */\nexport function isScreenCaptureSupported(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n navigator.mediaDevices !== undefined &&\n typeof navigator.mediaDevices.getDisplayMedia === \"function\"\n );\n}\n\n/**\n * A rejection from `getDisplayMedia` that means \"the user said no thanks\".\n *\n * There is no distinct exception for a dismissed picker: closing it produces the same\n * `NotAllowedError` as a policy block, and some builds report `AbortError` instead. The\n * useful default is therefore to treat both as a cancellation, because a\n * display-capture prompt is **always** user-initiated — nothing can open it behind\n * their back — so the overwhelmingly likely cause is that they changed their mind, and\n * a red error toast for that punishes them for it. The rejection is handed to\n * `onCancelled` so the rarer causes stay diagnosable.\n */\nfunction isCancellation(err: unknown): boolean {\n return (\n err instanceof DOMException && (err.name === \"NotAllowedError\" || err.name === \"AbortError\")\n );\n}\n\n/**\n * Capture a screen, a window or a tab with `getDisplayMedia`.\n *\n * Three states decide whether this feels right, and two of them are easy to miss:\n *\n * - **The user dismissed the picker.** A rejection, but not a failure. It leaves\n * `error` at `null` and the status back at `\"idle\"`, and calls `onCancelled`.\n * - **The user stopped the share from the browser's own bar.** Nothing in your UI was\n * clicked and no promise rejects — the *only* signal is the video track's `ended`\n * event, so the hook listens for it and clears the stream. Without that listener, an\n * app shows \"gravando\" over a stream that is already dead.\n * - **The share is live.** `surface` says what was picked and `hasAudio` says whether\n * audio actually came along, which is not what you asked for but what you got.\n *\n * The stream is owned here: `stop()` and unmount both release every track. A recorder\n * built on it (`useVideoRecorder`) deliberately does not, so stopping a recording\n * leaves the share running for the next take.\n *\n * @param options - See {@link UseScreenCaptureOptions}.\n * @returns The stream, its status, a classified error and `start`/`stop`.\n *\n * @example\n * const screen = useScreenCapture({ preferCurrentTab: true, onEnded: () => save() });\n * const rec = useVideoRecorder(screen.stream);\n * <button onClick={screen.start}>Compartilhar tela</button>\n */\nexport function useScreenCapture(options: UseScreenCaptureOptions = {}): UseScreenCaptureResult {\n const {\n audio = false,\n displaySurface,\n preferCurrentTab,\n selfBrowserSurface,\n surfaceSwitching,\n systemAudio,\n options: raw,\n onEnded,\n onCancelled,\n } = options;\n\n const [status, setStatus] = useState<ScreenCaptureStatus>(\"idle\");\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [error, setError] = useState<MediaAccessError | null>(null);\n const [surface, setSurface] = useState<string | null>(null);\n const [hasAudio, setHasAudio] = useState(false);\n const [supported] = useState(isScreenCaptureSupported);\n\n const streamRef = useRef<MediaStream | null>(null);\n const detach = useRef<(() => void) | null>(null);\n const generation = useRef(0);\n\n const emitEnded = useStableCallback(() => onEnded?.());\n const emitCancelled = useStableCallback((reason: unknown) => onCancelled?.(reason));\n\n const release = useCallback((): void => {\n detach.current?.();\n detach.current = null;\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n }, []);\n\n const reset = useCallback((): void => {\n setStream(null);\n setSurface(null);\n setHasAudio(false);\n }, []);\n\n const stop = useCallback((): void => {\n generation.current += 1;\n release();\n reset();\n setStatus(\"idle\");\n setError(null);\n }, [release, reset]);\n\n const start = useCallback((): void => {\n if (streamRef.current) return;\n const run = (generation.current += 1);\n setStatus(\"requesting\");\n setError(null);\n\n if (!isScreenCaptureSupported()) {\n setError(missingCaptureApiError(\"screen\"));\n setStatus(\"error\");\n return;\n }\n\n const video: DisplayVideoConstraints = displaySurface ? { displaySurface } : {};\n const request: DisplayMediaOptionsWithHints = raw ?? {\n video,\n audio,\n ...(preferCurrentTab !== undefined ? { preferCurrentTab } : {}),\n ...(selfBrowserSurface !== undefined ? { selfBrowserSurface } : {}),\n ...(surfaceSwitching !== undefined ? { surfaceSwitching } : {}),\n ...(systemAudio !== undefined ? { systemAudio } : {}),\n };\n\n void navigator.mediaDevices\n .getDisplayMedia(request)\n .then((next) => {\n if (run !== generation.current) {\n next.getTracks().forEach((track) => track.stop());\n return;\n }\n streamRef.current = next;\n const track = next.getVideoTracks()[0];\n const ended = (): void => {\n if (run !== generation.current) return;\n release();\n reset();\n setStatus(\"idle\");\n emitEnded();\n };\n track?.addEventListener(\"ended\", ended);\n detach.current = () => track?.removeEventListener(\"ended\", ended);\n\n setStream(next);\n setSurface(track?.getSettings().displaySurface ?? null);\n setHasAudio(next.getAudioTracks().length > 0);\n setStatus(\"sharing\");\n })\n .catch((err: unknown) => {\n if (run !== generation.current) return;\n if (isCancellation(err)) {\n setStatus(\"idle\");\n emitCancelled(err);\n return;\n }\n setError(classifyMediaError(err, \"screen\"));\n setStatus(\"error\");\n });\n }, [\n audio,\n displaySurface,\n preferCurrentTab,\n selfBrowserSurface,\n surfaceSwitching,\n systemAudio,\n raw,\n release,\n reset,\n emitEnded,\n emitCancelled,\n ]);\n\n useEffect(() => release, [release]);\n\n return { status, stream, error, surface, hasAudio, supported, start, stop };\n}\n"],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import { useStableCallback as e } from "../hooks/use-stable-callback.js";
2
+ import { classifyMediaError as t, missingCaptureApiError as n } from "../audio/media-access.js";
3
+ import { useCallback as r, useEffect as i, useRef as a, useState as o } from "react";
4
+ //#region src/capture/use-screen-capture.ts
5
+ function s() {
6
+ return typeof navigator < "u" && navigator.mediaDevices !== void 0 && typeof navigator.mediaDevices.getDisplayMedia == "function";
7
+ }
8
+ function c(e) {
9
+ return e instanceof DOMException && (e.name === "NotAllowedError" || e.name === "AbortError");
10
+ }
11
+ function l(l = {}) {
12
+ let { audio: u = !1, displaySurface: d, preferCurrentTab: f, selfBrowserSurface: p, surfaceSwitching: m, systemAudio: h, options: g, onEnded: _, onCancelled: v } = l, [y, b] = o("idle"), [x, S] = o(null), [C, w] = o(null), [T, E] = o(null), [D, O] = o(!1), [k] = o(s), A = a(null), j = a(null), M = a(0), N = e(() => _?.()), P = e((e) => v?.(e)), F = r(() => {
13
+ j.current?.(), j.current = null, A.current?.getTracks().forEach((e) => e.stop()), A.current = null;
14
+ }, []), I = r(() => {
15
+ S(null), E(null), O(!1);
16
+ }, []), L = r(() => {
17
+ M.current += 1, F(), I(), b("idle"), w(null);
18
+ }, [F, I]), R = r(() => {
19
+ if (A.current) return;
20
+ let e = M.current += 1;
21
+ if (b("requesting"), w(null), !s()) {
22
+ w(n("screen")), b("error");
23
+ return;
24
+ }
25
+ let r = g ?? {
26
+ video: d ? { displaySurface: d } : {},
27
+ audio: u,
28
+ ...f === void 0 ? {} : { preferCurrentTab: f },
29
+ ...p === void 0 ? {} : { selfBrowserSurface: p },
30
+ ...m === void 0 ? {} : { surfaceSwitching: m },
31
+ ...h === void 0 ? {} : { systemAudio: h }
32
+ };
33
+ navigator.mediaDevices.getDisplayMedia(r).then((t) => {
34
+ if (e !== M.current) {
35
+ t.getTracks().forEach((e) => e.stop());
36
+ return;
37
+ }
38
+ A.current = t;
39
+ let n = t.getVideoTracks()[0], r = () => {
40
+ e === M.current && (F(), I(), b("idle"), N());
41
+ };
42
+ n?.addEventListener("ended", r), j.current = () => n?.removeEventListener("ended", r), S(t), E(n?.getSettings().displaySurface ?? null), O(t.getAudioTracks().length > 0), b("sharing");
43
+ }).catch((n) => {
44
+ if (e === M.current) {
45
+ if (c(n)) {
46
+ b("idle"), P(n);
47
+ return;
48
+ }
49
+ w(t(n, "screen")), b("error");
50
+ }
51
+ });
52
+ }, [
53
+ u,
54
+ d,
55
+ f,
56
+ p,
57
+ m,
58
+ h,
59
+ g,
60
+ F,
61
+ I,
62
+ N,
63
+ P
64
+ ]);
65
+ return i(() => F, [F]), {
66
+ status: y,
67
+ stream: x,
68
+ error: C,
69
+ surface: T,
70
+ hasAudio: D,
71
+ supported: k,
72
+ start: R,
73
+ stop: L
74
+ };
75
+ }
76
+ //#endregion
77
+ export { s as isScreenCaptureSupported, l as useScreenCapture };
78
+
79
+ //# sourceMappingURL=use-screen-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-screen-capture.js","names":[],"sources":["../../src/capture/use-screen-capture.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport {\n classifyMediaError,\n missingCaptureApiError,\n type MediaAccessError,\n} from \"@/audio/media-access\";\nimport { useStableCallback } from \"@/hooks/use-stable-callback\";\n\n/** Lifecycle of a screen share. */\nexport type ScreenCaptureStatus = \"idle\" | \"requesting\" | \"sharing\" | \"error\";\n\n/** Which surface to put first in the picker. */\nexport type DisplaySurfaceHint = \"monitor\" | \"window\" | \"browser\";\n\n/**\n * Hints `getDisplayMedia` takes that TypeScript's `DisplayMediaStreamOptions` does not\n * list yet.\n */\ntype DisplayMediaOptionsWithHints = DisplayMediaStreamOptions & {\n preferCurrentTab?: boolean;\n selfBrowserSurface?: \"include\" | \"exclude\";\n surfaceSwitching?: \"include\" | \"exclude\";\n systemAudio?: \"include\" | \"exclude\";\n};\n\n/** `displaySurface` is a constrainable property of a display track, but not in the DOM lib. */\ntype DisplayVideoConstraints = MediaTrackConstraints & { displaySurface?: DisplaySurfaceHint };\n\n/** Options for {@link useScreenCapture}. */\nexport interface UseScreenCaptureOptions {\n /**\n * Capture the tab's audio too. Default `false`.\n *\n * Chromium only offers this for a **tab** — sharing a window or a whole screen\n * yields no audio track no matter what you ask for, and Safari has no display\n * audio at all. Ask for it and check what you got.\n */\n audio?: boolean;\n /**\n * Which surface the picker should offer first — `\"browser\"` is a tab.\n *\n * A hint, never a guarantee: the user can always pick something else, and Firefox\n * ignores it. Read `surface` afterwards to learn what actually happened.\n */\n displaySurface?: DisplaySurfaceHint;\n /**\n * Put *this* tab at the top of the picker. Default `false`.\n *\n * The right setting for \"record what you are seeing right now\" in a support flow.\n * Chromium only.\n */\n preferCurrentTab?: boolean;\n /** Offer this tab in the list at all. `\"exclude\"` prevents the hall-of-mirrors capture. */\n selfBrowserSurface?: \"include\" | \"exclude\";\n /** Let the user switch to a different surface mid-share, without a new prompt. */\n surfaceSwitching?: \"include\" | \"exclude\";\n /** Include the system audio when a whole screen is shared. Chromium, Windows only. */\n systemAudio?: \"include\" | \"exclude\";\n /** Escape hatch: full options, replacing everything above. */\n options?: DisplayMediaStreamOptions;\n /**\n * The user stopped the share from the browser's own bar.\n *\n * The single most important callback here — see the note on\n * {@link useScreenCapture}.\n */\n onEnded?: () => void;\n /**\n * The user dismissed the picker. **Not an error.**\n *\n * Receives the rejection so an app that needs to tell a dismissal from an OS-level\n * block (macOS screen-recording permission) can look at the message. Most should\n * simply return the UI to its previous state.\n */\n onCancelled?: (reason: unknown) => void;\n}\n\n/** Value returned by {@link useScreenCapture}. */\nexport interface UseScreenCaptureResult {\n status: ScreenCaptureStatus;\n /** The live stream, or `null`. Feed it to {@link useVideoRecorder} or a `<video>`. */\n stream: MediaStream | null;\n /** Classified error, or `null`. A cancelled picker leaves this `null`. */\n error: MediaAccessError | null;\n /** What the user actually picked, when the browser reports it. */\n surface: string | null;\n /** Whether the stream carries an audio track — ask, do not assume. */\n hasAudio: boolean;\n /** `false` when `getDisplayMedia` is missing (every browser on iOS, insecure pages). */\n supported: boolean;\n /** Open the picker. Must be called from a user gesture. */\n start: () => void;\n /** Stop sharing from the app side. Fires nothing — you asked for it. */\n stop: () => void;\n}\n\n/** Whether `getDisplayMedia` is reachable at all. */\nexport function isScreenCaptureSupported(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n navigator.mediaDevices !== undefined &&\n typeof navigator.mediaDevices.getDisplayMedia === \"function\"\n );\n}\n\n/**\n * A rejection from `getDisplayMedia` that means \"the user said no thanks\".\n *\n * There is no distinct exception for a dismissed picker: closing it produces the same\n * `NotAllowedError` as a policy block, and some builds report `AbortError` instead. The\n * useful default is therefore to treat both as a cancellation, because a\n * display-capture prompt is **always** user-initiated — nothing can open it behind\n * their back — so the overwhelmingly likely cause is that they changed their mind, and\n * a red error toast for that punishes them for it. The rejection is handed to\n * `onCancelled` so the rarer causes stay diagnosable.\n */\nfunction isCancellation(err: unknown): boolean {\n return (\n err instanceof DOMException && (err.name === \"NotAllowedError\" || err.name === \"AbortError\")\n );\n}\n\n/**\n * Capture a screen, a window or a tab with `getDisplayMedia`.\n *\n * Three states decide whether this feels right, and two of them are easy to miss:\n *\n * - **The user dismissed the picker.** A rejection, but not a failure. It leaves\n * `error` at `null` and the status back at `\"idle\"`, and calls `onCancelled`.\n * - **The user stopped the share from the browser's own bar.** Nothing in your UI was\n * clicked and no promise rejects — the *only* signal is the video track's `ended`\n * event, so the hook listens for it and clears the stream. Without that listener, an\n * app shows \"gravando\" over a stream that is already dead.\n * - **The share is live.** `surface` says what was picked and `hasAudio` says whether\n * audio actually came along, which is not what you asked for but what you got.\n *\n * The stream is owned here: `stop()` and unmount both release every track. A recorder\n * built on it (`useVideoRecorder`) deliberately does not, so stopping a recording\n * leaves the share running for the next take.\n *\n * @param options - See {@link UseScreenCaptureOptions}.\n * @returns The stream, its status, a classified error and `start`/`stop`.\n *\n * @example\n * const screen = useScreenCapture({ preferCurrentTab: true, onEnded: () => save() });\n * const rec = useVideoRecorder(screen.stream);\n * <button onClick={screen.start}>Compartilhar tela</button>\n */\nexport function useScreenCapture(options: UseScreenCaptureOptions = {}): UseScreenCaptureResult {\n const {\n audio = false,\n displaySurface,\n preferCurrentTab,\n selfBrowserSurface,\n surfaceSwitching,\n systemAudio,\n options: raw,\n onEnded,\n onCancelled,\n } = options;\n\n const [status, setStatus] = useState<ScreenCaptureStatus>(\"idle\");\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [error, setError] = useState<MediaAccessError | null>(null);\n const [surface, setSurface] = useState<string | null>(null);\n const [hasAudio, setHasAudio] = useState(false);\n const [supported] = useState(isScreenCaptureSupported);\n\n const streamRef = useRef<MediaStream | null>(null);\n const detach = useRef<(() => void) | null>(null);\n const generation = useRef(0);\n\n const emitEnded = useStableCallback(() => onEnded?.());\n const emitCancelled = useStableCallback((reason: unknown) => onCancelled?.(reason));\n\n const release = useCallback((): void => {\n detach.current?.();\n detach.current = null;\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n }, []);\n\n const reset = useCallback((): void => {\n setStream(null);\n setSurface(null);\n setHasAudio(false);\n }, []);\n\n const stop = useCallback((): void => {\n generation.current += 1;\n release();\n reset();\n setStatus(\"idle\");\n setError(null);\n }, [release, reset]);\n\n const start = useCallback((): void => {\n if (streamRef.current) return;\n const run = (generation.current += 1);\n setStatus(\"requesting\");\n setError(null);\n\n if (!isScreenCaptureSupported()) {\n setError(missingCaptureApiError(\"screen\"));\n setStatus(\"error\");\n return;\n }\n\n const video: DisplayVideoConstraints = displaySurface ? { displaySurface } : {};\n const request: DisplayMediaOptionsWithHints = raw ?? {\n video,\n audio,\n ...(preferCurrentTab !== undefined ? { preferCurrentTab } : {}),\n ...(selfBrowserSurface !== undefined ? { selfBrowserSurface } : {}),\n ...(surfaceSwitching !== undefined ? { surfaceSwitching } : {}),\n ...(systemAudio !== undefined ? { systemAudio } : {}),\n };\n\n void navigator.mediaDevices\n .getDisplayMedia(request)\n .then((next) => {\n if (run !== generation.current) {\n next.getTracks().forEach((track) => track.stop());\n return;\n }\n streamRef.current = next;\n const track = next.getVideoTracks()[0];\n const ended = (): void => {\n if (run !== generation.current) return;\n release();\n reset();\n setStatus(\"idle\");\n emitEnded();\n };\n track?.addEventListener(\"ended\", ended);\n detach.current = () => track?.removeEventListener(\"ended\", ended);\n\n setStream(next);\n setSurface(track?.getSettings().displaySurface ?? null);\n setHasAudio(next.getAudioTracks().length > 0);\n setStatus(\"sharing\");\n })\n .catch((err: unknown) => {\n if (run !== generation.current) return;\n if (isCancellation(err)) {\n setStatus(\"idle\");\n emitCancelled(err);\n return;\n }\n setError(classifyMediaError(err, \"screen\"));\n setStatus(\"error\");\n });\n }, [\n audio,\n displaySurface,\n preferCurrentTab,\n selfBrowserSurface,\n surfaceSwitching,\n systemAudio,\n raw,\n release,\n reset,\n emitEnded,\n emitCancelled,\n ]);\n\n useEffect(() => release, [release]);\n\n return { status, stream, error, surface, hasAudio, supported, start, stop };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../hooks/use-stable-callback.cjs");let t=require("react");var n={unsupported:`Speech recognition is not supported in this browser.`,"not-allowed":`Microphone permission denied. Enable access in your browser settings.`,"no-speech":`No speech was detected.`,"audio-capture":`No microphone available on this device.`,network:`The recognition service could not be reached.`,aborted:`Recognition was cancelled.`,"language-not-supported":`The recognition service does not support this language.`,unknown:`Unexpected error during speech recognition.`};function r(e){switch(e){case`not-allowed`:case`service-not-allowed`:return`not-allowed`;case`no-speech`:return`no-speech`;case`audio-capture`:return`audio-capture`;case`network`:return`network`;case`aborted`:return`aborted`;case`language-not-supported`:return`language-not-supported`;default:return`unknown`}}function i(){let e=globalThis,t=e.SpeechRecognition??e.webkitSpeechRecognition;return typeof t==`function`?t:null}function a(){return i()!==null}function o(o={}){let{lang:s=`pt-BR`,continuous:c=!1,interimResults:l=!0,maxAlternatives:u=1,onResult:d,onFinal:f,onError:p,onEnd:m,factory:h}=o,[g]=(0,t.useState)(()=>h!==void 0||a()),[_,v]=(0,t.useState)(!1),[y,b]=(0,t.useState)(``),[x,S]=(0,t.useState)(``),[C,w]=(0,t.useState)(null),T=(0,t.useRef)(null),E=e.useStableCallback(e=>d?.(e)),D=e.useStableCallback(e=>f?.(e)),O=e.useStableCallback(()=>m?.()),k=e.useStableCallback(e=>{w(e),p?.(e)}),A=(0,t.useCallback)(()=>{let e=T.current;e&&(e.onresult=null,e.onerror=null,e.onend=null,e.onstart=null,T.current=null)},[]),j=(0,t.useCallback)(()=>{if(T.current)return;let e=(h??(()=>{let e=i();return e?new e:null}))();if(!e){k({kind:`unsupported`,message:n.unsupported});return}e.lang=s,e.continuous=c,e.interimResults=l,e.maxAlternatives=u,e.onresult=e=>{let t=``,n=``;for(let r=e.resultIndex;r<e.results.length;r+=1){let i=e.results[r];if(!i)continue;let a=i[0]?.transcript??``;i.isFinal?t+=a:n+=a}S(n),t!==``&&(b(e=>e+t),D(t)),E({transcript:t===``?n:t,isFinal:t!==``})},e.onerror=e=>{let t=r(e.error);k({kind:t,message:e.message||n[t]})},e.onend=()=>{v(!1),S(``),A(),O()},e.onstart=()=>v(!0),w(null);try{e.start()}catch(e){k({kind:`unknown`,message:e instanceof Error?e.message:n.unknown});return}T.current=e,v(!0)},[s,c,l,u,h,E,D,O,k,A]),M=(0,t.useCallback)(()=>{T.current?.stop()},[]),N=(0,t.useCallback)(()=>{T.current?.abort()},[]),P=(0,t.useCallback)(()=>{b(``),S(``),w(null)},[]);return(0,t.useEffect)(()=>()=>{T.current?.abort(),A()},[A]),{supported:g,listening:_,transcript:y,interim:x,error:C,start:j,stop:M,abort:N,reset:P}}exports.isSpeechRecognitionSupported=a,exports.useSpeechRecognition=o;
2
+ //# sourceMappingURL=use-speech-recognition.cjs.map