tempest-react-sdk 0.25.0 → 0.26.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 (320) hide show
  1. package/README.md +14 -1
  2. package/bin/lib/alias/collect.mjs +94 -0
  3. package/bin/lib/alias/collect.test.mjs +95 -0
  4. package/bin/lib/alias/discover.mjs +77 -0
  5. package/bin/lib/alias/discover.test.mjs +189 -0
  6. package/bin/lib/alias/index.mjs +88 -0
  7. package/bin/lib/alias/index.test.mjs +138 -0
  8. package/bin/lib/alias/rewrite-css.mjs +76 -0
  9. package/bin/lib/alias/rewrite-css.test.mjs +85 -0
  10. package/bin/lib/alias/rewrite.mjs +141 -0
  11. package/bin/lib/alias/rewrite.test.mjs +148 -0
  12. package/bin/lib/alias/specifier.mjs +49 -0
  13. package/bin/lib/alias/specifier.test.mjs +85 -0
  14. package/bin/lib/alias/tsconfig.mjs +116 -0
  15. package/bin/lib/alias/typescript.mjs +23 -0
  16. package/bin/lib/icons/generate.mjs +156 -0
  17. package/bin/lib/icons/generate.test.mjs +125 -0
  18. package/bin/tempest.mjs +158 -13
  19. package/dist/audio/use-audio.cjs +1 -1
  20. package/dist/audio/use-audio.cjs.map +1 -1
  21. package/dist/audio/use-audio.js +12 -12
  22. package/dist/audio/use-audio.js.map +1 -1
  23. package/dist/charts/palette.cjs +1 -1
  24. package/dist/charts/palette.cjs.map +1 -1
  25. package/dist/charts/palette.js +2 -2
  26. package/dist/charts/palette.js.map +1 -1
  27. package/dist/charts.d.ts +5 -3
  28. package/dist/components/ClickOutside/ClickOutside.cjs +1 -1
  29. package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
  30. package/dist/components/ClickOutside/ClickOutside.js +3 -1
  31. package/dist/components/ClickOutside/ClickOutside.js.map +1 -1
  32. package/dist/components/DataTable/DataTable.cjs +1 -1
  33. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  34. package/dist/components/DataTable/DataTable.js +21 -23
  35. package/dist/components/DataTable/DataTable.js.map +1 -1
  36. package/dist/components/Kanban/Kanban.cjs +2 -0
  37. package/dist/components/Kanban/Kanban.cjs.map +1 -0
  38. package/dist/components/Kanban/Kanban.js +102 -0
  39. package/dist/components/Kanban/Kanban.js.map +1 -0
  40. package/dist/components/Kanban/Kanban.module.cjs +2 -0
  41. package/dist/components/Kanban/Kanban.module.cjs.map +1 -0
  42. package/dist/components/Kanban/Kanban.module.js +19 -0
  43. package/dist/components/Kanban/Kanban.module.js.map +1 -0
  44. package/dist/components/NotificationCenter/NotificationCenter.cjs +2 -0
  45. package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -0
  46. package/dist/components/NotificationCenter/NotificationCenter.js +83 -0
  47. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -0
  48. package/dist/components/NotificationCenter/NotificationCenter.module.cjs +2 -0
  49. package/dist/components/NotificationCenter/NotificationCenter.module.cjs.map +1 -0
  50. package/dist/components/NotificationCenter/NotificationCenter.module.js +21 -0
  51. package/dist/components/NotificationCenter/NotificationCenter.module.js.map +1 -0
  52. package/dist/components/NotificationCenter/use-notification-inbox.cjs +2 -0
  53. package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -0
  54. package/dist/components/NotificationCenter/use-notification-inbox.js +63 -0
  55. package/dist/components/NotificationCenter/use-notification-inbox.js.map +1 -0
  56. package/dist/components/RefreshIndicator/RefreshIndicator.cjs +1 -1
  57. package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
  58. package/dist/components/RefreshIndicator/RefreshIndicator.js +17 -17
  59. package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
  60. package/dist/components/Tooltip/Tooltip.cjs +1 -1
  61. package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
  62. package/dist/components/Tooltip/Tooltip.js +26 -20
  63. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  64. package/dist/components/VirtualTable/VirtualTable.cjs +2 -0
  65. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -0
  66. package/dist/components/VirtualTable/VirtualTable.js +116 -0
  67. package/dist/components/VirtualTable/VirtualTable.js.map +1 -0
  68. package/dist/components/VirtualTable/VirtualTable.module.cjs +2 -0
  69. package/dist/components/VirtualTable/VirtualTable.module.cjs.map +1 -0
  70. package/dist/components/VirtualTable/VirtualTable.module.js +20 -0
  71. package/dist/components/VirtualTable/VirtualTable.module.js.map +1 -0
  72. package/dist/components/Wizard/Wizard.cjs +1 -1
  73. package/dist/components/Wizard/Wizard.cjs.map +1 -1
  74. package/dist/components/Wizard/Wizard.js +53 -53
  75. package/dist/components/Wizard/Wizard.js.map +1 -1
  76. package/dist/geo/use-position-tracker.cjs +1 -1
  77. package/dist/geo/use-position-tracker.cjs.map +1 -1
  78. package/dist/geo/use-position-tracker.js +3 -1
  79. package/dist/geo/use-position-tracker.js.map +1 -1
  80. package/dist/hooks/use-async.cjs +1 -1
  81. package/dist/hooks/use-async.cjs.map +1 -1
  82. package/dist/hooks/use-async.js +3 -1
  83. package/dist/hooks/use-async.js.map +1 -1
  84. package/dist/hooks/use-click-outside.cjs +1 -1
  85. package/dist/hooks/use-click-outside.cjs.map +1 -1
  86. package/dist/hooks/use-click-outside.js +3 -1
  87. package/dist/hooks/use-click-outside.js.map +1 -1
  88. package/dist/hooks/use-deep-memo.cjs.map +1 -1
  89. package/dist/hooks/use-deep-memo.js.map +1 -1
  90. package/dist/hooks/use-event-listener.cjs +1 -1
  91. package/dist/hooks/use-event-listener.cjs.map +1 -1
  92. package/dist/hooks/use-event-listener.js +3 -1
  93. package/dist/hooks/use-event-listener.js.map +1 -1
  94. package/dist/hooks/use-interval.cjs +1 -1
  95. package/dist/hooks/use-interval.cjs.map +1 -1
  96. package/dist/hooks/use-interval.js +3 -1
  97. package/dist/hooks/use-interval.js.map +1 -1
  98. package/dist/hooks/use-is-first-render.cjs.map +1 -1
  99. package/dist/hooks/use-is-first-render.js.map +1 -1
  100. package/dist/hooks/use-long-press-handlers.cjs +1 -1
  101. package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
  102. package/dist/hooks/use-long-press-handlers.js +25 -23
  103. package/dist/hooks/use-long-press-handlers.js.map +1 -1
  104. package/dist/hooks/use-long-press.cjs +1 -1
  105. package/dist/hooks/use-long-press.cjs.map +1 -1
  106. package/dist/hooks/use-long-press.js +3 -1
  107. package/dist/hooks/use-long-press.js.map +1 -1
  108. package/dist/hooks/use-online.cjs +1 -1
  109. package/dist/hooks/use-online.cjs.map +1 -1
  110. package/dist/hooks/use-online.js +6 -4
  111. package/dist/hooks/use-online.js.map +1 -1
  112. package/dist/hooks/use-previous.cjs.map +1 -1
  113. package/dist/hooks/use-previous.js.map +1 -1
  114. package/dist/hooks/use-service-worker-update.cjs +1 -1
  115. package/dist/hooks/use-service-worker-update.cjs.map +1 -1
  116. package/dist/hooks/use-service-worker-update.js +3 -1
  117. package/dist/hooks/use-service-worker-update.js.map +1 -1
  118. package/dist/hooks/use-sortable.cjs +2 -0
  119. package/dist/hooks/use-sortable.cjs.map +1 -0
  120. package/dist/hooks/use-sortable.js +169 -0
  121. package/dist/hooks/use-sortable.js.map +1 -0
  122. package/dist/hooks/use-stable-callback.cjs.map +1 -1
  123. package/dist/hooks/use-stable-callback.js.map +1 -1
  124. package/dist/hooks/use-timeout.cjs +1 -1
  125. package/dist/hooks/use-timeout.cjs.map +1 -1
  126. package/dist/hooks/use-timeout.js +3 -1
  127. package/dist/hooks/use-timeout.js.map +1 -1
  128. package/dist/icons/Icon.cjs +2 -0
  129. package/dist/icons/Icon.cjs.map +1 -0
  130. package/dist/icons/Icon.js +24 -0
  131. package/dist/icons/Icon.js.map +1 -0
  132. package/dist/icons/IconProvider.cjs +2 -0
  133. package/dist/icons/IconProvider.cjs.map +1 -0
  134. package/dist/icons/IconProvider.js +24 -0
  135. package/dist/icons/IconProvider.js.map +1 -0
  136. package/dist/icons/generated/aliases.cjs +2 -0
  137. package/dist/icons/generated/aliases.cjs.map +1 -0
  138. package/dist/icons/generated/aliases.js +255 -0
  139. package/dist/icons/generated/aliases.js.map +1 -0
  140. package/dist/icons/generated/icon-names.cjs +2 -0
  141. package/dist/icons/generated/icon-names.cjs.map +1 -0
  142. package/dist/icons/generated/icon-names.js +6 -0
  143. package/dist/icons/generated/icon-names.js.map +1 -0
  144. package/dist/icons/generated/loaders.cjs +2 -0
  145. package/dist/icons/generated/loaders.cjs.map +1 -0
  146. package/dist/icons/generated/loaders.js +32 -0
  147. package/dist/icons/generated/loaders.js.map +1 -0
  148. package/dist/icons/generated/shard-a.cjs +2 -0
  149. package/dist/icons/generated/shard-a.cjs.map +1 -0
  150. package/dist/icons/generated/shard-a.js +114 -0
  151. package/dist/icons/generated/shard-a.js.map +1 -0
  152. package/dist/icons/generated/shard-b.cjs +2 -0
  153. package/dist/icons/generated/shard-b.cjs.map +1 -0
  154. package/dist/icons/generated/shard-b.js +156 -0
  155. package/dist/icons/generated/shard-b.js.map +1 -0
  156. package/dist/icons/generated/shard-c.cjs +2 -0
  157. package/dist/icons/generated/shard-c.cjs.map +1 -0
  158. package/dist/icons/generated/shard-c.js +269 -0
  159. package/dist/icons/generated/shard-c.js.map +1 -0
  160. package/dist/icons/generated/shard-d.cjs +2 -0
  161. package/dist/icons/generated/shard-d.cjs.map +1 -0
  162. package/dist/icons/generated/shard-d.js +62 -0
  163. package/dist/icons/generated/shard-d.js.map +1 -0
  164. package/dist/icons/generated/shard-e.cjs +2 -0
  165. package/dist/icons/generated/shard-e.cjs.map +1 -0
  166. package/dist/icons/generated/shard-e.js +32 -0
  167. package/dist/icons/generated/shard-e.js.map +1 -0
  168. package/dist/icons/generated/shard-f.cjs +2 -0
  169. package/dist/icons/generated/shard-f.cjs.map +1 -0
  170. package/dist/icons/generated/shard-f.js +139 -0
  171. package/dist/icons/generated/shard-f.js.map +1 -0
  172. package/dist/icons/generated/shard-g.cjs +2 -0
  173. package/dist/icons/generated/shard-g.cjs.map +1 -0
  174. package/dist/icons/generated/shard-g.js +61 -0
  175. package/dist/icons/generated/shard-g.js.map +1 -0
  176. package/dist/icons/generated/shard-h.cjs +2 -0
  177. package/dist/icons/generated/shard-h.cjs.map +1 -0
  178. package/dist/icons/generated/shard-h.js +63 -0
  179. package/dist/icons/generated/shard-h.js.map +1 -0
  180. package/dist/icons/generated/shard-i.cjs +2 -0
  181. package/dist/icons/generated/shard-i.cjs.map +1 -0
  182. package/dist/icons/generated/shard-i.js +30 -0
  183. package/dist/icons/generated/shard-i.js.map +1 -0
  184. package/dist/icons/generated/shard-j.cjs +2 -0
  185. package/dist/icons/generated/shard-j.cjs.map +1 -0
  186. package/dist/icons/generated/shard-j.js +10 -0
  187. package/dist/icons/generated/shard-j.js.map +1 -0
  188. package/dist/icons/generated/shard-k.cjs +2 -0
  189. package/dist/icons/generated/shard-k.cjs.map +1 -0
  190. package/dist/icons/generated/shard-k.js +16 -0
  191. package/dist/icons/generated/shard-k.js.map +1 -0
  192. package/dist/icons/generated/shard-l.cjs +2 -0
  193. package/dist/icons/generated/shard-l.cjs.map +1 -0
  194. package/dist/icons/generated/shard-l.js +88 -0
  195. package/dist/icons/generated/shard-l.js.map +1 -0
  196. package/dist/icons/generated/shard-m.cjs +2 -0
  197. package/dist/icons/generated/shard-m.cjs.map +1 -0
  198. package/dist/icons/generated/shard-m.js +140 -0
  199. package/dist/icons/generated/shard-m.js.map +1 -0
  200. package/dist/icons/generated/shard-n.cjs +2 -0
  201. package/dist/icons/generated/shard-n.cjs.map +1 -0
  202. package/dist/icons/generated/shard-n.js +24 -0
  203. package/dist/icons/generated/shard-n.js.map +1 -0
  204. package/dist/icons/generated/shard-o.cjs +2 -0
  205. package/dist/icons/generated/shard-o.cjs.map +1 -0
  206. package/dist/icons/generated/shard-o.js +17 -0
  207. package/dist/icons/generated/shard-o.js.map +1 -0
  208. package/dist/icons/generated/shard-p.cjs +2 -0
  209. package/dist/icons/generated/shard-p.cjs.map +1 -0
  210. package/dist/icons/generated/shard-p.js +115 -0
  211. package/dist/icons/generated/shard-p.js.map +1 -0
  212. package/dist/icons/generated/shard-q.cjs +2 -0
  213. package/dist/icons/generated/shard-q.cjs.map +1 -0
  214. package/dist/icons/generated/shard-q.js +10 -0
  215. package/dist/icons/generated/shard-q.js.map +1 -0
  216. package/dist/icons/generated/shard-r.cjs +2 -0
  217. package/dist/icons/generated/shard-r.cjs.map +1 -0
  218. package/dist/icons/generated/shard-r.js +77 -0
  219. package/dist/icons/generated/shard-r.js.map +1 -0
  220. package/dist/icons/generated/shard-s.cjs +2 -0
  221. package/dist/icons/generated/shard-s.cjs.map +1 -0
  222. package/dist/icons/generated/shard-s.js +255 -0
  223. package/dist/icons/generated/shard-s.js.map +1 -0
  224. package/dist/icons/generated/shard-t.cjs +2 -0
  225. package/dist/icons/generated/shard-t.cjs.map +1 -0
  226. package/dist/icons/generated/shard-t.js +109 -0
  227. package/dist/icons/generated/shard-t.js.map +1 -0
  228. package/dist/icons/generated/shard-u.cjs +2 -0
  229. package/dist/icons/generated/shard-u.cjs.map +1 -0
  230. package/dist/icons/generated/shard-u.js +49 -0
  231. package/dist/icons/generated/shard-u.js.map +1 -0
  232. package/dist/icons/generated/shard-v.cjs +2 -0
  233. package/dist/icons/generated/shard-v.cjs.map +1 -0
  234. package/dist/icons/generated/shard-v.js +30 -0
  235. package/dist/icons/generated/shard-v.js.map +1 -0
  236. package/dist/icons/generated/shard-w.cjs +2 -0
  237. package/dist/icons/generated/shard-w.cjs.map +1 -0
  238. package/dist/icons/generated/shard-w.js +48 -0
  239. package/dist/icons/generated/shard-w.js.map +1 -0
  240. package/dist/icons/generated/shard-x.cjs +2 -0
  241. package/dist/icons/generated/shard-x.cjs.map +1 -0
  242. package/dist/icons/generated/shard-x.js +10 -0
  243. package/dist/icons/generated/shard-x.js.map +1 -0
  244. package/dist/icons/generated/shard-z.cjs +2 -0
  245. package/dist/icons/generated/shard-z.cjs.map +1 -0
  246. package/dist/icons/generated/shard-z.js +25 -0
  247. package/dist/icons/generated/shard-z.js.map +1 -0
  248. package/dist/icons/icon-context.cjs +2 -0
  249. package/dist/icons/icon-context.cjs.map +1 -0
  250. package/dist/icons/icon-context.js +13 -0
  251. package/dist/icons/icon-context.js.map +1 -0
  252. package/dist/icons/is-icon-name.cjs +2 -0
  253. package/dist/icons/is-icon-name.cjs.map +1 -0
  254. package/dist/icons/is-icon-name.js +10 -0
  255. package/dist/icons/is-icon-name.js.map +1 -0
  256. package/dist/icons/shard-cache.cjs +2 -0
  257. package/dist/icons/shard-cache.cjs.map +1 -0
  258. package/dist/icons/shard-cache.js +41 -0
  259. package/dist/icons/shard-cache.js.map +1 -0
  260. package/dist/icons/use-icon.cjs +2 -0
  261. package/dist/icons/use-icon.cjs.map +1 -0
  262. package/dist/icons/use-icon.js +20 -0
  263. package/dist/icons/use-icon.js.map +1 -0
  264. package/dist/icons-virtual.d.ts +22 -0
  265. package/dist/icons.cjs +1 -0
  266. package/dist/icons.d.ts +220 -0
  267. package/dist/icons.js +9 -0
  268. package/dist/offline/use-offline-sync.cjs +1 -1
  269. package/dist/offline/use-offline-sync.cjs.map +1 -1
  270. package/dist/offline/use-offline-sync.js +3 -1
  271. package/dist/offline/use-offline-sync.js.map +1 -1
  272. package/dist/push/use-push-subscription.cjs +1 -1
  273. package/dist/push/use-push-subscription.cjs.map +1 -1
  274. package/dist/push/use-push-subscription.js +3 -1
  275. package/dist/push/use-push-subscription.js.map +1 -1
  276. package/dist/sse/use-event-stream.cjs +1 -1
  277. package/dist/sse/use-event-stream.cjs.map +1 -1
  278. package/dist/sse/use-event-stream.js +3 -1
  279. package/dist/sse/use-event-stream.js.map +1 -1
  280. package/dist/styles.css +1 -1
  281. package/dist/tempest-react-sdk.cjs +1 -1
  282. package/dist/tempest-react-sdk.d.ts +585 -3
  283. package/dist/tempest-react-sdk.js +189 -183
  284. package/dist/theme/ThemeProvider.cjs +1 -1
  285. package/dist/theme/ThemeProvider.cjs.map +1 -1
  286. package/dist/theme/ThemeProvider.js +6 -2
  287. package/dist/theme/ThemeProvider.js.map +1 -1
  288. package/dist/theme/color.cjs +1 -1
  289. package/dist/theme/color.cjs.map +1 -1
  290. package/dist/theme/color.js +77 -46
  291. package/dist/theme/color.js.map +1 -1
  292. package/dist/theme/create-theme.cjs +1 -1
  293. package/dist/theme/create-theme.cjs.map +1 -1
  294. package/dist/theme/create-theme.js +9 -3
  295. package/dist/theme/create-theme.js.map +1 -1
  296. package/dist/utils/compare-values.cjs +2 -0
  297. package/dist/utils/compare-values.cjs.map +1 -0
  298. package/dist/utils/compare-values.js +8 -0
  299. package/dist/utils/compare-values.js.map +1 -0
  300. package/dist/vision/use-camera-stream.cjs +1 -1
  301. package/dist/vision/use-camera-stream.cjs.map +1 -1
  302. package/dist/vision/use-camera-stream.js +3 -1
  303. package/dist/vision/use-camera-stream.js.map +1 -1
  304. package/dist/vite/create-vite-config.cjs +1 -1
  305. package/dist/vite/create-vite-config.cjs.map +1 -1
  306. package/dist/vite/create-vite-config.js +30 -22
  307. package/dist/vite/create-vite-config.js.map +1 -1
  308. package/dist/vite/tempest-icons.cjs +9 -0
  309. package/dist/vite/tempest-icons.cjs.map +1 -0
  310. package/dist/vite/tempest-icons.js +99 -0
  311. package/dist/vite/tempest-icons.js.map +1 -0
  312. package/dist/vite.cjs +1 -1
  313. package/dist/vite.d.ts +79 -0
  314. package/dist/vite.js +6 -5
  315. package/dist/ws/use-web-socket.cjs +1 -1
  316. package/dist/ws/use-web-socket.cjs.map +1 -1
  317. package/dist/ws/use-web-socket.js +3 -1
  318. package/dist/ws/use-web-socket.js.map +1 -1
  319. package/package.json +12 -3
  320. package/template/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"create-theme.js","names":[],"sources":["../../src/theme/create-theme.ts"],"sourcesContent":["/**\n * Theme factory: turns a handful of brand colors into the full set of\n * `--tempest-*` token overrides, for both light and dark.\n *\n * Rebranding used to mean hand-writing ~167 custom properties (and getting the\n * dark ramp inversion right by hand). `createTheme({ primary: \"#7c3aed\" })`\n * emits the same thing, derived in OKLCH so the ramp stays perceptually even.\n */\nimport {\n contrastRatio,\n createColorScale,\n hexToRgbaString,\n readableForeground,\n type ColorScale,\n type ScaleStep,\n} from \"./color\";\n\n/** Radius presets, applied to the whole `--tempest-radius-*` family at once. */\nexport type ThemeRadius = \"none\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\n/** Status token families that {@link createTheme} can regenerate. */\nexport type ThemeStatus = \"success\" | \"warning\" | \"danger\" | \"info\";\n\n/** Input for {@link createTheme}. Every field is optional — omitted families keep the built-in tokens. */\nexport interface CreateThemeOptions {\n /** Brand color, used as the `500` step of the primary scale. */\n primary?: string;\n /** Neutral color, used as the `500` step of the gray scale (surfaces, borders, text). */\n gray?: string;\n /** Success color (`--tempest-success*`). */\n success?: string;\n /** Warning color (`--tempest-warning*`). */\n warning?: string;\n /** Danger color (`--tempest-danger*`). */\n danger?: string;\n /** Info color (`--tempest-info*`). */\n info?: string;\n /**\n * Categorical series colors for `tempest-react-sdk/charts`, in cycle order.\n * Written to `--tempest-chart-1` … `--tempest-chart-N`, so charts follow the\n * theme instead of a hardcoded palette.\n */\n chart?: string[];\n /** Corner radius scale. A preset name, or explicit per-step values. */\n radius?: ThemeRadius | Partial<Record<\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\", string>>;\n /** Alpha of `--tempest-focus-ring-color`, derived from the brand color. Default `0.35`. */\n focusRingAlpha?: number;\n /** Selector the light tokens are written under. Default `\":root\"`. */\n selector?: string;\n /** Selector the dark tokens are written under. Default `'[data-tempest-theme=\"dark\"]'`. */\n darkSelector?: string;\n}\n\n/** A generated theme: token maps per color scheme, plus the CSS text that carries them. */\nexport interface GeneratedTheme {\n /** Light-scheme custom properties, without the leading `--`-less names (keys include `--`). */\n light: Record<string, string>;\n /** Dark-scheme custom properties. */\n dark: Record<string, string>;\n /** Both blocks rendered as CSS, ready for {@link applyTheme} or a stylesheet. */\n css: string;\n}\n\nconst RADIUS_PRESETS: Record<ThemeRadius, Record<string, string>> = {\n none: { xs: \"0\", sm: \"0\", md: \"0\", lg: \"0\", xl: \"0\", \"2xl\": \"0\" },\n sm: { xs: \"1px\", sm: \"2px\", md: \"4px\", lg: \"6px\", xl: \"8px\", \"2xl\": \"12px\" },\n md: { xs: \"2px\", sm: \"4px\", md: \"8px\", lg: \"12px\", xl: \"16px\", \"2xl\": \"24px\" },\n lg: { xs: \"4px\", sm: \"6px\", md: \"12px\", lg: \"16px\", xl: \"22px\", \"2xl\": \"32px\" },\n xl: { xs: \"6px\", sm: \"10px\", md: \"16px\", lg: \"24px\", xl: \"32px\", \"2xl\": \"44px\" },\n full: { xs: \"4px\", sm: \"8px\", md: \"9999px\", lg: \"9999px\", xl: \"9999px\", \"2xl\": \"9999px\" },\n};\n\nconst SCALE_STEPS: ScaleStep[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];\n\nfunction writeScale(tokens: Record<string, string>, name: string, scale: ColorScale): void {\n for (const step of SCALE_STEPS) {\n tokens[`--tempest-${name}-${step}`] = scale[step];\n }\n}\n\n/** Minimum contrast for body text, WCAG 2.x AA. */\nconst AA_TEXT_CONTRAST = 4.5;\n\n/**\n * Choose the ramp step for text sitting on the soft tint.\n *\n * A fixed step does not survive an arbitrary brand color: the built-in blue only\n * reaches 4.37:1 at `500` over its own `50` tint, and a generated emerald lands\n * at 4.41:1 at `600` — both fail AA for body text by a hair. So the step is\n * picked by measuring, walking away from the tint until it clears AA, and\n * falling back to the most extreme candidate when even that cannot (a very light\n * or very desaturated brand).\n *\n * @param scale - The generated ramp.\n * @param softStep - Step used as `--tempest-primary-soft`.\n * @param candidates - Steps to try, in order of preference.\n * @returns The first candidate clearing {@link AA_TEXT_CONTRAST}, else the last.\n */\nfunction pickOnSoftStep(\n scale: ColorScale,\n softStep: ScaleStep,\n candidates: ScaleStep[],\n): ScaleStep {\n for (const step of candidates) {\n if (contrastRatio(scale[softStep], scale[step]) >= AA_TEXT_CONTRAST) return step;\n }\n return candidates[candidates.length - 1];\n}\n\n/**\n * Emit the primary aliases for one scheme.\n *\n * The dark scheme walks the ramp the other way (hover is *lighter*, the soft\n * tint is a dark shade, and readable text on that tint is a light shade) — the\n * same inversion the built-in `colors.css` dark block does by hand.\n */\nfunction writePrimaryAliases(\n tokens: Record<string, string>,\n scale: ColorScale,\n scheme: \"light\" | \"dark\",\n focusRingAlpha: number,\n): void {\n tokens[\"--tempest-primary\"] = \"var(--tempest-primary-500)\";\n if (scheme === \"light\") {\n tokens[\"--tempest-primary-hover\"] = \"var(--tempest-primary-600)\";\n tokens[\"--tempest-primary-active\"] = \"var(--tempest-primary-700)\";\n tokens[\"--tempest-primary-soft\"] = \"var(--tempest-primary-50)\";\n tokens[\"--tempest-primary-soft-hover\"] = \"var(--tempest-primary-100)\";\n tokens[\"--tempest-primary-on-soft\"] =\n `var(--tempest-primary-${pickOnSoftStep(scale, 50, [600, 700, 800, 900])})`;\n } else {\n tokens[\"--tempest-primary-hover\"] = \"var(--tempest-primary-400)\";\n tokens[\"--tempest-primary-active\"] = \"var(--tempest-primary-300)\";\n tokens[\"--tempest-primary-soft\"] = \"var(--tempest-primary-100)\";\n tokens[\"--tempest-primary-soft-hover\"] = \"var(--tempest-primary-200)\";\n tokens[\"--tempest-primary-on-soft\"] =\n `var(--tempest-primary-${pickOnSoftStep(scale, 100, [700, 800, 900])})`;\n }\n\n const foreground = readableForeground(scale[500]);\n tokens[\"--tempest-primary-foreground\"] = foreground;\n tokens[\"--tempest-text-on-primary\"] = foreground;\n tokens[\"--tempest-focus-ring-color\"] = hexToRgbaString(scale[500], focusRingAlpha);\n}\n\n/**\n * Emit the neutral surface/border/text aliases from a generated gray scale.\n *\n * `--tempest-bg` is pushed past the ramp on purpose: pure white in light mode and\n * a shade darker than `gray-50` in dark mode, so a raised surface still reads as\n * raised against the page.\n */\nfunction writeNeutralAliases(\n tokens: Record<string, string>,\n scale: ColorScale,\n scheme: \"light\" | \"dark\",\n): void {\n if (scheme === \"light\") {\n tokens[\"--tempest-bg\"] = \"#ffffff\";\n tokens[\"--tempest-surface\"] = \"var(--tempest-gray-50)\";\n tokens[\"--tempest-surface-2\"] = \"var(--tempest-gray-100)\";\n tokens[\"--tempest-surface-3\"] = \"var(--tempest-gray-200)\";\n tokens[\"--tempest-border\"] = \"var(--tempest-gray-200)\";\n tokens[\"--tempest-border-strong\"] = \"var(--tempest-gray-300)\";\n tokens[\"--tempest-text\"] = \"var(--tempest-gray-900)\";\n tokens[\"--tempest-text-muted\"] = \"var(--tempest-gray-600)\";\n tokens[\"--tempest-text-subtle\"] = \"var(--tempest-gray-500)\";\n } else {\n tokens[\"--tempest-bg\"] = scale[50];\n tokens[\"--tempest-surface\"] = \"var(--tempest-gray-100)\";\n tokens[\"--tempest-surface-2\"] = \"var(--tempest-gray-200)\";\n tokens[\"--tempest-surface-3\"] = \"var(--tempest-gray-300)\";\n tokens[\"--tempest-border\"] = \"var(--tempest-gray-300)\";\n tokens[\"--tempest-border-strong\"] = \"var(--tempest-gray-400)\";\n tokens[\"--tempest-text\"] = \"var(--tempest-gray-900)\";\n tokens[\"--tempest-text-muted\"] = \"var(--tempest-gray-700)\";\n tokens[\"--tempest-text-subtle\"] = \"var(--tempest-gray-600)\";\n }\n}\n\n/**\n * Emit one status family (`--tempest-danger`, `-fg`, `-bg`, `-border`, `-solid`).\n *\n * `-fg` is the text shade over `-bg`, so it has to cross the ramp in opposite\n * directions per scheme; `-solid` stays the saturated fill used by badges.\n */\nfunction writeStatus(\n tokens: Record<string, string>,\n name: ThemeStatus,\n scale: ColorScale,\n scheme: \"light\" | \"dark\",\n): void {\n if (scheme === \"light\") {\n tokens[`--tempest-${name}`] = scale[700];\n tokens[`--tempest-${name}-fg`] = scale[800];\n tokens[`--tempest-${name}-bg`] = scale[50];\n tokens[`--tempest-${name}-border`] = scale[200];\n tokens[`--tempest-${name}-solid`] = scale[600];\n } else {\n tokens[`--tempest-${name}`] = scale[700];\n tokens[`--tempest-${name}-fg`] = scale[700];\n tokens[`--tempest-${name}-bg`] = scale[50];\n tokens[`--tempest-${name}-border`] = scale[200];\n tokens[`--tempest-${name}-solid`] = scale[500];\n }\n}\n\nfunction renderBlock(selector: string, tokens: Record<string, string>): string {\n const entries = Object.entries(tokens);\n if (entries.length === 0) return \"\";\n const body = entries.map(([name, value]) => ` ${name}: ${value};`).join(\"\\n\");\n return `${selector} {\\n${body}\\n}`;\n}\n\n/**\n * Generate `--tempest-*` overrides from a small brand description.\n *\n * Only the families you pass are generated; everything else falls through to the\n * SDK's own tokens, so a theme stays a patch and not a fork of `colors.css`.\n *\n * @example\n * ```ts\n * import { applyTheme, createTheme } from \"tempest-react-sdk\";\n *\n * const theme = createTheme({\n * primary: \"#7c3aed\",\n * radius: \"lg\",\n * chart: [\"#7c3aed\", \"#0ea5e9\", \"#22c55e\", \"#f59e0b\"],\n * });\n *\n * applyTheme(theme);\n * ```\n *\n * @param options - Brand colors plus optional radius / chart / focus-ring tuning.\n * @returns The light and dark token maps and the CSS text that carries them.\n */\nexport function createTheme(options: CreateThemeOptions = {}): GeneratedTheme {\n const {\n primary,\n gray,\n chart,\n radius,\n focusRingAlpha = 0.35,\n selector = \":root\",\n darkSelector = '[data-tempest-theme=\"dark\"]',\n } = options;\n\n const light: Record<string, string> = {};\n const dark: Record<string, string> = {};\n\n if (primary) {\n const lightScale = createColorScale(primary, \"light\");\n const darkScale = createColorScale(primary, \"dark\");\n writeScale(light, \"primary\", lightScale);\n writeScale(dark, \"primary\", darkScale);\n writePrimaryAliases(light, lightScale, \"light\", focusRingAlpha);\n writePrimaryAliases(dark, darkScale, \"dark\", focusRingAlpha);\n }\n\n if (gray) {\n const lightScale = createColorScale(gray, \"light\");\n const darkScale = createColorScale(gray, \"dark\");\n writeScale(light, \"gray\", lightScale);\n writeScale(dark, \"gray\", darkScale);\n writeNeutralAliases(light, lightScale, \"light\");\n writeNeutralAliases(dark, darkScale, \"dark\");\n }\n\n for (const status of [\"success\", \"warning\", \"danger\", \"info\"] as const) {\n const value = options[status];\n if (!value) continue;\n writeStatus(light, status, createColorScale(value, \"light\"), \"light\");\n writeStatus(dark, status, createColorScale(value, \"dark\"), \"dark\");\n }\n\n if (chart?.length) {\n chart.forEach((color, index) => {\n light[`--tempest-chart-${index + 1}`] = color;\n });\n }\n\n if (radius) {\n const steps = typeof radius === \"string\" ? RADIUS_PRESETS[radius] : radius;\n for (const [step, value] of Object.entries(steps)) {\n light[`--tempest-radius-${step}`] = value;\n }\n }\n\n const css = [renderBlock(selector, light), renderBlock(darkSelector, dark)]\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n return { light, dark, css };\n}\n\n/**\n * Contrast ratio of `--tempest-primary-foreground` over the brand color.\n *\n * Exposed so an app (or a test) can assert its own brand clears WCAG AA (4.5) for\n * body text or AA-large (3.0) for button labels, instead of trusting the pick.\n *\n * @param options - The same input given to {@link createTheme}.\n * @returns The ratio, or `null` when no `primary` was provided.\n */\nexport function themeContrast(options: CreateThemeOptions): number | null {\n if (!options.primary) return null;\n const scale = createColorScale(options.primary, \"light\");\n return contrastRatio(scale[500], readableForeground(scale[500]));\n}\n"],"mappings":";;AA+DA,IAAM,IAA8D;CAChE,MAAM;EAAE,IAAI;EAAK,IAAI;EAAK,IAAI;EAAK,IAAI;EAAK,IAAI;EAAK,OAAO;CAAI;CAChE,IAAI;EAAE,IAAI;EAAO,IAAI;EAAO,IAAI;EAAO,IAAI;EAAO,IAAI;EAAO,OAAO;CAAO;CAC3E,IAAI;EAAE,IAAI;EAAO,IAAI;EAAO,IAAI;EAAO,IAAI;EAAQ,IAAI;EAAQ,OAAO;CAAO;CAC7E,IAAI;EAAE,IAAI;EAAO,IAAI;EAAO,IAAI;EAAQ,IAAI;EAAQ,IAAI;EAAQ,OAAO;CAAO;CAC9E,IAAI;EAAE,IAAI;EAAO,IAAI;EAAQ,IAAI;EAAQ,IAAI;EAAQ,IAAI;EAAQ,OAAO;CAAO;CAC/E,MAAM;EAAE,IAAI;EAAO,IAAI;EAAO,IAAI;EAAU,IAAI;EAAU,IAAI;EAAU,OAAO;CAAS;AAC5F,GAEM,IAA2B;CAAC;CAAI;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG;AAEjF,SAAS,EAAW,GAAgC,GAAc,GAAyB;CACvF,KAAK,IAAM,KAAQ,GACf,EAAO,aAAa,EAAK,GAAG,OAAU,EAAM;AAEpD;AAGA,IAAM,IAAmB;AAiBzB,SAAS,EACL,GACA,GACA,GACS;CACT,KAAK,IAAM,KAAQ,GACf,IAAI,EAAc,EAAM,IAAW,EAAM,EAAK,KAAK,GAAkB,OAAO;CAEhF,OAAO,EAAW,EAAW,SAAS;AAC1C;AASA,SAAS,EACL,GACA,GACA,GACA,GACI;CAEJ,AADA,EAAO,uBAAuB,8BAC1B,MAAW,WACX,EAAO,6BAA6B,8BACpC,EAAO,8BAA8B,8BACrC,EAAO,4BAA4B,6BACnC,EAAO,kCAAkC,8BACzC,EAAO,+BACH,yBAAyB,EAAe,GAAO,IAAI;EAAC;EAAK;EAAK;EAAK;CAAG,CAAC,EAAE,OAE7E,EAAO,6BAA6B,8BACpC,EAAO,8BAA8B,8BACrC,EAAO,4BAA4B,8BACnC,EAAO,kCAAkC,8BACzC,EAAO,+BACH,yBAAyB,EAAe,GAAO,KAAK;EAAC;EAAK;EAAK;CAAG,CAAC,EAAE;CAG7E,IAAM,IAAa,EAAmB,EAAM,IAAI;CAGhD,AAFA,EAAO,kCAAkC,GACzC,EAAO,+BAA+B,GACtC,EAAO,gCAAgC,EAAgB,EAAM,MAAM,CAAc;AACrF;AASA,SAAS,EACL,GACA,GACA,GACI;CACJ,AAAI,MAAW,WACX,EAAO,kBAAkB,WACzB,EAAO,uBAAuB,0BAC9B,EAAO,yBAAyB,2BAChC,EAAO,yBAAyB,2BAChC,EAAO,sBAAsB,2BAC7B,EAAO,6BAA6B,2BACpC,EAAO,oBAAoB,2BAC3B,EAAO,0BAA0B,2BACjC,EAAO,2BAA2B,8BAElC,EAAO,kBAAkB,EAAM,KAC/B,EAAO,uBAAuB,2BAC9B,EAAO,yBAAyB,2BAChC,EAAO,yBAAyB,2BAChC,EAAO,sBAAsB,2BAC7B,EAAO,6BAA6B,2BACpC,EAAO,oBAAoB,2BAC3B,EAAO,0BAA0B,2BACjC,EAAO,2BAA2B;AAE1C;AAQA,SAAS,EACL,GACA,GACA,GACA,GACI;CACJ,AAAI,MAAW,WACX,EAAO,aAAa,OAAU,EAAM,MACpC,EAAO,aAAa,EAAK,QAAQ,EAAM,MACvC,EAAO,aAAa,EAAK,QAAQ,EAAM,KACvC,EAAO,aAAa,EAAK,YAAY,EAAM,MAC3C,EAAO,aAAa,EAAK,WAAW,EAAM,SAE1C,EAAO,aAAa,OAAU,EAAM,MACpC,EAAO,aAAa,EAAK,QAAQ,EAAM,MACvC,EAAO,aAAa,EAAK,QAAQ,EAAM,KACvC,EAAO,aAAa,EAAK,YAAY,EAAM,MAC3C,EAAO,aAAa,EAAK,WAAW,EAAM;AAElD;AAEA,SAAS,EAAY,GAAkB,GAAwC;CAC3E,IAAM,IAAU,OAAO,QAAQ,CAAM;CAGrC,OAFI,EAAQ,WAAW,IAAU,KAE1B,GAAG,EAAS,MADN,EAAQ,KAAK,CAAC,GAAM,OAAW,OAAO,EAAK,IAAI,EAAM,EAAE,CAAC,CAAC,KAAK,IAClD,EAAK;AAClC;AAwBA,SAAgB,EAAY,IAA8B,CAAC,GAAmB;CAC1E,IAAM,EACF,YACA,SACA,UACA,WACA,oBAAiB,KACjB,cAAW,SACX,kBAAe,oCACf,GAEE,IAAgC,CAAC,GACjC,IAA+B,CAAC;CAEtC,IAAI,GAAS;EACT,IAAM,IAAa,EAAiB,GAAS,OAAO,GAC9C,IAAY,EAAiB,GAAS,MAAM;EAIlD,AAHA,EAAW,GAAO,WAAW,CAAU,GACvC,EAAW,GAAM,WAAW,CAAS,GACrC,EAAoB,GAAO,GAAY,SAAS,CAAc,GAC9D,EAAoB,GAAM,GAAW,QAAQ,CAAc;CAC/D;CAEA,IAAI,GAAM;EACN,IAAM,IAAa,EAAiB,GAAM,OAAO,GAC3C,IAAY,EAAiB,GAAM,MAAM;EAI/C,AAHA,EAAW,GAAO,QAAQ,CAAU,GACpC,EAAW,GAAM,QAAQ,CAAS,GAClC,EAAoB,GAAO,GAAY,OAAO,GAC9C,EAAoB,GAAM,GAAW,MAAM;CAC/C;CAEA,KAAK,IAAM,KAAU;EAAC;EAAW;EAAW;EAAU;CAAM,GAAY;EACpE,IAAM,IAAQ,EAAQ;EACjB,MACL,EAAY,GAAO,GAAQ,EAAiB,GAAO,OAAO,GAAG,OAAO,GACpE,EAAY,GAAM,GAAQ,EAAiB,GAAO,MAAM,GAAG,MAAM;CACrE;CAQA,IANI,GAAO,UACP,EAAM,SAAS,GAAO,MAAU;EAC5B,EAAM,mBAAmB,IAAQ,OAAO;CAC5C,CAAC,GAGD,GAAQ;EACR,IAAM,IAAQ,OAAO,KAAW,WAAW,EAAe,KAAU;EACpE,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,CAAK,GAC5C,EAAM,oBAAoB,OAAU;CAE5C;CAMA,OAAO;EAAE;EAAO;EAAM,KAJV,CAAC,EAAY,GAAU,CAAK,GAAG,EAAY,GAAc,CAAI,CAAC,CAAC,CACtE,OAAO,OAAO,CAAC,CACf,KAAK,MAEY;CAAI;AAC9B;AAWA,SAAgB,EAAc,GAA4C;CACtE,IAAI,CAAC,EAAQ,SAAS,OAAO;CAC7B,IAAM,IAAQ,EAAiB,EAAQ,SAAS,OAAO;CACvD,OAAO,EAAc,EAAM,MAAM,EAAmB,EAAM,IAAI,CAAC;AACnE"}
1
+ {"version":3,"file":"create-theme.js","names":[],"sources":["../../src/theme/create-theme.ts"],"sourcesContent":["/**\n * Theme factory: turns a handful of brand colors into the full set of\n * `--tempest-*` token overrides, for both light and dark.\n *\n * Rebranding used to mean hand-writing ~167 custom properties (and getting the\n * dark ramp inversion right by hand). `createTheme({ primary: \"#7c3aed\" })`\n * emits the same thing, derived in OKLCH so the ramp stays perceptually even.\n */\nimport {\n contrastRatio,\n createColorScale,\n hexToRgbaString,\n readableForeground,\n type ColorScale,\n type ScaleStep,\n} from \"./color\";\n\n/** Radius presets, applied to the whole `--tempest-radius-*` family at once. */\nexport type ThemeRadius = \"none\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\n/** Status token families that {@link createTheme} can regenerate. */\nexport type ThemeStatus = \"success\" | \"warning\" | \"danger\" | \"info\";\n\n/** Input for {@link createTheme}. Every field is optional — omitted families keep the built-in tokens. */\nexport interface CreateThemeOptions {\n /** Brand color, used as the `500` step of the primary scale. */\n primary?: string;\n /** Neutral color, used as the `500` step of the gray scale (surfaces, borders, text). */\n gray?: string;\n /** Success color (`--tempest-success*`). */\n success?: string;\n /** Warning color (`--tempest-warning*`). */\n warning?: string;\n /** Danger color (`--tempest-danger*`). */\n danger?: string;\n /** Info color (`--tempest-info*`). */\n info?: string;\n /**\n * Categorical series colors for `tempest-react-sdk/charts`, in cycle order.\n * Written to `--tempest-chart-1` … `--tempest-chart-N`, so charts follow the\n * theme instead of a hardcoded palette.\n */\n chart?: string[];\n /** Corner radius scale. A preset name, or explicit per-step values. */\n radius?: ThemeRadius | Partial<Record<\"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\", string>>;\n /** Alpha of `--tempest-focus-ring-color`, derived from the brand color. Default `0.35`. */\n focusRingAlpha?: number;\n /** Selector the light tokens are written under. Default `\":root\"`. */\n selector?: string;\n /** Selector the dark tokens are written under. Default `'[data-tempest-theme=\"dark\"]'`. */\n darkSelector?: string;\n}\n\n/** A generated theme: token maps per color scheme, plus the CSS text that carries them. */\nexport interface GeneratedTheme {\n /** Light-scheme custom properties, without the leading `--`-less names (keys include `--`). */\n light: Record<string, string>;\n /** Dark-scheme custom properties. */\n dark: Record<string, string>;\n /** Both blocks rendered as CSS, ready for {@link applyTheme} or a stylesheet. */\n css: string;\n}\n\nconst RADIUS_PRESETS: Record<ThemeRadius, Record<string, string>> = {\n none: { xs: \"0\", sm: \"0\", md: \"0\", lg: \"0\", xl: \"0\", \"2xl\": \"0\" },\n sm: { xs: \"1px\", sm: \"2px\", md: \"4px\", lg: \"6px\", xl: \"8px\", \"2xl\": \"12px\" },\n md: { xs: \"2px\", sm: \"4px\", md: \"8px\", lg: \"12px\", xl: \"16px\", \"2xl\": \"24px\" },\n lg: { xs: \"4px\", sm: \"6px\", md: \"12px\", lg: \"16px\", xl: \"22px\", \"2xl\": \"32px\" },\n xl: { xs: \"6px\", sm: \"10px\", md: \"16px\", lg: \"24px\", xl: \"32px\", \"2xl\": \"44px\" },\n full: { xs: \"4px\", sm: \"8px\", md: \"9999px\", lg: \"9999px\", xl: \"9999px\", \"2xl\": \"9999px\" },\n};\n\nconst SCALE_STEPS: ScaleStep[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];\n\nfunction writeScale(tokens: Record<string, string>, name: string, scale: ColorScale): void {\n for (const step of SCALE_STEPS) {\n tokens[`--tempest-${name}-${step}`] = scale[step];\n }\n}\n\n/** Minimum contrast for body text, WCAG 2.x AA. */\nconst AA_TEXT_CONTRAST = 4.5;\n\n/**\n * Choose the ramp step for text sitting on the soft tint.\n *\n * A fixed step does not survive an arbitrary brand color: the built-in blue only\n * reaches 4.37:1 at `500` over its own `50` tint, and a generated emerald lands\n * at 4.41:1 at `600` — both fail AA for body text by a hair. So the step is\n * picked by measuring, walking away from the tint until it clears AA, and\n * falling back to the most extreme candidate when even that cannot (a very light\n * or very desaturated brand).\n *\n * @param scale - The generated ramp.\n * @param softStep - Step used as `--tempest-primary-soft`.\n * @param candidates - Steps to try, in order of preference.\n * @returns The first candidate clearing {@link AA_TEXT_CONTRAST}, else the last.\n */\nfunction pickOnSoftStep(\n scale: ColorScale,\n softStep: ScaleStep,\n candidates: ScaleStep[],\n): ScaleStep {\n for (const step of candidates) {\n if (contrastRatio(scale[softStep], scale[step]) >= AA_TEXT_CONTRAST) return step;\n }\n return candidates[candidates.length - 1];\n}\n\n/**\n * Emit the primary aliases for one scheme.\n *\n * The dark scheme walks the ramp the other way (hover is *lighter*, the soft\n * tint is a dark shade, and readable text on that tint is a light shade) — the\n * same inversion the built-in `colors.css` dark block does by hand.\n */\nfunction writePrimaryAliases(\n tokens: Record<string, string>,\n scale: ColorScale,\n scheme: \"light\" | \"dark\",\n focusRingAlpha: number,\n): void {\n tokens[\"--tempest-primary\"] = \"var(--tempest-primary-500)\";\n if (scheme === \"light\") {\n tokens[\"--tempest-primary-hover\"] = \"var(--tempest-primary-600)\";\n tokens[\"--tempest-primary-active\"] = \"var(--tempest-primary-700)\";\n tokens[\"--tempest-primary-soft\"] = \"var(--tempest-primary-50)\";\n tokens[\"--tempest-primary-soft-hover\"] = \"var(--tempest-primary-100)\";\n tokens[\"--tempest-primary-on-soft\"] =\n `var(--tempest-primary-${pickOnSoftStep(scale, 50, [600, 700, 800, 900])})`;\n } else {\n tokens[\"--tempest-primary-hover\"] = \"var(--tempest-primary-400)\";\n tokens[\"--tempest-primary-active\"] = \"var(--tempest-primary-300)\";\n tokens[\"--tempest-primary-soft\"] = \"var(--tempest-primary-100)\";\n tokens[\"--tempest-primary-soft-hover\"] = \"var(--tempest-primary-200)\";\n tokens[\"--tempest-primary-on-soft\"] =\n `var(--tempest-primary-${pickOnSoftStep(scale, 100, [700, 800, 900])})`;\n }\n\n const foreground = readableForeground(scale[500]);\n tokens[\"--tempest-primary-foreground\"] = foreground;\n tokens[\"--tempest-text-on-primary\"] = foreground;\n tokens[\"--tempest-focus-ring-color\"] = hexToRgbaString(scale[500], focusRingAlpha);\n}\n\n/**\n * Emit the neutral surface/border/text aliases from a generated gray scale.\n *\n * `--tempest-bg` is pushed past the ramp on purpose: pure white in light mode and\n * a shade darker than `gray-50` in dark mode, so a raised surface still reads as\n * raised against the page.\n */\nfunction writeNeutralAliases(\n tokens: Record<string, string>,\n scale: ColorScale,\n scheme: \"light\" | \"dark\",\n): void {\n if (scheme === \"light\") {\n tokens[\"--tempest-bg\"] = \"#ffffff\";\n tokens[\"--tempest-surface\"] = \"var(--tempest-gray-50)\";\n tokens[\"--tempest-surface-2\"] = \"var(--tempest-gray-100)\";\n tokens[\"--tempest-surface-3\"] = \"var(--tempest-gray-200)\";\n tokens[\"--tempest-border\"] = \"var(--tempest-gray-200)\";\n tokens[\"--tempest-border-strong\"] = \"var(--tempest-gray-300)\";\n tokens[\"--tempest-text\"] = \"var(--tempest-gray-900)\";\n tokens[\"--tempest-text-muted\"] = \"var(--tempest-gray-600)\";\n tokens[\"--tempest-text-subtle\"] = \"var(--tempest-gray-500)\";\n } else {\n tokens[\"--tempest-bg\"] = scale[50];\n tokens[\"--tempest-surface\"] = \"var(--tempest-gray-100)\";\n tokens[\"--tempest-surface-2\"] = \"var(--tempest-gray-200)\";\n tokens[\"--tempest-surface-3\"] = \"var(--tempest-gray-300)\";\n tokens[\"--tempest-border\"] = \"var(--tempest-gray-300)\";\n tokens[\"--tempest-border-strong\"] = \"var(--tempest-gray-400)\";\n tokens[\"--tempest-text\"] = \"var(--tempest-gray-900)\";\n tokens[\"--tempest-text-muted\"] = \"var(--tempest-gray-700)\";\n tokens[\"--tempest-text-subtle\"] = \"var(--tempest-gray-600)\";\n }\n}\n\n/**\n * Emit one status family (`--tempest-danger`, `-fg`, `-bg`, `-border`, `-solid`).\n *\n * `-fg` is the text shade over `-bg`, so it has to cross the ramp in opposite\n * directions per scheme; `-solid` stays the saturated fill used by badges.\n */\nfunction writeStatus(\n tokens: Record<string, string>,\n name: ThemeStatus,\n scale: ColorScale,\n scheme: \"light\" | \"dark\",\n): void {\n if (scheme === \"light\") {\n tokens[`--tempest-${name}`] = scale[700];\n tokens[`--tempest-${name}-fg`] = scale[800];\n tokens[`--tempest-${name}-bg`] = scale[50];\n tokens[`--tempest-${name}-border`] = scale[200];\n tokens[`--tempest-${name}-solid`] = scale[600];\n } else {\n tokens[`--tempest-${name}`] = scale[700];\n tokens[`--tempest-${name}-fg`] = scale[700];\n tokens[`--tempest-${name}-bg`] = scale[50];\n tokens[`--tempest-${name}-border`] = scale[200];\n tokens[`--tempest-${name}-solid`] = scale[500];\n }\n}\n\nfunction renderBlock(selector: string, tokens: Record<string, string>): string {\n const entries = Object.entries(tokens);\n if (entries.length === 0) return \"\";\n const body = entries.map(([name, value]) => ` ${name}: ${value};`).join(\"\\n\");\n return `${selector} {\\n${body}\\n}`;\n}\n\n/**\n * Generate `--tempest-*` overrides from a small brand description.\n *\n * Only the families you pass are generated; everything else falls through to the\n * SDK's own tokens, so a theme stays a patch and not a fork of `colors.css`.\n *\n * @example\n * ```ts\n * import { applyTheme, createTheme } from \"tempest-react-sdk\";\n *\n * const theme = createTheme({\n * primary: \"#7c3aed\",\n * radius: \"lg\",\n * chart: [\"#7c3aed\", \"#0ea5e9\", \"#22c55e\", \"#f59e0b\"],\n * });\n *\n * applyTheme(theme);\n * ```\n *\n * @param options - Brand colors plus optional radius / chart / focus-ring tuning.\n * @returns The light and dark token maps and the CSS text that carries them.\n */\nexport function createTheme(options: CreateThemeOptions = {}): GeneratedTheme {\n const {\n primary,\n gray,\n chart,\n radius,\n focusRingAlpha = 0.35,\n selector = \":root\",\n darkSelector = '[data-tempest-theme=\"dark\"]',\n } = options;\n\n const light: Record<string, string> = {};\n const dark: Record<string, string> = {};\n\n if (primary) {\n const lightScale = createColorScale(primary, \"light\");\n const darkScale = createColorScale(primary, \"dark\");\n writeScale(light, \"primary\", lightScale);\n writeScale(dark, \"primary\", darkScale);\n writePrimaryAliases(light, lightScale, \"light\", focusRingAlpha);\n writePrimaryAliases(dark, darkScale, \"dark\", focusRingAlpha);\n }\n\n if (gray) {\n // `anchor: false`: a neutral ramp has to keep its tuned lightness curve.\n // Anchoring it at the input compressed both halves and dropped\n // text-muted-on-surface-3 to ~4.2:1 — below AA — which the browser axe\n // sweep caught on the generated themes.\n const lightScale = createColorScale(gray, \"light\", { anchor: false, neutral: true });\n const darkScale = createColorScale(gray, \"dark\", { anchor: false, neutral: true });\n writeScale(light, \"gray\", lightScale);\n writeScale(dark, \"gray\", darkScale);\n writeNeutralAliases(light, lightScale, \"light\");\n writeNeutralAliases(dark, darkScale, \"dark\");\n }\n\n for (const status of [\"success\", \"warning\", \"danger\", \"info\"] as const) {\n const value = options[status];\n if (!value) continue;\n writeStatus(light, status, createColorScale(value, \"light\"), \"light\");\n writeStatus(dark, status, createColorScale(value, \"dark\"), \"dark\");\n }\n\n if (chart?.length) {\n chart.forEach((color, index) => {\n light[`--tempest-chart-${index + 1}`] = color;\n });\n // Declares how many series colors this theme owns. Without it the reader\n // keeps walking into the SDK's built-in `--tempest-chart-7`/`-8`, so a\n // 6-color brand palette silently mixes with two leftover defaults — which\n // is exactly what a 7-series chart would show.\n light[\"--tempest-chart-count\"] = String(chart.length);\n }\n\n if (radius) {\n const steps = typeof radius === \"string\" ? RADIUS_PRESETS[radius] : radius;\n for (const [step, value] of Object.entries(steps)) {\n light[`--tempest-radius-${step}`] = value;\n }\n }\n\n const css = [renderBlock(selector, light), renderBlock(darkSelector, dark)]\n .filter(Boolean)\n .join(\"\\n\\n\");\n\n return { light, dark, css };\n}\n\n/**\n * Contrast ratio of `--tempest-primary-foreground` over the brand color.\n *\n * Exposed so an app (or a test) can assert its own brand clears WCAG AA (4.5) for\n * body text or AA-large (3.0) for button labels, instead of trusting the pick.\n *\n * @param options - The same input given to {@link createTheme}.\n * @returns The ratio, or `null` when no `primary` was provided.\n */\nexport function themeContrast(options: CreateThemeOptions): number | null {\n if (!options.primary) return null;\n const scale = createColorScale(options.primary, \"light\");\n return contrastRatio(scale[500], readableForeground(scale[500]));\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ function e(e,t){return e==null&&t==null?0:e==null?-1:t==null?1:typeof e==`number`&&typeof t==`number`?e-t:e instanceof Date&&t instanceof Date?e.getTime()-t.getTime():typeof e==`boolean`&&typeof t==`boolean`?Number(e)-Number(t):String(e).localeCompare(String(t),void 0,{numeric:!0})}exports.compareValues=e;
2
+ //# sourceMappingURL=compare-values.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-values.cjs","names":[],"sources":["../../src/utils/compare-values.ts"],"sourcesContent":["/**\n * Compare two arbitrary cell values with stable, type-aware ordering.\n *\n * The comparator every table in the SDK sorts with, so `DataTable` and\n * `VirtualTable` agree on what \"sorted\" means. The type-aware branches exist\n * because the naive `String(a) < String(b)` gets the two most common cases wrong:\n * numbers would sort `10` before `9`, and dates would sort by their formatted\n * text. Strings fall back to `localeCompare` with `numeric: true`, so `\"item 2\"`\n * lands before `\"item 10\"`.\n *\n * `null` and `undefined` sort first, together — a missing value is not smaller\n * than another missing value.\n *\n * @param a - First value.\n * @param b - Second value.\n * @returns Negative when `a < b`, positive when `a > b`, zero when equal.\n */\nexport function compareValues(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();\n if (typeof a === \"boolean\" && typeof b === \"boolean\") return Number(a) - Number(b);\n return String(a).localeCompare(String(b), undefined, { numeric: true });\n}\n"],"mappings":"AAiBA,SAAgB,EAAc,EAAY,EAAoB,CAO1D,OANI,GAAK,MAAQ,GAAK,KAAa,EAC/B,GAAK,KAAa,GAClB,GAAK,KAAa,EAClB,OAAO,GAAM,UAAY,OAAO,GAAM,SAAiB,EAAI,EAC3D,aAAa,MAAQ,aAAa,KAAa,EAAE,QAAQ,EAAI,EAAE,QAAQ,EACvE,OAAO,GAAM,WAAa,OAAO,GAAM,UAAkB,OAAO,CAAC,EAAI,OAAO,CAAC,EAC1E,OAAO,CAAC,CAAC,CAAC,cAAc,OAAO,CAAC,EAAG,IAAA,GAAW,CAAE,QAAS,EAAK,CAAC,CAC1E"}
@@ -0,0 +1,8 @@
1
+ //#region src/utils/compare-values.ts
2
+ function e(e, t) {
3
+ return e == null && t == null ? 0 : e == null ? -1 : t == null ? 1 : typeof e == "number" && typeof t == "number" ? e - t : e instanceof Date && t instanceof Date ? e.getTime() - t.getTime() : typeof e == "boolean" && typeof t == "boolean" ? Number(e) - Number(t) : String(e).localeCompare(String(t), void 0, { numeric: !0 });
4
+ }
5
+ //#endregion
6
+ export { e as compareValues };
7
+
8
+ //# sourceMappingURL=compare-values.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare-values.js","names":[],"sources":["../../src/utils/compare-values.ts"],"sourcesContent":["/**\n * Compare two arbitrary cell values with stable, type-aware ordering.\n *\n * The comparator every table in the SDK sorts with, so `DataTable` and\n * `VirtualTable` agree on what \"sorted\" means. The type-aware branches exist\n * because the naive `String(a) < String(b)` gets the two most common cases wrong:\n * numbers would sort `10` before `9`, and dates would sort by their formatted\n * text. Strings fall back to `localeCompare` with `numeric: true`, so `\"item 2\"`\n * lands before `\"item 10\"`.\n *\n * `null` and `undefined` sort first, together — a missing value is not smaller\n * than another missing value.\n *\n * @param a - First value.\n * @param b - Second value.\n * @returns Negative when `a < b`, positive when `a > b`, zero when equal.\n */\nexport function compareValues(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();\n if (typeof a === \"boolean\" && typeof b === \"boolean\") return Number(a) - Number(b);\n return String(a).localeCompare(String(b), undefined, { numeric: true });\n}\n"],"mappings":";AAiBA,SAAgB,EAAc,GAAY,GAAoB;CAO1D,OANI,KAAK,QAAQ,KAAK,OAAa,IAC/B,KAAK,OAAa,KAClB,KAAK,OAAa,IAClB,OAAO,KAAM,YAAY,OAAO,KAAM,WAAiB,IAAI,IAC3D,aAAa,QAAQ,aAAa,OAAa,EAAE,QAAQ,IAAI,EAAE,QAAQ,IACvE,OAAO,KAAM,aAAa,OAAO,KAAM,YAAkB,OAAO,CAAC,IAAI,OAAO,CAAC,IAC1E,OAAO,CAAC,CAAC,CAAC,cAAc,OAAO,CAAC,GAAG,KAAA,GAAW,EAAE,SAAS,GAAK,CAAC;AAC1E"}
@@ -1,2 +1,2 @@
1
- let e=require("react");var t={video:{facingMode:{ideal:`environment`},width:{ideal:1920},height:{ideal:1080}},audio:!1};function n(e){if(typeof window>`u`)return{kind:`unsupported`,message:`Camera is unavailable in this environment.`};if(!window.isSecureContext)return{kind:`insecure`,message:`Camera access requires a secure (HTTPS) connection.`};if(e instanceof DOMException)switch(e.name){case`NotAllowedError`:case`SecurityError`:return{kind:`permission-denied`,message:`Camera permission denied. Enable access in your browser settings.`};case`NotFoundError`:case`OverconstrainedError`:return{kind:`no-camera`,message:`No camera available on this device.`};case`NotReadableError`:case`AbortError`:return{kind:`in-use`,message:`The camera is in use by another app. Close it and try again.`}}return{kind:`unknown`,message:e instanceof Error?e.message:`Unexpected error while accessing the camera.`}}function r(r={}){let[i,a]=(0,e.useState)(`loading`),[o,s]=(0,e.useState)(null),[c,l]=(0,e.useState)(0),u=(0,e.useRef)(null),d=(0,e.useRef)(r.constraints??t);return d.current=r.constraints??t,(0,e.useEffect)(()=>{let e=!1,t=null,r=null;async function i(){if(a(`loading`),s(null),typeof navigator>`u`||!navigator.mediaDevices||typeof navigator.mediaDevices.getUserMedia!=`function`){e||(s(typeof window<`u`&&!window.isSecureContext?n(null):{kind:`unsupported`,message:`Camera capture is not supported in this browser.`}),a(`error`));return}try{if(t=await navigator.mediaDevices.getUserMedia(d.current),e){t.getTracks().forEach(e=>e.stop());return}let n=u.current;if(!n){t.getTracks().forEach(e=>e.stop());return}r=n,n.srcObject=t,await n.play().catch(()=>void 0),e||a(`ready`)}catch(t){e||(s(n(t)),a(`error`))}}return i(),()=>{e=!0,t&&t.getTracks().forEach(e=>e.stop()),r&&(r.srcObject=null)}},[c]),{status:i,error:o,videoRef:u,retry:()=>l(e=>e+1)}}exports.useCameraStream=r;
1
+ let e=require("react");var t={video:{facingMode:{ideal:`environment`},width:{ideal:1920},height:{ideal:1080}},audio:!1};function n(e){if(typeof window>`u`)return{kind:`unsupported`,message:`Camera is unavailable in this environment.`};if(!window.isSecureContext)return{kind:`insecure`,message:`Camera access requires a secure (HTTPS) connection.`};if(e instanceof DOMException)switch(e.name){case`NotAllowedError`:case`SecurityError`:return{kind:`permission-denied`,message:`Camera permission denied. Enable access in your browser settings.`};case`NotFoundError`:case`OverconstrainedError`:return{kind:`no-camera`,message:`No camera available on this device.`};case`NotReadableError`:case`AbortError`:return{kind:`in-use`,message:`The camera is in use by another app. Close it and try again.`}}return{kind:`unknown`,message:e instanceof Error?e.message:`Unexpected error while accessing the camera.`}}function r(r={}){let[i,a]=(0,e.useState)(`loading`),[o,s]=(0,e.useState)(null),[c,l]=(0,e.useState)(0),u=(0,e.useRef)(null),d=(0,e.useRef)(r.constraints??t);return(0,e.useEffect)(()=>{d.current=r.constraints??t},[r.constraints]),(0,e.useEffect)(()=>{let e=!1,t=null,r=null;async function i(){if(a(`loading`),s(null),typeof navigator>`u`||!navigator.mediaDevices||typeof navigator.mediaDevices.getUserMedia!=`function`){e||(s(typeof window<`u`&&!window.isSecureContext?n(null):{kind:`unsupported`,message:`Camera capture is not supported in this browser.`}),a(`error`));return}try{if(t=await navigator.mediaDevices.getUserMedia(d.current),e){t.getTracks().forEach(e=>e.stop());return}let n=u.current;if(!n){t.getTracks().forEach(e=>e.stop());return}r=n,n.srcObject=t,await n.play().catch(()=>void 0),e||a(`ready`)}catch(t){e||(s(n(t)),a(`error`))}}return i(),()=>{e=!0,t&&t.getTracks().forEach(e=>e.stop()),r&&(r.srcObject=null)}},[c]),{status:i,error:o,videoRef:u,retry:()=>l(e=>e+1)}}exports.useCameraStream=r;
2
2
  //# sourceMappingURL=use-camera-stream.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-camera-stream.cjs","names":[],"sources":["../../src/vision/use-camera-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\n\n/** Lifecycle status of the camera stream. */\nexport type CameraStreamStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Classified reason a camera stream could not be acquired. */\nexport type CameraStreamErrorKind =\n \"unsupported\" | \"permission-denied\" | \"no-camera\" | \"in-use\" | \"insecure\" | \"unknown\";\n\n/** A classified camera error with a human-readable, English message. */\nexport interface CameraStreamError {\n kind: CameraStreamErrorKind;\n message: string;\n}\n\n/** Options for {@link useCameraStream}. */\nexport interface UseCameraStreamOptions {\n /**\n * Constraints passed to `getUserMedia`. Defaults to the rear\n * (\"environment\") camera at Full-HD ideal resolution with audio off.\n * Read when the stream (re)starts — change it and call `retry()` to apply.\n */\n constraints?: MediaStreamConstraints;\n}\n\n/** Value returned by {@link useCameraStream}. */\nexport interface UseCameraStreamApi {\n /** Current lifecycle status. */\n status: CameraStreamStatus;\n /** The classified error, or `null` while not in the `error` status. */\n error: CameraStreamError | null;\n /** Attach to a `<video ref={…} />`. The stream is wired to it once ready. */\n videoRef: RefObject<HTMLVideoElement | null>;\n /** Manually re-attempt after an error (e.g. the user changed permissions). */\n retry: () => void;\n}\n\n/** Rear-camera Full-HD defaults used when no `constraints` are supplied. */\nconst DEFAULT_CONSTRAINTS: MediaStreamConstraints = {\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n audio: false,\n};\n\n/**\n * Map an unknown failure into a {@link CameraStreamError}. Secure-context and\n * environment checks run first (they are the reason `getUserMedia` is missing\n * or rejects), then the `DOMException.name` is mapped to a stable `kind`.\n */\nfunction classifyError(err: unknown): CameraStreamError {\n if (typeof window === \"undefined\") {\n return { kind: \"unsupported\", message: \"Camera is unavailable in this environment.\" };\n }\n if (!window.isSecureContext) {\n return {\n kind: \"insecure\",\n message: \"Camera access requires a secure (HTTPS) connection.\",\n };\n }\n if (err instanceof DOMException) {\n switch (err.name) {\n case \"NotAllowedError\":\n case \"SecurityError\":\n return {\n kind: \"permission-denied\",\n message: \"Camera permission denied. Enable access in your browser settings.\",\n };\n case \"NotFoundError\":\n case \"OverconstrainedError\":\n return {\n kind: \"no-camera\",\n message: \"No camera available on this device.\",\n };\n case \"NotReadableError\":\n case \"AbortError\":\n return {\n kind: \"in-use\",\n message: \"The camera is in use by another app. Close it and try again.\",\n };\n }\n }\n return {\n kind: \"unknown\",\n message:\n err instanceof Error ? err.message : \"Unexpected error while accessing the camera.\",\n };\n}\n\n/**\n * Acquire a `MediaStream` via `getUserMedia`, attach it to a `<video>` element,\n * and expose status/error so the page can render permission and error states.\n * The stream is automatically released on unmount or retry.\n *\n * Defaults to the rear (\"environment\") camera; desktops fall back to whatever\n * single camera they expose. Pass `options.constraints` to override.\n *\n * Implementation notes:\n * - Cleanup detaches the stream from a *snapshotted* video node, so it releases\n * the same element it attached to even if the page remounts the `<video>`.\n * - When `getUserMedia` is missing, an insecure context is the usual cause, so\n * the hook prefers that (actionable) error; otherwise it reports `unsupported`.\n * - `video.play()` rejections are swallowed: autoplay may be blocked, but the\n * user gesture that opened the camera usually counts and playback resumes on\n * the next interaction.\n *\n * @param options - optional configuration (see {@link UseCameraStreamOptions}).\n * @returns The stream status, classified error, a `videoRef` to attach, and a\n * `retry()` to re-attempt acquisition.\n */\nexport function useCameraStream(options: UseCameraStreamOptions = {}): UseCameraStreamApi {\n const [status, setStatus] = useState<CameraStreamStatus>(\"loading\");\n const [error, setError] = useState<CameraStreamError | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const constraintsRef = useRef<MediaStreamConstraints>(\n options.constraints ?? DEFAULT_CONSTRAINTS,\n );\n constraintsRef.current = options.constraints ?? DEFAULT_CONSTRAINTS;\n\n useEffect(() => {\n let cancelled = false;\n let stream: MediaStream | null = null;\n let attachedVideo: HTMLVideoElement | null = null;\n\n async function start(): Promise<void> {\n setStatus(\"loading\");\n setError(null);\n\n if (\n typeof navigator === \"undefined\" ||\n !navigator.mediaDevices ||\n typeof navigator.mediaDevices.getUserMedia !== \"function\"\n ) {\n if (!cancelled) {\n setError(\n typeof window !== \"undefined\" && !window.isSecureContext\n ? classifyError(null)\n : {\n kind: \"unsupported\",\n message: \"Camera capture is not supported in this browser.\",\n },\n );\n setStatus(\"error\");\n }\n return;\n }\n\n try {\n stream = await navigator.mediaDevices.getUserMedia(constraintsRef.current);\n if (cancelled) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n const video = videoRef.current;\n if (!video) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n attachedVideo = video;\n video.srcObject = stream;\n await video.play().catch(() => undefined);\n if (!cancelled) setStatus(\"ready\");\n } catch (err) {\n if (!cancelled) {\n setError(classifyError(err));\n setStatus(\"error\");\n }\n }\n }\n\n void start();\n\n return () => {\n cancelled = true;\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n if (attachedVideo) {\n attachedVideo.srcObject = null;\n }\n };\n }, [retryToken]);\n\n return {\n status,\n error,\n videoRef,\n retry: () => setRetryToken((n) => n + 1),\n };\n}\n"],"mappings":"uBAsCA,IAAM,EAA8C,CAChD,MAAO,CACH,WAAY,CAAE,MAAO,aAAc,EACnC,MAAO,CAAE,MAAO,IAAK,EACrB,OAAQ,CAAE,MAAO,IAAK,CAC1B,EACA,MAAO,EACX,EAOA,SAAS,EAAc,EAAiC,CACpD,GAAI,OAAO,OAAW,IAClB,MAAO,CAAE,KAAM,cAAe,QAAS,4CAA6C,EAExF,GAAI,CAAC,OAAO,gBACR,MAAO,CACH,KAAM,WACN,QAAS,qDACb,EAEJ,GAAI,aAAe,aACf,OAAQ,EAAI,KAAZ,CACI,IAAK,kBACL,IAAK,gBACD,MAAO,CACH,KAAM,oBACN,QAAS,mEACb,EACJ,IAAK,gBACL,IAAK,uBACD,MAAO,CACH,KAAM,YACN,QAAS,qCACb,EACJ,IAAK,mBACL,IAAK,aACD,MAAO,CACH,KAAM,SACN,QAAS,8DACb,CACR,CAEJ,MAAO,CACH,KAAM,UACN,QACI,aAAe,MAAQ,EAAI,QAAU,8CAC7C,CACJ,CAuBA,SAAgB,EAAgB,EAAkC,CAAC,EAAuB,CACtF,GAAM,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAA0C,SAAS,EAC5D,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAA+C,IAAI,EAC3D,CAAC,EAAY,IAAA,EAAA,EAAA,SAAA,CAA0B,CAAC,EACxC,GAAA,EAAA,EAAA,OAAA,CAA2C,IAAI,EAC/C,GAAA,EAAA,EAAA,OAAA,CACF,EAAQ,aAAe,CAC3B,EAmEA,MAlEA,GAAe,QAAU,EAAQ,aAAe,GAEhD,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAY,GACZ,EAA6B,KAC7B,EAAyC,KAE7C,eAAe,GAAuB,CAIlC,GAHA,EAAU,SAAS,EACnB,EAAS,IAAI,EAGT,OAAO,UAAc,KACrB,CAAC,UAAU,cACX,OAAO,UAAU,aAAa,cAAiB,WACjD,CACO,IACD,EACI,OAAO,OAAW,KAAe,CAAC,OAAO,gBACnC,EAAc,IAAI,EAClB,CACI,KAAM,cACN,QAAS,kDACb,CACV,EACA,EAAU,OAAO,GAErB,MACJ,CAEA,GAAI,CAEA,GADA,EAAS,MAAM,UAAU,aAAa,aAAa,EAAe,OAAO,EACrE,EAAW,CACX,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAClD,MACJ,CACA,IAAM,EAAQ,EAAS,QACvB,GAAI,CAAC,EAAO,CACR,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAClD,MACJ,CACA,EAAgB,EAChB,EAAM,UAAY,EAClB,MAAM,EAAM,KAAK,CAAC,CAAC,UAAY,IAAA,EAAS,EACnC,GAAW,EAAU,OAAO,CACrC,OAAS,EAAK,CACL,IACD,EAAS,EAAc,CAAG,CAAC,EAC3B,EAAU,OAAO,EAEzB,CACJ,CAIA,OAFA,EAAW,MAEE,CACT,EAAY,GACR,GACA,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAElD,IACA,EAAc,UAAY,KAElC,CACJ,EAAG,CAAC,CAAU,CAAC,EAER,CACH,SACA,QACA,WACA,UAAa,EAAe,GAAM,EAAI,CAAC,CAC3C,CACJ"}
1
+ {"version":3,"file":"use-camera-stream.cjs","names":[],"sources":["../../src/vision/use-camera-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\n\n/** Lifecycle status of the camera stream. */\nexport type CameraStreamStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Classified reason a camera stream could not be acquired. */\nexport type CameraStreamErrorKind =\n \"unsupported\" | \"permission-denied\" | \"no-camera\" | \"in-use\" | \"insecure\" | \"unknown\";\n\n/** A classified camera error with a human-readable, English message. */\nexport interface CameraStreamError {\n kind: CameraStreamErrorKind;\n message: string;\n}\n\n/** Options for {@link useCameraStream}. */\nexport interface UseCameraStreamOptions {\n /**\n * Constraints passed to `getUserMedia`. Defaults to the rear\n * (\"environment\") camera at Full-HD ideal resolution with audio off.\n * Read when the stream (re)starts — change it and call `retry()` to apply.\n */\n constraints?: MediaStreamConstraints;\n}\n\n/** Value returned by {@link useCameraStream}. */\nexport interface UseCameraStreamApi {\n /** Current lifecycle status. */\n status: CameraStreamStatus;\n /** The classified error, or `null` while not in the `error` status. */\n error: CameraStreamError | null;\n /** Attach to a `<video ref={…} />`. The stream is wired to it once ready. */\n videoRef: RefObject<HTMLVideoElement | null>;\n /** Manually re-attempt after an error (e.g. the user changed permissions). */\n retry: () => void;\n}\n\n/** Rear-camera Full-HD defaults used when no `constraints` are supplied. */\nconst DEFAULT_CONSTRAINTS: MediaStreamConstraints = {\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n audio: false,\n};\n\n/**\n * Map an unknown failure into a {@link CameraStreamError}. Secure-context and\n * environment checks run first (they are the reason `getUserMedia` is missing\n * or rejects), then the `DOMException.name` is mapped to a stable `kind`.\n */\nfunction classifyError(err: unknown): CameraStreamError {\n if (typeof window === \"undefined\") {\n return { kind: \"unsupported\", message: \"Camera is unavailable in this environment.\" };\n }\n if (!window.isSecureContext) {\n return {\n kind: \"insecure\",\n message: \"Camera access requires a secure (HTTPS) connection.\",\n };\n }\n if (err instanceof DOMException) {\n switch (err.name) {\n case \"NotAllowedError\":\n case \"SecurityError\":\n return {\n kind: \"permission-denied\",\n message: \"Camera permission denied. Enable access in your browser settings.\",\n };\n case \"NotFoundError\":\n case \"OverconstrainedError\":\n return {\n kind: \"no-camera\",\n message: \"No camera available on this device.\",\n };\n case \"NotReadableError\":\n case \"AbortError\":\n return {\n kind: \"in-use\",\n message: \"The camera is in use by another app. Close it and try again.\",\n };\n }\n }\n return {\n kind: \"unknown\",\n message:\n err instanceof Error ? err.message : \"Unexpected error while accessing the camera.\",\n };\n}\n\n/**\n * Acquire a `MediaStream` via `getUserMedia`, attach it to a `<video>` element,\n * and expose status/error so the page can render permission and error states.\n * The stream is automatically released on unmount or retry.\n *\n * Defaults to the rear (\"environment\") camera; desktops fall back to whatever\n * single camera they expose. Pass `options.constraints` to override.\n *\n * Implementation notes:\n * - Cleanup detaches the stream from a *snapshotted* video node, so it releases\n * the same element it attached to even if the page remounts the `<video>`.\n * - When `getUserMedia` is missing, an insecure context is the usual cause, so\n * the hook prefers that (actionable) error; otherwise it reports `unsupported`.\n * - `video.play()` rejections are swallowed: autoplay may be blocked, but the\n * user gesture that opened the camera usually counts and playback resumes on\n * the next interaction.\n *\n * @param options - optional configuration (see {@link UseCameraStreamOptions}).\n * @returns The stream status, classified error, a `videoRef` to attach, and a\n * `retry()` to re-attempt acquisition.\n */\nexport function useCameraStream(options: UseCameraStreamOptions = {}): UseCameraStreamApi {\n const [status, setStatus] = useState<CameraStreamStatus>(\"loading\");\n const [error, setError] = useState<CameraStreamError | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const constraintsRef = useRef<MediaStreamConstraints>(\n options.constraints ?? DEFAULT_CONSTRAINTS,\n );\n useEffect(() => {\n constraintsRef.current = options.constraints ?? DEFAULT_CONSTRAINTS;\n }, [options.constraints]);\n\n useEffect(() => {\n let cancelled = false;\n let stream: MediaStream | null = null;\n let attachedVideo: HTMLVideoElement | null = null;\n\n async function start(): Promise<void> {\n setStatus(\"loading\");\n setError(null);\n\n if (\n typeof navigator === \"undefined\" ||\n !navigator.mediaDevices ||\n typeof navigator.mediaDevices.getUserMedia !== \"function\"\n ) {\n if (!cancelled) {\n setError(\n typeof window !== \"undefined\" && !window.isSecureContext\n ? classifyError(null)\n : {\n kind: \"unsupported\",\n message: \"Camera capture is not supported in this browser.\",\n },\n );\n setStatus(\"error\");\n }\n return;\n }\n\n try {\n stream = await navigator.mediaDevices.getUserMedia(constraintsRef.current);\n if (cancelled) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n const video = videoRef.current;\n if (!video) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n attachedVideo = video;\n video.srcObject = stream;\n await video.play().catch(() => undefined);\n if (!cancelled) setStatus(\"ready\");\n } catch (err) {\n if (!cancelled) {\n setError(classifyError(err));\n setStatus(\"error\");\n }\n }\n }\n\n void start();\n\n return () => {\n cancelled = true;\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n if (attachedVideo) {\n attachedVideo.srcObject = null;\n }\n };\n }, [retryToken]);\n\n return {\n status,\n error,\n videoRef,\n retry: () => setRetryToken((n) => n + 1),\n };\n}\n"],"mappings":"uBAsCA,IAAM,EAA8C,CAChD,MAAO,CACH,WAAY,CAAE,MAAO,aAAc,EACnC,MAAO,CAAE,MAAO,IAAK,EACrB,OAAQ,CAAE,MAAO,IAAK,CAC1B,EACA,MAAO,EACX,EAOA,SAAS,EAAc,EAAiC,CACpD,GAAI,OAAO,OAAW,IAClB,MAAO,CAAE,KAAM,cAAe,QAAS,4CAA6C,EAExF,GAAI,CAAC,OAAO,gBACR,MAAO,CACH,KAAM,WACN,QAAS,qDACb,EAEJ,GAAI,aAAe,aACf,OAAQ,EAAI,KAAZ,CACI,IAAK,kBACL,IAAK,gBACD,MAAO,CACH,KAAM,oBACN,QAAS,mEACb,EACJ,IAAK,gBACL,IAAK,uBACD,MAAO,CACH,KAAM,YACN,QAAS,qCACb,EACJ,IAAK,mBACL,IAAK,aACD,MAAO,CACH,KAAM,SACN,QAAS,8DACb,CACR,CAEJ,MAAO,CACH,KAAM,UACN,QACI,aAAe,MAAQ,EAAI,QAAU,8CAC7C,CACJ,CAuBA,SAAgB,EAAgB,EAAkC,CAAC,EAAuB,CACtF,GAAM,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAA0C,SAAS,EAC5D,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAA+C,IAAI,EAC3D,CAAC,EAAY,IAAA,EAAA,EAAA,SAAA,CAA0B,CAAC,EACxC,GAAA,EAAA,EAAA,OAAA,CAA2C,IAAI,EAC/C,GAAA,EAAA,EAAA,OAAA,CACF,EAAQ,aAAe,CAC3B,EAqEA,OApEA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAe,QAAU,EAAQ,aAAe,CACpD,EAAG,CAAC,EAAQ,WAAW,CAAC,GAExB,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAY,GACZ,EAA6B,KAC7B,EAAyC,KAE7C,eAAe,GAAuB,CAIlC,GAHA,EAAU,SAAS,EACnB,EAAS,IAAI,EAGT,OAAO,UAAc,KACrB,CAAC,UAAU,cACX,OAAO,UAAU,aAAa,cAAiB,WACjD,CACO,IACD,EACI,OAAO,OAAW,KAAe,CAAC,OAAO,gBACnC,EAAc,IAAI,EAClB,CACI,KAAM,cACN,QAAS,kDACb,CACV,EACA,EAAU,OAAO,GAErB,MACJ,CAEA,GAAI,CAEA,GADA,EAAS,MAAM,UAAU,aAAa,aAAa,EAAe,OAAO,EACrE,EAAW,CACX,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAClD,MACJ,CACA,IAAM,EAAQ,EAAS,QACvB,GAAI,CAAC,EAAO,CACR,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAClD,MACJ,CACA,EAAgB,EAChB,EAAM,UAAY,EAClB,MAAM,EAAM,KAAK,CAAC,CAAC,UAAY,IAAA,EAAS,EACnC,GAAW,EAAU,OAAO,CACrC,OAAS,EAAK,CACL,IACD,EAAS,EAAc,CAAG,CAAC,EAC3B,EAAU,OAAO,EAEzB,CACJ,CAIA,OAFA,EAAW,MAEE,CACT,EAAY,GACR,GACA,EAAO,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAElD,IACA,EAAc,UAAY,KAElC,CACJ,EAAG,CAAC,CAAU,CAAC,EAER,CACH,SACA,QACA,WACA,UAAa,EAAe,GAAM,EAAI,CAAC,CAC3C,CACJ"}
@@ -41,7 +41,9 @@ function i(e) {
41
41
  }
42
42
  function a(a = {}) {
43
43
  let [o, s] = n("loading"), [c, l] = n(null), [u, d] = n(0), f = t(null), p = t(a.constraints ?? r);
44
- return p.current = a.constraints ?? r, e(() => {
44
+ return e(() => {
45
+ p.current = a.constraints ?? r;
46
+ }, [a.constraints]), e(() => {
45
47
  let e = !1, t = null, n = null;
46
48
  async function r() {
47
49
  if (s("loading"), l(null), typeof navigator > "u" || !navigator.mediaDevices || typeof navigator.mediaDevices.getUserMedia != "function") {
@@ -1 +1 @@
1
- {"version":3,"file":"use-camera-stream.js","names":[],"sources":["../../src/vision/use-camera-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\n\n/** Lifecycle status of the camera stream. */\nexport type CameraStreamStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Classified reason a camera stream could not be acquired. */\nexport type CameraStreamErrorKind =\n \"unsupported\" | \"permission-denied\" | \"no-camera\" | \"in-use\" | \"insecure\" | \"unknown\";\n\n/** A classified camera error with a human-readable, English message. */\nexport interface CameraStreamError {\n kind: CameraStreamErrorKind;\n message: string;\n}\n\n/** Options for {@link useCameraStream}. */\nexport interface UseCameraStreamOptions {\n /**\n * Constraints passed to `getUserMedia`. Defaults to the rear\n * (\"environment\") camera at Full-HD ideal resolution with audio off.\n * Read when the stream (re)starts — change it and call `retry()` to apply.\n */\n constraints?: MediaStreamConstraints;\n}\n\n/** Value returned by {@link useCameraStream}. */\nexport interface UseCameraStreamApi {\n /** Current lifecycle status. */\n status: CameraStreamStatus;\n /** The classified error, or `null` while not in the `error` status. */\n error: CameraStreamError | null;\n /** Attach to a `<video ref={…} />`. The stream is wired to it once ready. */\n videoRef: RefObject<HTMLVideoElement | null>;\n /** Manually re-attempt after an error (e.g. the user changed permissions). */\n retry: () => void;\n}\n\n/** Rear-camera Full-HD defaults used when no `constraints` are supplied. */\nconst DEFAULT_CONSTRAINTS: MediaStreamConstraints = {\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n audio: false,\n};\n\n/**\n * Map an unknown failure into a {@link CameraStreamError}. Secure-context and\n * environment checks run first (they are the reason `getUserMedia` is missing\n * or rejects), then the `DOMException.name` is mapped to a stable `kind`.\n */\nfunction classifyError(err: unknown): CameraStreamError {\n if (typeof window === \"undefined\") {\n return { kind: \"unsupported\", message: \"Camera is unavailable in this environment.\" };\n }\n if (!window.isSecureContext) {\n return {\n kind: \"insecure\",\n message: \"Camera access requires a secure (HTTPS) connection.\",\n };\n }\n if (err instanceof DOMException) {\n switch (err.name) {\n case \"NotAllowedError\":\n case \"SecurityError\":\n return {\n kind: \"permission-denied\",\n message: \"Camera permission denied. Enable access in your browser settings.\",\n };\n case \"NotFoundError\":\n case \"OverconstrainedError\":\n return {\n kind: \"no-camera\",\n message: \"No camera available on this device.\",\n };\n case \"NotReadableError\":\n case \"AbortError\":\n return {\n kind: \"in-use\",\n message: \"The camera is in use by another app. Close it and try again.\",\n };\n }\n }\n return {\n kind: \"unknown\",\n message:\n err instanceof Error ? err.message : \"Unexpected error while accessing the camera.\",\n };\n}\n\n/**\n * Acquire a `MediaStream` via `getUserMedia`, attach it to a `<video>` element,\n * and expose status/error so the page can render permission and error states.\n * The stream is automatically released on unmount or retry.\n *\n * Defaults to the rear (\"environment\") camera; desktops fall back to whatever\n * single camera they expose. Pass `options.constraints` to override.\n *\n * Implementation notes:\n * - Cleanup detaches the stream from a *snapshotted* video node, so it releases\n * the same element it attached to even if the page remounts the `<video>`.\n * - When `getUserMedia` is missing, an insecure context is the usual cause, so\n * the hook prefers that (actionable) error; otherwise it reports `unsupported`.\n * - `video.play()` rejections are swallowed: autoplay may be blocked, but the\n * user gesture that opened the camera usually counts and playback resumes on\n * the next interaction.\n *\n * @param options - optional configuration (see {@link UseCameraStreamOptions}).\n * @returns The stream status, classified error, a `videoRef` to attach, and a\n * `retry()` to re-attempt acquisition.\n */\nexport function useCameraStream(options: UseCameraStreamOptions = {}): UseCameraStreamApi {\n const [status, setStatus] = useState<CameraStreamStatus>(\"loading\");\n const [error, setError] = useState<CameraStreamError | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const constraintsRef = useRef<MediaStreamConstraints>(\n options.constraints ?? DEFAULT_CONSTRAINTS,\n );\n constraintsRef.current = options.constraints ?? DEFAULT_CONSTRAINTS;\n\n useEffect(() => {\n let cancelled = false;\n let stream: MediaStream | null = null;\n let attachedVideo: HTMLVideoElement | null = null;\n\n async function start(): Promise<void> {\n setStatus(\"loading\");\n setError(null);\n\n if (\n typeof navigator === \"undefined\" ||\n !navigator.mediaDevices ||\n typeof navigator.mediaDevices.getUserMedia !== \"function\"\n ) {\n if (!cancelled) {\n setError(\n typeof window !== \"undefined\" && !window.isSecureContext\n ? classifyError(null)\n : {\n kind: \"unsupported\",\n message: \"Camera capture is not supported in this browser.\",\n },\n );\n setStatus(\"error\");\n }\n return;\n }\n\n try {\n stream = await navigator.mediaDevices.getUserMedia(constraintsRef.current);\n if (cancelled) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n const video = videoRef.current;\n if (!video) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n attachedVideo = video;\n video.srcObject = stream;\n await video.play().catch(() => undefined);\n if (!cancelled) setStatus(\"ready\");\n } catch (err) {\n if (!cancelled) {\n setError(classifyError(err));\n setStatus(\"error\");\n }\n }\n }\n\n void start();\n\n return () => {\n cancelled = true;\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n if (attachedVideo) {\n attachedVideo.srcObject = null;\n }\n };\n }, [retryToken]);\n\n return {\n status,\n error,\n videoRef,\n retry: () => setRetryToken((n) => n + 1),\n };\n}\n"],"mappings":";;AAsCA,IAAM,IAA8C;CAChD,OAAO;EACH,YAAY,EAAE,OAAO,cAAc;EACnC,OAAO,EAAE,OAAO,KAAK;EACrB,QAAQ,EAAE,OAAO,KAAK;CAC1B;CACA,OAAO;AACX;AAOA,SAAS,EAAc,GAAiC;CACpD,IAAI,OAAO,SAAW,KAClB,OAAO;EAAE,MAAM;EAAe,SAAS;CAA6C;CAExF,IAAI,CAAC,OAAO,iBACR,OAAO;EACH,MAAM;EACN,SAAS;CACb;CAEJ,IAAI,aAAe,cACf,QAAQ,EAAI,MAAZ;EACI,KAAK;EACL,KAAK,iBACD,OAAO;GACH,MAAM;GACN,SAAS;EACb;EACJ,KAAK;EACL,KAAK,wBACD,OAAO;GACH,MAAM;GACN,SAAS;EACb;EACJ,KAAK;EACL,KAAK,cACD,OAAO;GACH,MAAM;GACN,SAAS;EACb;CACR;CAEJ,OAAO;EACH,MAAM;EACN,SACI,aAAe,QAAQ,EAAI,UAAU;CAC7C;AACJ;AAuBA,SAAgB,EAAgB,IAAkC,CAAC,GAAuB;CACtF,IAAM,CAAC,GAAQ,KAAa,EAA6B,SAAS,GAC5D,CAAC,GAAO,KAAY,EAAmC,IAAI,GAC3D,CAAC,GAAY,KAAiB,EAAS,CAAC,GACxC,IAAW,EAAgC,IAAI,GAC/C,IAAiB,EACnB,EAAQ,eAAe,CAC3B;CAmEA,OAlEA,EAAe,UAAU,EAAQ,eAAe,GAEhD,QAAgB;EACZ,IAAI,IAAY,IACZ,IAA6B,MAC7B,IAAyC;EAE7C,eAAe,IAAuB;GAIlC,IAHA,EAAU,SAAS,GACnB,EAAS,IAAI,GAGT,OAAO,YAAc,OACrB,CAAC,UAAU,gBACX,OAAO,UAAU,aAAa,gBAAiB,YACjD;IACE,AAAK,MACD,EACI,OAAO,SAAW,OAAe,CAAC,OAAO,kBACnC,EAAc,IAAI,IAClB;KACI,MAAM;KACN,SAAS;IACb,CACV,GACA,EAAU,OAAO;IAErB;GACJ;GAEA,IAAI;IAEA,IADA,IAAS,MAAM,UAAU,aAAa,aAAa,EAAe,OAAO,GACrE,GAAW;KACX,EAAO,UAAU,CAAC,CAAC,SAAS,MAAU,EAAM,KAAK,CAAC;KAClD;IACJ;IACA,IAAM,IAAQ,EAAS;IACvB,IAAI,CAAC,GAAO;KACR,EAAO,UAAU,CAAC,CAAC,SAAS,MAAU,EAAM,KAAK,CAAC;KAClD;IACJ;IAIA,AAHA,IAAgB,GAChB,EAAM,YAAY,GAClB,MAAM,EAAM,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS,GACnC,KAAW,EAAU,OAAO;GACrC,SAAS,GAAK;IACV,AAAK,MACD,EAAS,EAAc,CAAG,CAAC,GAC3B,EAAU,OAAO;GAEzB;EACJ;EAIA,OAFA,EAAW,SAEE;GAKT,AAJA,IAAY,IACR,KACA,EAAO,UAAU,CAAC,CAAC,SAAS,MAAU,EAAM,KAAK,CAAC,GAElD,MACA,EAAc,YAAY;EAElC;CACJ,GAAG,CAAC,CAAU,CAAC,GAER;EACH;EACA;EACA;EACA,aAAa,GAAe,MAAM,IAAI,CAAC;CAC3C;AACJ"}
1
+ {"version":3,"file":"use-camera-stream.js","names":[],"sources":["../../src/vision/use-camera-stream.ts"],"sourcesContent":["import { useEffect, useRef, useState, type RefObject } from \"react\";\n\n/** Lifecycle status of the camera stream. */\nexport type CameraStreamStatus = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\n/** Classified reason a camera stream could not be acquired. */\nexport type CameraStreamErrorKind =\n \"unsupported\" | \"permission-denied\" | \"no-camera\" | \"in-use\" | \"insecure\" | \"unknown\";\n\n/** A classified camera error with a human-readable, English message. */\nexport interface CameraStreamError {\n kind: CameraStreamErrorKind;\n message: string;\n}\n\n/** Options for {@link useCameraStream}. */\nexport interface UseCameraStreamOptions {\n /**\n * Constraints passed to `getUserMedia`. Defaults to the rear\n * (\"environment\") camera at Full-HD ideal resolution with audio off.\n * Read when the stream (re)starts — change it and call `retry()` to apply.\n */\n constraints?: MediaStreamConstraints;\n}\n\n/** Value returned by {@link useCameraStream}. */\nexport interface UseCameraStreamApi {\n /** Current lifecycle status. */\n status: CameraStreamStatus;\n /** The classified error, or `null` while not in the `error` status. */\n error: CameraStreamError | null;\n /** Attach to a `<video ref={…} />`. The stream is wired to it once ready. */\n videoRef: RefObject<HTMLVideoElement | null>;\n /** Manually re-attempt after an error (e.g. the user changed permissions). */\n retry: () => void;\n}\n\n/** Rear-camera Full-HD defaults used when no `constraints` are supplied. */\nconst DEFAULT_CONSTRAINTS: MediaStreamConstraints = {\n video: {\n facingMode: { ideal: \"environment\" },\n width: { ideal: 1920 },\n height: { ideal: 1080 },\n },\n audio: false,\n};\n\n/**\n * Map an unknown failure into a {@link CameraStreamError}. Secure-context and\n * environment checks run first (they are the reason `getUserMedia` is missing\n * or rejects), then the `DOMException.name` is mapped to a stable `kind`.\n */\nfunction classifyError(err: unknown): CameraStreamError {\n if (typeof window === \"undefined\") {\n return { kind: \"unsupported\", message: \"Camera is unavailable in this environment.\" };\n }\n if (!window.isSecureContext) {\n return {\n kind: \"insecure\",\n message: \"Camera access requires a secure (HTTPS) connection.\",\n };\n }\n if (err instanceof DOMException) {\n switch (err.name) {\n case \"NotAllowedError\":\n case \"SecurityError\":\n return {\n kind: \"permission-denied\",\n message: \"Camera permission denied. Enable access in your browser settings.\",\n };\n case \"NotFoundError\":\n case \"OverconstrainedError\":\n return {\n kind: \"no-camera\",\n message: \"No camera available on this device.\",\n };\n case \"NotReadableError\":\n case \"AbortError\":\n return {\n kind: \"in-use\",\n message: \"The camera is in use by another app. Close it and try again.\",\n };\n }\n }\n return {\n kind: \"unknown\",\n message:\n err instanceof Error ? err.message : \"Unexpected error while accessing the camera.\",\n };\n}\n\n/**\n * Acquire a `MediaStream` via `getUserMedia`, attach it to a `<video>` element,\n * and expose status/error so the page can render permission and error states.\n * The stream is automatically released on unmount or retry.\n *\n * Defaults to the rear (\"environment\") camera; desktops fall back to whatever\n * single camera they expose. Pass `options.constraints` to override.\n *\n * Implementation notes:\n * - Cleanup detaches the stream from a *snapshotted* video node, so it releases\n * the same element it attached to even if the page remounts the `<video>`.\n * - When `getUserMedia` is missing, an insecure context is the usual cause, so\n * the hook prefers that (actionable) error; otherwise it reports `unsupported`.\n * - `video.play()` rejections are swallowed: autoplay may be blocked, but the\n * user gesture that opened the camera usually counts and playback resumes on\n * the next interaction.\n *\n * @param options - optional configuration (see {@link UseCameraStreamOptions}).\n * @returns The stream status, classified error, a `videoRef` to attach, and a\n * `retry()` to re-attempt acquisition.\n */\nexport function useCameraStream(options: UseCameraStreamOptions = {}): UseCameraStreamApi {\n const [status, setStatus] = useState<CameraStreamStatus>(\"loading\");\n const [error, setError] = useState<CameraStreamError | null>(null);\n const [retryToken, setRetryToken] = useState(0);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const constraintsRef = useRef<MediaStreamConstraints>(\n options.constraints ?? DEFAULT_CONSTRAINTS,\n );\n useEffect(() => {\n constraintsRef.current = options.constraints ?? DEFAULT_CONSTRAINTS;\n }, [options.constraints]);\n\n useEffect(() => {\n let cancelled = false;\n let stream: MediaStream | null = null;\n let attachedVideo: HTMLVideoElement | null = null;\n\n async function start(): Promise<void> {\n setStatus(\"loading\");\n setError(null);\n\n if (\n typeof navigator === \"undefined\" ||\n !navigator.mediaDevices ||\n typeof navigator.mediaDevices.getUserMedia !== \"function\"\n ) {\n if (!cancelled) {\n setError(\n typeof window !== \"undefined\" && !window.isSecureContext\n ? classifyError(null)\n : {\n kind: \"unsupported\",\n message: \"Camera capture is not supported in this browser.\",\n },\n );\n setStatus(\"error\");\n }\n return;\n }\n\n try {\n stream = await navigator.mediaDevices.getUserMedia(constraintsRef.current);\n if (cancelled) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n const video = videoRef.current;\n if (!video) {\n stream.getTracks().forEach((track) => track.stop());\n return;\n }\n attachedVideo = video;\n video.srcObject = stream;\n await video.play().catch(() => undefined);\n if (!cancelled) setStatus(\"ready\");\n } catch (err) {\n if (!cancelled) {\n setError(classifyError(err));\n setStatus(\"error\");\n }\n }\n }\n\n void start();\n\n return () => {\n cancelled = true;\n if (stream) {\n stream.getTracks().forEach((track) => track.stop());\n }\n if (attachedVideo) {\n attachedVideo.srcObject = null;\n }\n };\n }, [retryToken]);\n\n return {\n status,\n error,\n videoRef,\n retry: () => setRetryToken((n) => n + 1),\n };\n}\n"],"mappings":";;AAsCA,IAAM,IAA8C;CAChD,OAAO;EACH,YAAY,EAAE,OAAO,cAAc;EACnC,OAAO,EAAE,OAAO,KAAK;EACrB,QAAQ,EAAE,OAAO,KAAK;CAC1B;CACA,OAAO;AACX;AAOA,SAAS,EAAc,GAAiC;CACpD,IAAI,OAAO,SAAW,KAClB,OAAO;EAAE,MAAM;EAAe,SAAS;CAA6C;CAExF,IAAI,CAAC,OAAO,iBACR,OAAO;EACH,MAAM;EACN,SAAS;CACb;CAEJ,IAAI,aAAe,cACf,QAAQ,EAAI,MAAZ;EACI,KAAK;EACL,KAAK,iBACD,OAAO;GACH,MAAM;GACN,SAAS;EACb;EACJ,KAAK;EACL,KAAK,wBACD,OAAO;GACH,MAAM;GACN,SAAS;EACb;EACJ,KAAK;EACL,KAAK,cACD,OAAO;GACH,MAAM;GACN,SAAS;EACb;CACR;CAEJ,OAAO;EACH,MAAM;EACN,SACI,aAAe,QAAQ,EAAI,UAAU;CAC7C;AACJ;AAuBA,SAAgB,EAAgB,IAAkC,CAAC,GAAuB;CACtF,IAAM,CAAC,GAAQ,KAAa,EAA6B,SAAS,GAC5D,CAAC,GAAO,KAAY,EAAmC,IAAI,GAC3D,CAAC,GAAY,KAAiB,EAAS,CAAC,GACxC,IAAW,EAAgC,IAAI,GAC/C,IAAiB,EACnB,EAAQ,eAAe,CAC3B;CAqEA,OApEA,QAAgB;EACZ,EAAe,UAAU,EAAQ,eAAe;CACpD,GAAG,CAAC,EAAQ,WAAW,CAAC,GAExB,QAAgB;EACZ,IAAI,IAAY,IACZ,IAA6B,MAC7B,IAAyC;EAE7C,eAAe,IAAuB;GAIlC,IAHA,EAAU,SAAS,GACnB,EAAS,IAAI,GAGT,OAAO,YAAc,OACrB,CAAC,UAAU,gBACX,OAAO,UAAU,aAAa,gBAAiB,YACjD;IACE,AAAK,MACD,EACI,OAAO,SAAW,OAAe,CAAC,OAAO,kBACnC,EAAc,IAAI,IAClB;KACI,MAAM;KACN,SAAS;IACb,CACV,GACA,EAAU,OAAO;IAErB;GACJ;GAEA,IAAI;IAEA,IADA,IAAS,MAAM,UAAU,aAAa,aAAa,EAAe,OAAO,GACrE,GAAW;KACX,EAAO,UAAU,CAAC,CAAC,SAAS,MAAU,EAAM,KAAK,CAAC;KAClD;IACJ;IACA,IAAM,IAAQ,EAAS;IACvB,IAAI,CAAC,GAAO;KACR,EAAO,UAAU,CAAC,CAAC,SAAS,MAAU,EAAM,KAAK,CAAC;KAClD;IACJ;IAIA,AAHA,IAAgB,GAChB,EAAM,YAAY,GAClB,MAAM,EAAM,KAAK,CAAC,CAAC,YAAY,KAAA,CAAS,GACnC,KAAW,EAAU,OAAO;GACrC,SAAS,GAAK;IACV,AAAK,MACD,EAAS,EAAc,CAAG,CAAC,GAC3B,EAAU,OAAO;GAEzB;EACJ;EAIA,OAFA,EAAW,SAEE;GAKT,AAJA,IAAY,IACR,KACA,EAAO,UAAU,CAAC,CAAC,SAAS,MAAU,EAAM,KAAK,CAAC,GAElD,MACA,EAAc,YAAY;EAElC;CACJ,GAAG,CAAC,CAAU,CAAC,GAER;EACH;EACA;EACA;EACA,aAAa,GAAe,MAAM,IAAI,CAAC;CAC3C;AACJ"}
@@ -1,2 +1,2 @@
1
- const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:path"),n=require("vite"),r=require("@vitejs/plugin-react");r=e.__toESM(r,1);function i(e){let t={};for(let[n,r]of Object.entries(e))t[n]=typeof r==`string`?{target:r,changeOrigin:!0}:r;return t}function a(e={}){let{srcDir:a=`src`,port:o=5173,host:s=`127.0.0.1`,open:c=!1,proxy:l,alias:u={},plugins:d=[],overrides:f={}}=e,p=f,m={plugins:[(0,r.default)(),...d],resolve:{alias:{"@":(0,t.resolve)(process.cwd(),a),...u}},server:{port:o,host:s,open:c,...l?{proxy:i(l)}:{}}};return(0,n.defineConfig)({...m,...p,plugins:[...m.plugins??[],...p.plugins??[]],resolve:{...m.resolve,...p.resolve},server:{...m.server,...p.server}})}exports.createViteConfig=a;
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("./tempest-icons.cjs");let n=require("node:path"),r=require("vite"),i=require("@vitejs/plugin-react");i=e.__toESM(i,1);function a(e){let t={};for(let[n,r]of Object.entries(e))t[n]=typeof r==`string`?{target:r,changeOrigin:!0}:r;return t}function o(e={}){let{srcDir:o=`src`,port:s=5173,host:c=`127.0.0.1`,open:l=!1,proxy:u,alias:d={},plugins:f=[],icons:p=!0,overrides:m={}}=e,h=m,g=p?[t.tempestIcons({dir:o,...typeof p==`object`?p:{}})]:[],_={plugins:[(0,i.default)(),...g,...f],resolve:{alias:{"@":(0,n.resolve)(process.cwd(),o),...d}},server:{port:s,host:c,open:l,...u?{proxy:a(u)}:{}}};return(0,r.defineConfig)({..._,...h,plugins:[..._.plugins??[],...h.plugins??[]],resolve:{..._.resolve,...h.resolve},server:{..._.server,...h.server}})}exports.createViteConfig=o;
2
2
  //# sourceMappingURL=create-vite-config.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-vite-config.cjs","names":[],"sources":["../../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n\n const base: UserConfig = {\n plugins: [react(), ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"mappings":"qJAgDA,SAAS,EAAe,EAAiE,CACrF,IAAM,EAAoC,CAAC,EAC3C,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAK,EAC5C,EAAI,GACA,OAAO,GAAU,SACX,CAAE,OAAQ,EAAO,aAAc,EAAK,EACnC,EAEf,OAAO,CACX,CAkBA,SAAgB,EAAiB,EAAmC,CAAC,EAAsB,CACvF,GAAM,CACF,SAAS,MACT,OAAO,KACP,OAAO,YACP,OAAO,GACP,QACA,QAAQ,CAAC,EACT,UAAU,CAAC,EACX,YAAY,CAAC,GACb,EAEE,EAAkB,EAElB,EAAmB,CACrB,QAAS,EAAA,EAAA,EAAA,QAAA,CAAO,EAAG,GAAG,CAAO,EAC7B,QAAS,CACL,MAAO,CACH,KAAA,EAAA,EAAA,QAAA,CAAa,QAAQ,IAAI,EAAG,CAAM,EAClC,GAAG,CACP,CACJ,EACA,OAAQ,CACJ,OACA,OACA,OACA,GAAI,EAAQ,CAAE,MAAO,EAAe,CAAK,CAAE,EAAI,CAAC,CACpD,CACJ,EAUA,OAAA,EAAA,EAAA,aAAA,CAAoB,CAPhB,GAAG,EACH,GAAG,EACH,QAAS,CAAC,GAAI,EAAK,SAAW,CAAC,EAAI,GAAI,EAAgB,SAAW,CAAC,CAAE,EACrE,QAAS,CAAE,GAAG,EAAK,QAAS,GAAG,EAAgB,OAAQ,EACvD,OAAQ,CAAE,GAAG,EAAK,OAAQ,GAAG,EAAgB,MAAO,CAGpC,CAAM,CAC9B"}
1
+ {"version":3,"file":"create-vite-config.cjs","names":[],"sources":["../../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\nimport { tempestIcons, type TempestIconsOptions } from \"./tempest-icons\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * The `tempestIcons()` plugin, which generates `virtual:tempest-icons` from the\n * icon slugs your source mentions. On by default — it only ever removes\n * requests. Pass `false` to leave it out, or an options object to configure the\n * scan.\n */\n icons?: boolean | TempestIconsOptions;\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n icons = true,\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n const iconsPlugin = icons\n ? [tempestIcons({ dir: srcDir, ...(typeof icons === \"object\" ? icons : {}) })]\n : [];\n\n const base: UserConfig = {\n plugins: [react(), ...iconsPlugin, ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"mappings":"sLAyDA,SAAS,EAAe,EAAiE,CACrF,IAAM,EAAoC,CAAC,EAC3C,IAAK,GAAM,CAAC,EAAM,KAAU,OAAO,QAAQ,CAAK,EAC5C,EAAI,GACA,OAAO,GAAU,SACX,CAAE,OAAQ,EAAO,aAAc,EAAK,EACnC,EAEf,OAAO,CACX,CAkBA,SAAgB,EAAiB,EAAmC,CAAC,EAAsB,CACvF,GAAM,CACF,SAAS,MACT,OAAO,KACP,OAAO,YACP,OAAO,GACP,QACA,QAAQ,CAAC,EACT,UAAU,CAAC,EACX,QAAQ,GACR,YAAY,CAAC,GACb,EAEE,EAAkB,EAClB,EAAc,EACd,CAAC,EAAA,aAAa,CAAE,IAAK,EAAQ,GAAI,OAAO,GAAU,SAAW,EAAQ,CAAC,CAAG,CAAC,CAAC,EAC3E,CAAC,EAED,EAAmB,CACrB,QAAS,eAAO,EAAG,GAAG,EAAa,GAAG,CAAO,EAC7C,QAAS,CACL,MAAO,CACH,KAAA,EAAA,EAAA,QAAA,CAAa,QAAQ,IAAI,EAAG,CAAM,EAClC,GAAG,CACP,CACJ,EACA,OAAQ,CACJ,OACA,OACA,OACA,GAAI,EAAQ,CAAE,MAAO,EAAe,CAAK,CAAE,EAAI,CAAC,CACpD,CACJ,EAUA,OAAA,EAAA,EAAA,aAAA,CAAoB,CAPhB,GAAG,EACH,GAAG,EACH,QAAS,CAAC,GAAI,EAAK,SAAW,CAAC,EAAI,GAAI,EAAgB,SAAW,CAAC,CAAE,EACrE,QAAS,CAAE,GAAG,EAAK,QAAS,GAAG,EAAgB,OAAQ,EACvD,OAAQ,CAAE,GAAG,EAAK,OAAQ,GAAG,EAAgB,MAAO,CAGpC,CAAM,CAC9B"}
@@ -1,8 +1,9 @@
1
- import { resolve as e } from "node:path";
2
- import { defineConfig as t } from "vite";
3
- import n from "@vitejs/plugin-react";
1
+ import { tempestIcons as e } from "./tempest-icons.js";
2
+ import { resolve as t } from "node:path";
3
+ import { defineConfig as n } from "vite";
4
+ import r from "@vitejs/plugin-react";
4
5
  //#region src/vite/create-vite-config.ts
5
- function r(e) {
6
+ function i(e) {
6
7
  let t = {};
7
8
  for (let [n, r] of Object.entries(e)) t[n] = typeof r == "string" ? {
8
9
  target: r,
@@ -10,35 +11,42 @@ function r(e) {
10
11
  } : r;
11
12
  return t;
12
13
  }
13
- function i(i = {}) {
14
- let { srcDir: a = "src", port: o = 5173, host: s = "127.0.0.1", open: c = !1, proxy: l, alias: u = {}, plugins: d = [], overrides: f = {} } = i, p = f, m = {
15
- plugins: [n(), ...d],
14
+ function a(a = {}) {
15
+ let { srcDir: o = "src", port: s = 5173, host: c = "127.0.0.1", open: l = !1, proxy: u, alias: d = {}, plugins: f = [], icons: p = !0, overrides: m = {} } = a, h = m, g = p ? [e({
16
+ dir: o,
17
+ ...typeof p == "object" ? p : {}
18
+ })] : [], _ = {
19
+ plugins: [
20
+ r(),
21
+ ...g,
22
+ ...f
23
+ ],
16
24
  resolve: { alias: {
17
- "@": e(process.cwd(), a),
18
- ...u
25
+ "@": t(process.cwd(), o),
26
+ ...d
19
27
  } },
20
28
  server: {
21
- port: o,
22
- host: s,
23
- open: c,
24
- ...l ? { proxy: r(l) } : {}
29
+ port: s,
30
+ host: c,
31
+ open: l,
32
+ ...u ? { proxy: i(u) } : {}
25
33
  }
26
34
  };
27
- return t({
28
- ...m,
29
- ...p,
30
- plugins: [...m.plugins ?? [], ...p.plugins ?? []],
35
+ return n({
36
+ ..._,
37
+ ...h,
38
+ plugins: [..._.plugins ?? [], ...h.plugins ?? []],
31
39
  resolve: {
32
- ...m.resolve,
33
- ...p.resolve
40
+ ..._.resolve,
41
+ ...h.resolve
34
42
  },
35
43
  server: {
36
- ...m.server,
37
- ...p.server
44
+ ..._.server,
45
+ ...h.server
38
46
  }
39
47
  });
40
48
  }
41
49
  //#endregion
42
- export { i as createViteConfig };
50
+ export { a as createViteConfig };
43
51
 
44
52
  //# sourceMappingURL=create-vite-config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-vite-config.js","names":[],"sources":["../../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n\n const base: UserConfig = {\n plugins: [react(), ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"mappings":";;;;AAgDA,SAAS,EAAe,GAAiE;CACrF,IAAM,IAAoC,CAAC;CAC3C,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,CAAK,GAC5C,EAAI,KACA,OAAO,KAAU,WACX;EAAE,QAAQ;EAAO,cAAc;CAAK,IACnC;CAEf,OAAO;AACX;AAkBA,SAAgB,EAAiB,IAAmC,CAAC,GAAsB;CACvF,IAAM,EACF,YAAS,OACT,UAAO,MACP,UAAO,aACP,UAAO,IACP,UACA,WAAQ,CAAC,GACT,aAAU,CAAC,GACX,eAAY,CAAC,MACb,GAEE,IAAkB,GAElB,IAAmB;EACrB,SAAS,CAAC,EAAM,GAAG,GAAG,CAAO;EAC7B,SAAS,EACL,OAAO;GACH,KAAK,EAAQ,QAAQ,IAAI,GAAG,CAAM;GAClC,GAAG;EACP,EACJ;EACA,QAAQ;GACJ;GACA;GACA;GACA,GAAI,IAAQ,EAAE,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC;EACpD;CACJ;CAUA,OAAO,EAAa;EAPhB,GAAG;EACH,GAAG;EACH,SAAS,CAAC,GAAI,EAAK,WAAW,CAAC,GAAI,GAAI,EAAgB,WAAW,CAAC,CAAE;EACrE,SAAS;GAAE,GAAG,EAAK;GAAS,GAAG,EAAgB;EAAQ;EACvD,QAAQ;GAAE,GAAG,EAAK;GAAQ,GAAG,EAAgB;EAAO;CAGpC,CAAM;AAC9B"}
1
+ {"version":3,"file":"create-vite-config.js","names":[],"sources":["../../src/vite/create-vite-config.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { defineConfig } from \"vite\";\nimport type { ProxyOptions, UserConfig } from \"vite\";\nimport react from \"@vitejs/plugin-react\";\n\nimport { tempestIcons, type TempestIconsOptions } from \"./tempest-icons\";\n\n/**\n * A Vite proxy entry: either a target URL string (expanded to\n * `{ target, changeOrigin: true }`) or a raw Vite `ProxyOptions` object.\n */\nexport type ProxyEntry = string | Record<string, unknown>;\n\nexport interface CreateViteConfigOptions {\n /**\n * Source directory aliased to `@`, relative to the project root.\n * Default: `\"src\"` (so `@/components/Button` → `<root>/src/components/Button`).\n */\n srcDir?: string;\n /** Dev server port. Default: `5173`. */\n port?: number;\n /** Dev server host. Default: `\"127.0.0.1\"`. */\n host?: string | boolean;\n /** Open the browser on `dev` start. Default: `false`. */\n open?: boolean;\n /**\n * Dev proxy table. String values are expanded to\n * `{ target, changeOrigin: true }`; objects are passed through untouched.\n *\n * @example { \"/api\": \"http://127.0.0.1:8000\" }\n */\n proxy?: Record<string, ProxyEntry>;\n /** Extra path aliases merged on top of the default `@` → src alias. */\n alias?: Record<string, string>;\n /** Vite plugins appended after `@vitejs/plugin-react`. */\n plugins?: unknown[];\n /**\n * The `tempestIcons()` plugin, which generates `virtual:tempest-icons` from the\n * icon slugs your source mentions. On by default — it only ever removes\n * requests. Pass `false` to leave it out, or an options object to configure the\n * scan.\n */\n icons?: boolean | TempestIconsOptions;\n /**\n * Arbitrary Vite config (a `UserConfig` object) deep-merged last, for\n * escape-hatch overrides (build target, define, extra `server` keys, …).\n */\n overrides?: Record<string, unknown>;\n}\n\n/**\n * The resulting Vite config object. Typed loosely so the SDK's published\n * declarations stay free of `vite`'s internal types; assign it straight to a\n * `vite.config.ts` default export.\n */\nexport type TempestViteConfig = Record<string, unknown>;\n\nfunction normalizeProxy(proxy: Record<string, ProxyEntry>): Record<string, ProxyOptions> {\n const out: Record<string, ProxyOptions> = {};\n for (const [path, value] of Object.entries(proxy)) {\n out[path] =\n typeof value === \"string\"\n ? { target: value, changeOrigin: true }\n : (value as ProxyOptions);\n }\n return out;\n}\n\n/**\n * Build a Tempest-flavored Vite config for a React app: the `@vitejs/plugin-react`\n * plugin, the `@` → `src` import alias, and sane dev-server defaults — so a\n * consuming app's `vite.config.ts` is a single call instead of repeated\n * boilerplate. Everything is overridable.\n *\n * Import it from the dedicated Node entry point:\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({\n * proxy: { \"/api\": \"http://127.0.0.1:8000\" },\n * });\n */\nexport function createViteConfig(options: CreateViteConfigOptions = {}): TempestViteConfig {\n const {\n srcDir = \"src\",\n port = 5173,\n host = \"127.0.0.1\",\n open = false,\n proxy,\n alias = {},\n plugins = [],\n icons = true,\n overrides = {},\n } = options;\n\n const overridesConfig = overrides as UserConfig;\n const iconsPlugin = icons\n ? [tempestIcons({ dir: srcDir, ...(typeof icons === \"object\" ? icons : {}) })]\n : [];\n\n const base: UserConfig = {\n plugins: [react(), ...iconsPlugin, ...plugins] as UserConfig[\"plugins\"],\n resolve: {\n alias: {\n \"@\": resolve(process.cwd(), srcDir),\n ...alias,\n },\n },\n server: {\n port,\n host,\n open,\n ...(proxy ? { proxy: normalizeProxy(proxy) } : {}),\n },\n };\n\n const merged: UserConfig = {\n ...base,\n ...overridesConfig,\n plugins: [...(base.plugins ?? []), ...(overridesConfig.plugins ?? [])],\n resolve: { ...base.resolve, ...overridesConfig.resolve },\n server: { ...base.server, ...overridesConfig.server },\n };\n\n return defineConfig(merged) as TempestViteConfig;\n}\n"],"mappings":";;;;;AAyDA,SAAS,EAAe,GAAiE;CACrF,IAAM,IAAoC,CAAC;CAC3C,KAAK,IAAM,CAAC,GAAM,MAAU,OAAO,QAAQ,CAAK,GAC5C,EAAI,KACA,OAAO,KAAU,WACX;EAAE,QAAQ;EAAO,cAAc;CAAK,IACnC;CAEf,OAAO;AACX;AAkBA,SAAgB,EAAiB,IAAmC,CAAC,GAAsB;CACvF,IAAM,EACF,YAAS,OACT,UAAO,MACP,UAAO,aACP,UAAO,IACP,UACA,WAAQ,CAAC,GACT,aAAU,CAAC,GACX,WAAQ,IACR,eAAY,CAAC,MACb,GAEE,IAAkB,GAClB,IAAc,IACd,CAAC,EAAa;EAAE,KAAK;EAAQ,GAAI,OAAO,KAAU,WAAW,IAAQ,CAAC;CAAG,CAAC,CAAC,IAC3E,CAAC,GAED,IAAmB;EACrB,SAAS;GAAC,EAAM;GAAG,GAAG;GAAa,GAAG;EAAO;EAC7C,SAAS,EACL,OAAO;GACH,KAAK,EAAQ,QAAQ,IAAI,GAAG,CAAM;GAClC,GAAG;EACP,EACJ;EACA,QAAQ;GACJ;GACA;GACA;GACA,GAAI,IAAQ,EAAE,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC;EACpD;CACJ;CAUA,OAAO,EAAa;EAPhB,GAAG;EACH,GAAG;EACH,SAAS,CAAC,GAAI,EAAK,WAAW,CAAC,GAAI,GAAI,EAAgB,WAAW,CAAC,CAAE;EACrE,SAAS;GAAE,GAAG,EAAK;GAAS,GAAG,EAAgB;EAAQ;EACvD,QAAQ;GAAE,GAAG,EAAK;GAAQ,GAAG,EAAgB;EAAO;CAGpC,CAAM;AAC9B"}
@@ -0,0 +1,9 @@
1
+ const e=require("../icons/generated/aliases.cjs"),t=require("../icons/generated/icon-names.cjs");let n=require("node:path"),r=require("node:fs/promises");var i=`virtual:tempest-icons`,a=`\0${i}`,o=[/\bname\s*=\s*["']([a-z0-9-]+)["']/g,/\bname\s*=\s*\{\s*["']([a-z0-9-]+)["']\s*\}/g,/\bname\s*:\s*["']([a-z0-9-]+)["']/g],s=/\.[cm]?[jt]sx?$/,c=new Set([`node_modules`,`dist`,`build`,`coverage`]);function l(e,t){let n=new Set;for(let r of o){r.lastIndex=0;let i;for(;(i=r.exec(e))!==null;)t.has(i[1])&&n.add(i[1])}return[...n]}function u(e,t){let n=[...new Set(e)].sort();if(!n.length)return`export const staticIcons = {};
2
+ `;let r=new Map;for(let e of n)r.set(e,d(t[e]??e));return`import { ${[...new Set(r.values())].sort().map(e=>p(e)===e?e:`${e} as ${p(e)}`).join(`, `)} } from "lucide-react";
3
+
4
+ export const staticIcons = {
5
+ ${n.map(e=>` "${e}": ${p(r.get(e))},`).join(`
6
+ `)}
7
+ };
8
+ `}function d(e){return e.split(`-`).map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(``)}var f=new Set([`Infinity`,`NaN`,`undefined`,`eval`,`arguments`]);function p(e){return f.has(e)?`${e}Icon`:e}async function m(e,t){let i=[],a;try{a=await(0,r.readdir)(e,{withFileTypes:!0})}catch{return i}for(let r of a)if(r.isDirectory()){if(t.has(r.name)||r.name.startsWith(`.`))continue;i.push(...await m((0,n.join)(e,r.name),t))}else r.isFile()&&s.test(r.name)&&i.push((0,n.join)(e,r.name));return i}function h(i={}){let{dir:o=`src`,include:d=[],skipDirs:f}=i,p=new Set(t.iconNames),h=new Set(f??c),g=new Set(d.filter(e=>p.has(e))),_=process.cwd(),v=async()=>{let e=await m((0,n.resolve)(_,o),h);for(let t of e)try{let e=await(0,r.readFile)(t,`utf8`);for(let t of l(e,p))g.add(t)}catch{}};return{name:`tempest-icons`,enforce:`pre`,configResolved(e){_=e.root??_},async buildStart(){await v()},resolveId(e){return e===`virtual:tempest-icons`?a:null},load(t){return t===a?u([...g],e.iconAliases):null},async handleHotUpdate(e){if(!s.test(e.file))return;let t=g.size;for(let t of l(await e.read(),p))g.add(t);if(g.size===t)return;let n=e.server.moduleGraph.getModuleById(a);if(n)return[n]}}}exports.TEMPEST_ICONS_ID=i,exports.buildIconsModule=u,exports.scanIconSlugs=l,exports.tempestIcons=h;
9
+ //# sourceMappingURL=tempest-icons.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempest-icons.cjs","names":[],"sources":["../../src/vite/tempest-icons.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { iconAliases } from \"@/icons/generated/aliases\";\nimport { iconNames } from \"@/icons/generated/icon-names\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The module id an app imports to get its static registry. */\nexport const TEMPEST_ICONS_ID = \"virtual:tempest-icons\";\n\nconst RESOLVED_ID = `\\0${TEMPEST_ICONS_ID}`;\n\n/**\n * Slugs written as a literal `name` prop or a literal `name:` field.\n *\n * Matching the *attribute* rather than any kebab-case string is what keeps the\n * scan from sweeping in unrelated text — a CSS class, a translation key and a\n * route segment all look like slugs otherwise. Everything matched is still\n * validated against lucide's real slug list before it reaches the module.\n */\nconst NAME_PATTERNS = [\n /\\bname\\s*=\\s*[\"']([a-z0-9-]+)[\"']/g,\n /\\bname\\s*=\\s*\\{\\s*[\"']([a-z0-9-]+)[\"']\\s*\\}/g,\n /\\bname\\s*:\\s*[\"']([a-z0-9-]+)[\"']/g,\n];\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\nexport interface TempestIconsOptions {\n /**\n * Directory to scan, relative to the Vite root. Default: `\"src\"`.\n */\n dir?: string;\n /**\n * Extra slugs to include even when the scan cannot see them — a name built by\n * concatenation, or one that only ever arrives from the API but is worth\n * paying for statically because it is on the first screen.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n}\n\n/**\n * Collect the icon slugs a source file references literally.\n *\n * @param code - File contents.\n * @param known - The set of real lucide slugs, aliases included.\n * @returns The slugs found, validated against `known`.\n */\nexport function scanIconSlugs(code: string, known: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const pattern of NAME_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n if (known.has(match[1])) found.add(match[1]);\n }\n }\n return [...found];\n}\n\n/**\n * Build the virtual module source for a set of slugs.\n *\n * Aliases resolve to their canonical slug before importing, because only the\n * canonical name is a real lucide export — but the alias key still points at the\n * component, so `<Icon name=\"alert-circle\" />` stays a zero-request lookup.\n *\n * @param slugs - Validated slugs to include.\n * @param aliases - Alias slug → canonical slug.\n * @returns ES module source.\n */\nexport function buildIconsModule(\n slugs: readonly string[],\n aliases: Record<string, string>,\n): string {\n const sorted = [...new Set(slugs)].sort();\n if (!sorted.length) return \"export const staticIcons = {};\\n\";\n\n const componentOf = new Map<string, string>();\n for (const slug of sorted) componentOf.set(slug, pascalCase(aliases[slug] ?? slug));\n\n const named = [...new Set(componentOf.values())]\n .sort()\n .map((name) => (localName(name) === name ? name : `${name} as ${localName(name)}`))\n .join(\", \");\n const entries = sorted\n .map((slug) => ` \"${slug}\": ${localName(componentOf.get(slug) as string)},`)\n .join(\"\\n\");\n return `import { ${named} } from \"lucide-react\";\n\nexport const staticIcons = {\n${entries}\n};\n`;\n}\n\n/** `circle-alert` → `CircleAlert`; `a-arrow-down` → `AArrowDown`. */\nfunction pascalCase(slug: string): string {\n return slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Export names that would shadow a restricted global if bound directly.\n *\n * Lucide ships an icon literally called `infinity`, whose export is `Infinity`.\n * Binding that at module scope shadows the global, so the import is aliased.\n */\nconst RESERVED_NAMES = new Set([\"Infinity\", \"NaN\", \"undefined\", \"eval\", \"arguments\"]);\n\n/** The local binding to use for a lucide export name. */\nfunction localName(name: string): string {\n return RESERVED_NAMES.has(name) ? `${name}Icon` : name;\n}\n\n/**\n * Walk a directory and collect every source file path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Generate a static icon registry from the slugs your source mentions.\n *\n * Removes the reason to reach for `lucide-react`'s `DynamicIcon`: the slugs an app\n * writes as literals become ordinary static imports, so the bundler keeps exactly\n * those icons and the browser makes **no** extra request for them. A slug that only\n * exists at runtime still works — `<Icon>` falls back to fetching one shard per\n * initial letter.\n *\n * The scan reads the source tree directly at `buildStart` rather than harvesting\n * modules as they pass through `transform`: relying on transform order would let\n * the virtual module load before every consumer had been seen, and the registry\n * would come out short on a cold dev start — silently, since the missing slugs\n * would still render via the lazy path.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestIcons()] });\n *\n * @example\n * // src/main.tsx\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"virtual:tempest-icons\";\n *\n * <IconProvider registry={staticIcons}>\n * <App />\n * </IconProvider>\n */\nexport function tempestIcons(options: TempestIconsOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs } = options;\n const known = new Set<string>(iconNames);\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n const slugs = new Set<string>(include.filter((slug) => known.has(slug)));\n let root = process.cwd();\n\n /** Rescan the source tree, keeping the explicitly included slugs. */\n const rescan = async (): Promise<void> => {\n const files = await collectSourceFiles(resolve(root, dir), skip);\n for (const file of files) {\n try {\n const code = await readFile(file, \"utf8\");\n for (const slug of scanIconSlugs(code, known)) slugs.add(slug);\n } catch {\n /* an unreadable file just contributes nothing */\n }\n }\n };\n\n return {\n name: \"tempest-icons\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n resolveId(id: string) {\n return id === TEMPEST_ICONS_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n return id === RESOLVED_ID ? buildIconsModule([...slugs], iconAliases) : null;\n },\n\n /**\n * Pick up a slug added while the dev server is running.\n *\n * A newly written `<Icon name=\"save\" />` would otherwise only render via\n * the lazy path until the next restart. Rescanning the changed file and\n * invalidating the virtual module keeps dev and build agreeing on what the\n * static registry contains.\n */\n async handleHotUpdate(ctx: {\n file: string;\n read: () => Promise<string>;\n server: { moduleGraph: { getModuleById: (id: string) => unknown } };\n }) {\n if (!SOURCE_FILE.test(ctx.file)) return;\n const before = slugs.size;\n for (const slug of scanIconSlugs(await ctx.read(), known)) slugs.add(slug);\n if (slugs.size === before) return;\n const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);\n if (mod) return [mod];\n },\n } as TempestVitePlugin;\n}\n"],"mappings":"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"}
@@ -0,0 +1,99 @@
1
+ import { iconAliases as e } from "../icons/generated/aliases.js";
2
+ import { iconNames as t } from "../icons/generated/icon-names.js";
3
+ import { join as n, resolve as r } from "node:path";
4
+ import { readFile as i, readdir as a } from "node:fs/promises";
5
+ //#region src/vite/tempest-icons.ts
6
+ var o = "virtual:tempest-icons", s = `\0${o}`, c = [
7
+ /\bname\s*=\s*["']([a-z0-9-]+)["']/g,
8
+ /\bname\s*=\s*\{\s*["']([a-z0-9-]+)["']\s*\}/g,
9
+ /\bname\s*:\s*["']([a-z0-9-]+)["']/g
10
+ ], l = /\.[cm]?[jt]sx?$/, u = /* @__PURE__ */ new Set([
11
+ "node_modules",
12
+ "dist",
13
+ "build",
14
+ "coverage"
15
+ ]);
16
+ function d(e, t) {
17
+ let n = /* @__PURE__ */ new Set();
18
+ for (let r of c) {
19
+ r.lastIndex = 0;
20
+ let i;
21
+ for (; (i = r.exec(e)) !== null;) t.has(i[1]) && n.add(i[1]);
22
+ }
23
+ return [...n];
24
+ }
25
+ function f(e, t) {
26
+ let n = [...new Set(e)].sort();
27
+ if (!n.length) return "export const staticIcons = {};\n";
28
+ let r = /* @__PURE__ */ new Map();
29
+ for (let e of n) r.set(e, p(t[e] ?? e));
30
+ return `import { ${[...new Set(r.values())].sort().map((e) => h(e) === e ? e : `${e} as ${h(e)}`).join(", ")} } from "lucide-react";
31
+
32
+ export const staticIcons = {
33
+ ${n.map((e) => ` "${e}": ${h(r.get(e))},`).join("\n")}
34
+ };
35
+ `;
36
+ }
37
+ function p(e) {
38
+ return e.split("-").map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join("");
39
+ }
40
+ var m = /* @__PURE__ */ new Set([
41
+ "Infinity",
42
+ "NaN",
43
+ "undefined",
44
+ "eval",
45
+ "arguments"
46
+ ]);
47
+ function h(e) {
48
+ return m.has(e) ? `${e}Icon` : e;
49
+ }
50
+ async function g(e, t) {
51
+ let r = [], i;
52
+ try {
53
+ i = await a(e, { withFileTypes: !0 });
54
+ } catch {
55
+ return r;
56
+ }
57
+ for (let a of i) if (a.isDirectory()) {
58
+ if (t.has(a.name) || a.name.startsWith(".")) continue;
59
+ r.push(...await g(n(e, a.name), t));
60
+ } else a.isFile() && l.test(a.name) && r.push(n(e, a.name));
61
+ return r;
62
+ }
63
+ function _(n = {}) {
64
+ let { dir: a = "src", include: o = [], skipDirs: c } = n, p = new Set(t), m = new Set(c ?? u), h = new Set(o.filter((e) => p.has(e))), _ = process.cwd(), v = async () => {
65
+ let e = await g(r(_, a), m);
66
+ for (let t of e) try {
67
+ let e = await i(t, "utf8");
68
+ for (let t of d(e, p)) h.add(t);
69
+ } catch {}
70
+ };
71
+ return {
72
+ name: "tempest-icons",
73
+ enforce: "pre",
74
+ configResolved(e) {
75
+ _ = e.root ?? _;
76
+ },
77
+ async buildStart() {
78
+ await v();
79
+ },
80
+ resolveId(e) {
81
+ return e === "virtual:tempest-icons" ? s : null;
82
+ },
83
+ load(t) {
84
+ return t === s ? f([...h], e) : null;
85
+ },
86
+ async handleHotUpdate(e) {
87
+ if (!l.test(e.file)) return;
88
+ let t = h.size;
89
+ for (let t of d(await e.read(), p)) h.add(t);
90
+ if (h.size === t) return;
91
+ let n = e.server.moduleGraph.getModuleById(s);
92
+ if (n) return [n];
93
+ }
94
+ };
95
+ }
96
+ //#endregion
97
+ export { o as TEMPEST_ICONS_ID, f as buildIconsModule, d as scanIconSlugs, _ as tempestIcons };
98
+
99
+ //# sourceMappingURL=tempest-icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tempest-icons.js","names":[],"sources":["../../src/vite/tempest-icons.ts"],"sourcesContent":["import { readdir, readFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\n\nimport { iconAliases } from \"@/icons/generated/aliases\";\nimport { iconNames } from \"@/icons/generated/icon-names\";\n\nimport type { TempestVitePlugin } from \"./tempest-pwa-manifest\";\n\n/** The module id an app imports to get its static registry. */\nexport const TEMPEST_ICONS_ID = \"virtual:tempest-icons\";\n\nconst RESOLVED_ID = `\\0${TEMPEST_ICONS_ID}`;\n\n/**\n * Slugs written as a literal `name` prop or a literal `name:` field.\n *\n * Matching the *attribute* rather than any kebab-case string is what keeps the\n * scan from sweeping in unrelated text — a CSS class, a translation key and a\n * route segment all look like slugs otherwise. Everything matched is still\n * validated against lucide's real slug list before it reaches the module.\n */\nconst NAME_PATTERNS = [\n /\\bname\\s*=\\s*[\"']([a-z0-9-]+)[\"']/g,\n /\\bname\\s*=\\s*\\{\\s*[\"']([a-z0-9-]+)[\"']\\s*\\}/g,\n /\\bname\\s*:\\s*[\"']([a-z0-9-]+)[\"']/g,\n];\n\nconst SOURCE_FILE = /\\.[cm]?[jt]sx?$/;\nconst SKIP_DIRS = new Set([\"node_modules\", \"dist\", \"build\", \"coverage\"]);\n\nexport interface TempestIconsOptions {\n /**\n * Directory to scan, relative to the Vite root. Default: `\"src\"`.\n */\n dir?: string;\n /**\n * Extra slugs to include even when the scan cannot see them — a name built by\n * concatenation, or one that only ever arrives from the API but is worth\n * paying for statically because it is on the first screen.\n */\n include?: readonly string[];\n /** Directory names to skip. Default: `node_modules`, `dist`, `build`, `coverage`. */\n skipDirs?: readonly string[];\n}\n\n/**\n * Collect the icon slugs a source file references literally.\n *\n * @param code - File contents.\n * @param known - The set of real lucide slugs, aliases included.\n * @returns The slugs found, validated against `known`.\n */\nexport function scanIconSlugs(code: string, known: ReadonlySet<string>): string[] {\n const found = new Set<string>();\n for (const pattern of NAME_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(code)) !== null) {\n if (known.has(match[1])) found.add(match[1]);\n }\n }\n return [...found];\n}\n\n/**\n * Build the virtual module source for a set of slugs.\n *\n * Aliases resolve to their canonical slug before importing, because only the\n * canonical name is a real lucide export — but the alias key still points at the\n * component, so `<Icon name=\"alert-circle\" />` stays a zero-request lookup.\n *\n * @param slugs - Validated slugs to include.\n * @param aliases - Alias slug → canonical slug.\n * @returns ES module source.\n */\nexport function buildIconsModule(\n slugs: readonly string[],\n aliases: Record<string, string>,\n): string {\n const sorted = [...new Set(slugs)].sort();\n if (!sorted.length) return \"export const staticIcons = {};\\n\";\n\n const componentOf = new Map<string, string>();\n for (const slug of sorted) componentOf.set(slug, pascalCase(aliases[slug] ?? slug));\n\n const named = [...new Set(componentOf.values())]\n .sort()\n .map((name) => (localName(name) === name ? name : `${name} as ${localName(name)}`))\n .join(\", \");\n const entries = sorted\n .map((slug) => ` \"${slug}\": ${localName(componentOf.get(slug) as string)},`)\n .join(\"\\n\");\n return `import { ${named} } from \"lucide-react\";\n\nexport const staticIcons = {\n${entries}\n};\n`;\n}\n\n/** `circle-alert` → `CircleAlert`; `a-arrow-down` → `AArrowDown`. */\nfunction pascalCase(slug: string): string {\n return slug\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n}\n\n/**\n * Export names that would shadow a restricted global if bound directly.\n *\n * Lucide ships an icon literally called `infinity`, whose export is `Infinity`.\n * Binding that at module scope shadows the global, so the import is aliased.\n */\nconst RESERVED_NAMES = new Set([\"Infinity\", \"NaN\", \"undefined\", \"eval\", \"arguments\"]);\n\n/** The local binding to use for a lucide export name. */\nfunction localName(name: string): string {\n return RESERVED_NAMES.has(name) ? `${name}Icon` : name;\n}\n\n/**\n * Walk a directory and collect every source file path.\n *\n * @param dir - Absolute directory to walk.\n * @param skip - Directory names to skip.\n * @returns Absolute file paths.\n */\nasync function collectSourceFiles(dir: string, skip: ReadonlySet<string>): Promise<string[]> {\n const out: string[] = [];\n let entries;\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch {\n return out;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (skip.has(entry.name) || entry.name.startsWith(\".\")) continue;\n out.push(...(await collectSourceFiles(join(dir, entry.name), skip)));\n } else if (entry.isFile() && SOURCE_FILE.test(entry.name)) {\n out.push(join(dir, entry.name));\n }\n }\n return out;\n}\n\n/**\n * Generate a static icon registry from the slugs your source mentions.\n *\n * Removes the reason to reach for `lucide-react`'s `DynamicIcon`: the slugs an app\n * writes as literals become ordinary static imports, so the bundler keeps exactly\n * those icons and the browser makes **no** extra request for them. A slug that only\n * exists at runtime still works — `<Icon>` falls back to fetching one shard per\n * initial letter.\n *\n * The scan reads the source tree directly at `buildStart` rather than harvesting\n * modules as they pass through `transform`: relying on transform order would let\n * the virtual module load before every consumer had been seen, and the registry\n * would come out short on a cold dev start — silently, since the missing slugs\n * would still render via the lazy path.\n *\n * @example\n * // vite.config.ts\n * import { createViteConfig, tempestIcons } from \"tempest-react-sdk/vite\";\n *\n * export default createViteConfig({ plugins: [tempestIcons()] });\n *\n * @example\n * // src/main.tsx\n * import { IconProvider } from \"tempest-react-sdk/icons\";\n * import { staticIcons } from \"virtual:tempest-icons\";\n *\n * <IconProvider registry={staticIcons}>\n * <App />\n * </IconProvider>\n */\nexport function tempestIcons(options: TempestIconsOptions = {}): TempestVitePlugin {\n const { dir = \"src\", include = [], skipDirs } = options;\n const known = new Set<string>(iconNames);\n const skip = new Set<string>(skipDirs ?? SKIP_DIRS);\n const slugs = new Set<string>(include.filter((slug) => known.has(slug)));\n let root = process.cwd();\n\n /** Rescan the source tree, keeping the explicitly included slugs. */\n const rescan = async (): Promise<void> => {\n const files = await collectSourceFiles(resolve(root, dir), skip);\n for (const file of files) {\n try {\n const code = await readFile(file, \"utf8\");\n for (const slug of scanIconSlugs(code, known)) slugs.add(slug);\n } catch {\n /* an unreadable file just contributes nothing */\n }\n }\n };\n\n return {\n name: \"tempest-icons\",\n enforce: \"pre\",\n\n configResolved(config: { root?: string }) {\n root = config.root ?? root;\n },\n\n async buildStart() {\n await rescan();\n },\n\n resolveId(id: string) {\n return id === TEMPEST_ICONS_ID ? RESOLVED_ID : null;\n },\n\n load(id: string) {\n return id === RESOLVED_ID ? buildIconsModule([...slugs], iconAliases) : null;\n },\n\n /**\n * Pick up a slug added while the dev server is running.\n *\n * A newly written `<Icon name=\"save\" />` would otherwise only render via\n * the lazy path until the next restart. Rescanning the changed file and\n * invalidating the virtual module keeps dev and build agreeing on what the\n * static registry contains.\n */\n async handleHotUpdate(ctx: {\n file: string;\n read: () => Promise<string>;\n server: { moduleGraph: { getModuleById: (id: string) => unknown } };\n }) {\n if (!SOURCE_FILE.test(ctx.file)) return;\n const before = slugs.size;\n for (const slug of scanIconSlugs(await ctx.read(), known)) slugs.add(slug);\n if (slugs.size === before) return;\n const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);\n if (mod) return [mod];\n },\n } as TempestVitePlugin;\n}\n"],"mappings":"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"}
package/dist/vite.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./vite/create-vite-config.cjs"),t=require("./vite/tempest-pwa-manifest.cjs"),n=require("./vite/tempest-pwa-icons.cjs"),r=require("./vite/tempest-pwa-dev-sw.cjs");exports.createViteConfig=e.createViteConfig,exports.tempestPwaDevSw=r.tempestPwaDevSw,exports.tempestPwaIcons=n.tempestPwaIcons,exports.tempestPwaManifest=t.tempestPwaManifest;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./vite/tempest-icons.cjs"),t=require("./vite/create-vite-config.cjs"),n=require("./vite/tempest-pwa-manifest.cjs"),r=require("./vite/tempest-pwa-icons.cjs"),i=require("./vite/tempest-pwa-dev-sw.cjs");exports.TEMPEST_ICONS_ID=e.TEMPEST_ICONS_ID,exports.buildIconsModule=e.buildIconsModule,exports.createViteConfig=t.createViteConfig,exports.scanIconSlugs=e.scanIconSlugs,exports.tempestIcons=e.tempestIcons,exports.tempestPwaDevSw=i.tempestPwaDevSw,exports.tempestPwaIcons=r.tempestPwaIcons,exports.tempestPwaManifest=n.tempestPwaManifest;