one 1.2.82 → 1.2.84

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 (342) hide show
  1. package/devtools/dev.mjs +44 -0
  2. package/devtools/devtools.mjs +540 -0
  3. package/devtools/source-inspector.mjs +201 -0
  4. package/dist/cjs/cli/daemon.cjs +136 -0
  5. package/dist/cjs/cli/daemon.js +112 -0
  6. package/dist/cjs/cli/daemon.js.map +6 -0
  7. package/dist/cjs/cli/daemon.native.js +173 -0
  8. package/dist/cjs/cli/daemon.native.js.map +1 -0
  9. package/dist/cjs/createHandleRequest.cjs +15 -8
  10. package/dist/cjs/createHandleRequest.js +10 -6
  11. package/dist/cjs/createHandleRequest.js.map +1 -1
  12. package/dist/cjs/createHandleRequest.native.js +8 -3
  13. package/dist/cjs/createHandleRequest.native.js.map +1 -1
  14. package/dist/cjs/daemon/index.cjs +24 -0
  15. package/dist/cjs/daemon/index.js +21 -0
  16. package/dist/cjs/daemon/index.js.map +6 -0
  17. package/dist/cjs/daemon/index.native.js +27 -0
  18. package/dist/cjs/daemon/index.native.js.map +1 -0
  19. package/dist/cjs/daemon/ipc.cjs +235 -0
  20. package/dist/cjs/daemon/ipc.js +204 -0
  21. package/dist/cjs/daemon/ipc.js.map +6 -0
  22. package/dist/cjs/daemon/ipc.native.js +276 -0
  23. package/dist/cjs/daemon/ipc.native.js.map +1 -0
  24. package/dist/cjs/daemon/picker.cjs +223 -0
  25. package/dist/cjs/daemon/picker.js +191 -0
  26. package/dist/cjs/daemon/picker.js.map +6 -0
  27. package/dist/cjs/daemon/picker.native.js +308 -0
  28. package/dist/cjs/daemon/picker.native.js.map +1 -0
  29. package/dist/cjs/daemon/proxy.cjs +75 -0
  30. package/dist/cjs/daemon/proxy.js +70 -0
  31. package/dist/cjs/daemon/proxy.js.map +6 -0
  32. package/dist/cjs/daemon/proxy.native.js +81 -0
  33. package/dist/cjs/daemon/proxy.native.js.map +1 -0
  34. package/dist/cjs/daemon/registry.cjs +85 -0
  35. package/dist/cjs/daemon/registry.js +81 -0
  36. package/dist/cjs/daemon/registry.js.map +6 -0
  37. package/dist/cjs/daemon/registry.native.js +120 -0
  38. package/dist/cjs/daemon/registry.native.js.map +1 -0
  39. package/dist/cjs/daemon/server.cjs +178 -0
  40. package/dist/cjs/daemon/server.js +179 -0
  41. package/dist/cjs/daemon/server.js.map +6 -0
  42. package/dist/cjs/daemon/server.native.js +200 -0
  43. package/dist/cjs/daemon/server.native.js.map +1 -0
  44. package/dist/cjs/daemon/tui.cjs +223 -0
  45. package/dist/cjs/daemon/tui.js +192 -0
  46. package/dist/cjs/daemon/tui.js.map +6 -0
  47. package/dist/cjs/daemon/tui.native.js +234 -0
  48. package/dist/cjs/daemon/tui.native.js.map +1 -0
  49. package/dist/cjs/daemon/types.cjs +16 -0
  50. package/dist/cjs/daemon/types.js +14 -0
  51. package/dist/cjs/daemon/types.js.map +6 -0
  52. package/dist/cjs/daemon/types.native.js +19 -0
  53. package/dist/cjs/daemon/types.native.js.map +1 -0
  54. package/dist/cjs/daemon/utils.cjs +74 -0
  55. package/dist/cjs/daemon/utils.js +65 -0
  56. package/dist/cjs/daemon/utils.js.map +6 -0
  57. package/dist/cjs/daemon/utils.native.js +83 -0
  58. package/dist/cjs/daemon/utils.native.js.map +1 -0
  59. package/dist/cjs/fork/createMemoryHistory.cjs +22 -8
  60. package/dist/cjs/fork/createMemoryHistory.js +20 -6
  61. package/dist/cjs/fork/createMemoryHistory.js.map +1 -1
  62. package/dist/cjs/fork/createMemoryHistory.native.js +22 -8
  63. package/dist/cjs/fork/createMemoryHistory.native.js.map +1 -1
  64. package/dist/cjs/fork/useLinking.cjs +52 -13
  65. package/dist/cjs/fork/useLinking.js +35 -10
  66. package/dist/cjs/fork/useLinking.js.map +1 -1
  67. package/dist/cjs/index.cjs +6 -2
  68. package/dist/cjs/index.js +4 -2
  69. package/dist/cjs/index.js.map +2 -2
  70. package/dist/cjs/index.native.js +6 -2
  71. package/dist/cjs/index.native.js.map +1 -1
  72. package/dist/cjs/router/linkingConfig.cjs +13 -2
  73. package/dist/cjs/router/linkingConfig.js +14 -5
  74. package/dist/cjs/router/linkingConfig.js.map +1 -1
  75. package/dist/cjs/router/linkingConfig.native.js +10 -1
  76. package/dist/cjs/router/linkingConfig.native.js.map +1 -1
  77. package/dist/cjs/router/routeMask.cjs +137 -0
  78. package/dist/cjs/router/routeMask.js +127 -0
  79. package/dist/cjs/router/routeMask.js.map +6 -0
  80. package/dist/cjs/router/routeMask.native.js +160 -0
  81. package/dist/cjs/router/routeMask.native.js.map +1 -0
  82. package/dist/cjs/router/router.cjs +26 -3
  83. package/dist/cjs/router/router.js +23 -3
  84. package/dist/cjs/router/router.js.map +1 -1
  85. package/dist/cjs/router/router.native.js +26 -3
  86. package/dist/cjs/router/router.native.js.map +1 -1
  87. package/dist/cjs/router/useScreens.cjs +18 -4
  88. package/dist/cjs/router/useScreens.js +20 -2
  89. package/dist/cjs/router/useScreens.js.map +2 -2
  90. package/dist/cjs/router/useScreens.native.js +16 -0
  91. package/dist/cjs/router/useScreens.native.js.map +1 -1
  92. package/dist/cjs/server/PreloadScripts.native.js +45 -39
  93. package/dist/cjs/server/PreloadScripts.native.js.map +1 -6
  94. package/dist/cjs/ui/TabSlot.cjs +2 -1
  95. package/dist/cjs/ui/TabSlot.js +1 -1
  96. package/dist/cjs/ui/TabSlot.js.map +1 -1
  97. package/dist/cjs/ui/TabSlot.native.js +2 -1
  98. package/dist/cjs/ui/TabSlot.native.js.map +1 -1
  99. package/dist/cjs/ui/Tabs.cjs +8 -4
  100. package/dist/cjs/ui/Tabs.js +8 -4
  101. package/dist/cjs/ui/Tabs.js.map +1 -1
  102. package/dist/cjs/ui/Tabs.native.js +8 -4
  103. package/dist/cjs/ui/Tabs.native.js.map +1 -1
  104. package/dist/cjs/views/Navigator.cjs +13 -12
  105. package/dist/cjs/views/Navigator.js +8 -9
  106. package/dist/cjs/views/Navigator.js.map +1 -1
  107. package/dist/cjs/views/Navigator.native.js +16 -15
  108. package/dist/cjs/views/Navigator.native.js.map +1 -1
  109. package/dist/cjs/vite/DevHead.cjs +2 -909
  110. package/dist/cjs/vite/DevHead.js +1 -917
  111. package/dist/cjs/vite/DevHead.js.map +1 -1
  112. package/dist/cjs/vite/DevHead.native.js +1 -882
  113. package/dist/cjs/vite/DevHead.native.js.map +1 -1
  114. package/dist/cjs/vite/one.cjs +4 -1
  115. package/dist/cjs/vite/one.js +4 -2
  116. package/dist/cjs/vite/one.js.map +1 -1
  117. package/dist/cjs/vite/one.native.js +4 -1
  118. package/dist/cjs/vite/one.native.js.map +1 -1
  119. package/dist/cjs/vite/plugins/devtoolsPlugin.cjs +55 -0
  120. package/dist/cjs/vite/plugins/devtoolsPlugin.js +49 -0
  121. package/dist/cjs/vite/plugins/devtoolsPlugin.js.map +6 -0
  122. package/dist/cjs/vite/plugins/devtoolsPlugin.native.js +58 -0
  123. package/dist/cjs/vite/plugins/devtoolsPlugin.native.js.map +1 -0
  124. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.cjs +1 -6
  125. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.js +1 -6
  126. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.js.map +1 -1
  127. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js +1 -6
  128. package/dist/cjs/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  129. package/dist/cjs/vite/plugins/virtualEntryPlugin.cjs +7 -1
  130. package/dist/cjs/vite/plugins/virtualEntryPlugin.js +5 -1
  131. package/dist/cjs/vite/plugins/virtualEntryPlugin.js.map +1 -1
  132. package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js +8 -1
  133. package/dist/cjs/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
  134. package/dist/esm/cli/daemon.js +89 -0
  135. package/dist/esm/cli/daemon.js.map +6 -0
  136. package/dist/esm/cli/daemon.mjs +102 -0
  137. package/dist/esm/cli/daemon.mjs.map +1 -0
  138. package/dist/esm/cli/daemon.native.js +136 -0
  139. package/dist/esm/cli/daemon.native.js.map +1 -0
  140. package/dist/esm/createHandleRequest.js +10 -5
  141. package/dist/esm/createHandleRequest.js.map +1 -1
  142. package/dist/esm/createHandleRequest.mjs +15 -8
  143. package/dist/esm/createHandleRequest.mjs.map +1 -1
  144. package/dist/esm/createHandleRequest.native.js +8 -3
  145. package/dist/esm/createHandleRequest.native.js.map +1 -1
  146. package/dist/esm/daemon/index.js +8 -0
  147. package/dist/esm/daemon/index.js.map +6 -0
  148. package/dist/esm/daemon/index.mjs +8 -0
  149. package/dist/esm/daemon/index.mjs.map +1 -0
  150. package/dist/esm/daemon/index.native.js +8 -0
  151. package/dist/esm/daemon/index.native.js.map +1 -0
  152. package/dist/esm/daemon/ipc.js +192 -0
  153. package/dist/esm/daemon/ipc.js.map +6 -0
  154. package/dist/esm/daemon/ipc.mjs +191 -0
  155. package/dist/esm/daemon/ipc.mjs.map +1 -0
  156. package/dist/esm/daemon/ipc.native.js +229 -0
  157. package/dist/esm/daemon/ipc.native.js.map +1 -0
  158. package/dist/esm/daemon/picker.js +169 -0
  159. package/dist/esm/daemon/picker.js.map +6 -0
  160. package/dist/esm/daemon/picker.mjs +186 -0
  161. package/dist/esm/daemon/picker.mjs.map +1 -0
  162. package/dist/esm/daemon/picker.native.js +268 -0
  163. package/dist/esm/daemon/picker.native.js.map +1 -0
  164. package/dist/esm/daemon/proxy.js +47 -0
  165. package/dist/esm/daemon/proxy.js.map +6 -0
  166. package/dist/esm/daemon/proxy.mjs +40 -0
  167. package/dist/esm/daemon/proxy.mjs.map +1 -0
  168. package/dist/esm/daemon/proxy.native.js +43 -0
  169. package/dist/esm/daemon/proxy.native.js.map +1 -0
  170. package/dist/esm/daemon/registry.js +65 -0
  171. package/dist/esm/daemon/registry.js.map +6 -0
  172. package/dist/esm/daemon/registry.mjs +53 -0
  173. package/dist/esm/daemon/registry.mjs.map +1 -0
  174. package/dist/esm/daemon/registry.native.js +85 -0
  175. package/dist/esm/daemon/registry.native.js.map +1 -0
  176. package/dist/esm/daemon/server.js +167 -0
  177. package/dist/esm/daemon/server.js.map +6 -0
  178. package/dist/esm/daemon/server.mjs +143 -0
  179. package/dist/esm/daemon/server.mjs.map +1 -0
  180. package/dist/esm/daemon/server.native.js +162 -0
  181. package/dist/esm/daemon/server.native.js.map +1 -0
  182. package/dist/esm/daemon/tui.js +171 -0
  183. package/dist/esm/daemon/tui.js.map +6 -0
  184. package/dist/esm/daemon/tui.mjs +187 -0
  185. package/dist/esm/daemon/tui.mjs.map +1 -0
  186. package/dist/esm/daemon/tui.native.js +195 -0
  187. package/dist/esm/daemon/tui.native.js.map +1 -0
  188. package/dist/esm/daemon/types.js +1 -0
  189. package/dist/esm/daemon/types.js.map +6 -0
  190. package/dist/esm/daemon/types.mjs +2 -0
  191. package/dist/esm/daemon/types.mjs.map +1 -0
  192. package/dist/esm/daemon/types.native.js +2 -0
  193. package/dist/esm/daemon/types.native.js.map +1 -0
  194. package/dist/esm/daemon/utils.js +42 -0
  195. package/dist/esm/daemon/utils.js.map +6 -0
  196. package/dist/esm/daemon/utils.mjs +39 -0
  197. package/dist/esm/daemon/utils.mjs.map +1 -0
  198. package/dist/esm/daemon/utils.native.js +45 -0
  199. package/dist/esm/daemon/utils.native.js.map +1 -0
  200. package/dist/esm/fork/createMemoryHistory.js +20 -6
  201. package/dist/esm/fork/createMemoryHistory.js.map +1 -1
  202. package/dist/esm/fork/createMemoryHistory.mjs +22 -8
  203. package/dist/esm/fork/createMemoryHistory.mjs.map +1 -1
  204. package/dist/esm/fork/createMemoryHistory.native.js +22 -8
  205. package/dist/esm/fork/createMemoryHistory.native.js.map +1 -1
  206. package/dist/esm/fork/useLinking.js +35 -9
  207. package/dist/esm/fork/useLinking.js.map +1 -1
  208. package/dist/esm/fork/useLinking.mjs +52 -13
  209. package/dist/esm/fork/useLinking.mjs.map +1 -1
  210. package/dist/esm/index.js +4 -0
  211. package/dist/esm/index.js.map +1 -1
  212. package/dist/esm/index.mjs +3 -1
  213. package/dist/esm/index.mjs.map +1 -1
  214. package/dist/esm/index.native.js +3 -1
  215. package/dist/esm/index.native.js.map +1 -1
  216. package/dist/esm/router/linkingConfig.js +14 -4
  217. package/dist/esm/router/linkingConfig.js.map +1 -1
  218. package/dist/esm/router/linkingConfig.mjs +12 -1
  219. package/dist/esm/router/linkingConfig.mjs.map +1 -1
  220. package/dist/esm/router/linkingConfig.native.js +10 -1
  221. package/dist/esm/router/linkingConfig.native.js.map +1 -1
  222. package/dist/esm/router/routeMask.js +111 -0
  223. package/dist/esm/router/routeMask.js.map +6 -0
  224. package/dist/esm/router/routeMask.mjs +108 -0
  225. package/dist/esm/router/routeMask.mjs.map +1 -0
  226. package/dist/esm/router/routeMask.native.js +128 -0
  227. package/dist/esm/router/routeMask.native.js.map +1 -0
  228. package/dist/esm/router/router.js +23 -2
  229. package/dist/esm/router/router.js.map +1 -1
  230. package/dist/esm/router/router.mjs +24 -3
  231. package/dist/esm/router/router.mjs.map +1 -1
  232. package/dist/esm/router/router.native.js +24 -3
  233. package/dist/esm/router/router.native.js.map +1 -1
  234. package/dist/esm/router/useScreens.js +20 -2
  235. package/dist/esm/router/useScreens.js.map +1 -1
  236. package/dist/esm/router/useScreens.mjs +18 -4
  237. package/dist/esm/router/useScreens.mjs.map +1 -1
  238. package/dist/esm/router/useScreens.native.js +16 -0
  239. package/dist/esm/router/useScreens.native.js.map +1 -1
  240. package/dist/esm/server/PreloadScripts.native.js +29 -33
  241. package/dist/esm/server/PreloadScripts.native.js.map +1 -6
  242. package/dist/esm/ui/TabSlot.js +1 -1
  243. package/dist/esm/ui/TabSlot.js.map +1 -1
  244. package/dist/esm/ui/TabSlot.mjs +2 -1
  245. package/dist/esm/ui/TabSlot.mjs.map +1 -1
  246. package/dist/esm/ui/TabSlot.native.js +2 -1
  247. package/dist/esm/ui/TabSlot.native.js.map +1 -1
  248. package/dist/esm/ui/Tabs.js +10 -5
  249. package/dist/esm/ui/Tabs.js.map +1 -1
  250. package/dist/esm/ui/Tabs.mjs +9 -5
  251. package/dist/esm/ui/Tabs.mjs.map +1 -1
  252. package/dist/esm/ui/Tabs.native.js +9 -5
  253. package/dist/esm/ui/Tabs.native.js.map +1 -1
  254. package/dist/esm/views/Navigator.js +8 -9
  255. package/dist/esm/views/Navigator.js.map +1 -1
  256. package/dist/esm/views/Navigator.mjs +13 -12
  257. package/dist/esm/views/Navigator.mjs.map +1 -1
  258. package/dist/esm/views/Navigator.native.js +16 -15
  259. package/dist/esm/views/Navigator.native.js.map +1 -1
  260. package/dist/esm/vite/DevHead.js +1 -917
  261. package/dist/esm/vite/DevHead.js.map +1 -1
  262. package/dist/esm/vite/DevHead.mjs +2 -909
  263. package/dist/esm/vite/DevHead.mjs.map +1 -1
  264. package/dist/esm/vite/DevHead.native.js +0 -881
  265. package/dist/esm/vite/DevHead.native.js.map +1 -1
  266. package/dist/esm/vite/one.js +4 -1
  267. package/dist/esm/vite/one.js.map +1 -1
  268. package/dist/esm/vite/one.mjs +4 -1
  269. package/dist/esm/vite/one.mjs.map +1 -1
  270. package/dist/esm/vite/one.native.js +4 -1
  271. package/dist/esm/vite/one.native.js.map +1 -1
  272. package/dist/esm/vite/plugins/devtoolsPlugin.js +33 -0
  273. package/dist/esm/vite/plugins/devtoolsPlugin.js.map +6 -0
  274. package/dist/esm/vite/plugins/devtoolsPlugin.mjs +31 -0
  275. package/dist/esm/vite/plugins/devtoolsPlugin.mjs.map +1 -0
  276. package/dist/esm/vite/plugins/devtoolsPlugin.native.js +31 -0
  277. package/dist/esm/vite/plugins/devtoolsPlugin.native.js.map +1 -0
  278. package/dist/esm/vite/plugins/fileSystemRouterPlugin.js +1 -6
  279. package/dist/esm/vite/plugins/fileSystemRouterPlugin.js.map +1 -1
  280. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs +1 -6
  281. package/dist/esm/vite/plugins/fileSystemRouterPlugin.mjs.map +1 -1
  282. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js +1 -6
  283. package/dist/esm/vite/plugins/fileSystemRouterPlugin.native.js.map +1 -1
  284. package/dist/esm/vite/plugins/virtualEntryPlugin.js +5 -1
  285. package/dist/esm/vite/plugins/virtualEntryPlugin.js.map +1 -1
  286. package/dist/esm/vite/plugins/virtualEntryPlugin.mjs +7 -1
  287. package/dist/esm/vite/plugins/virtualEntryPlugin.mjs.map +1 -1
  288. package/dist/esm/vite/plugins/virtualEntryPlugin.native.js +8 -1
  289. package/dist/esm/vite/plugins/virtualEntryPlugin.native.js.map +1 -1
  290. package/package.json +13 -11
  291. package/src/createHandleRequest.ts +16 -3
  292. package/src/fork/createMemoryHistory.tsx +39 -4
  293. package/src/fork/useLinking.ts +83 -12
  294. package/src/index.ts +3 -0
  295. package/src/router/linkingConfig.ts +21 -4
  296. package/src/router/routeMask.ts +293 -0
  297. package/src/router/router.ts +53 -1
  298. package/src/router/useScreens.tsx +54 -2
  299. package/src/ui/TabSlot.tsx +2 -1
  300. package/src/ui/Tabs.tsx +12 -5
  301. package/src/views/Navigator.tsx +14 -9
  302. package/src/vite/DevHead.tsx +1 -924
  303. package/src/vite/one.ts +4 -0
  304. package/src/vite/plugins/devtoolsPlugin.ts +45 -0
  305. package/src/vite/plugins/fileSystemRouterPlugin.tsx +2 -7
  306. package/src/vite/plugins/virtualEntryPlugin.ts +12 -3
  307. package/src/vite/types.ts +1 -0
  308. package/types/cli/daemon.d.ts.map +1 -0
  309. package/types/createHandleRequest.d.ts.map +1 -1
  310. package/types/daemon/index.d.ts.map +1 -0
  311. package/types/daemon/ipc.d.ts.map +1 -0
  312. package/types/daemon/picker.d.ts.map +1 -0
  313. package/types/daemon/proxy.d.ts.map +1 -0
  314. package/types/daemon/registry.d.ts.map +1 -0
  315. package/types/daemon/server.d.ts.map +1 -0
  316. package/types/daemon/tui.d.ts.map +1 -0
  317. package/types/daemon/types.d.ts.map +1 -0
  318. package/types/daemon/utils.d.ts.map +1 -0
  319. package/types/fork/createMemoryHistory.d.ts +6 -2
  320. package/types/fork/createMemoryHistory.d.ts.map +1 -1
  321. package/types/fork/useLinking.d.ts.map +1 -1
  322. package/types/index.d.ts +2 -0
  323. package/types/index.d.ts.map +1 -1
  324. package/types/router/linkingConfig.d.ts.map +1 -1
  325. package/types/router/routeMask.d.ts +130 -0
  326. package/types/router/routeMask.d.ts.map +1 -0
  327. package/types/router/router.d.ts +23 -0
  328. package/types/router/router.d.ts.map +1 -1
  329. package/types/router/useScreens.d.ts.map +1 -1
  330. package/types/ui/TabSlot.d.ts.map +1 -1
  331. package/types/ui/Tabs.d.ts +1 -16
  332. package/types/ui/Tabs.d.ts.map +1 -1
  333. package/types/views/Navigator.d.ts +4 -14
  334. package/types/views/Navigator.d.ts.map +1 -1
  335. package/types/vite/DevHead.d.ts.map +1 -1
  336. package/types/vite/one.d.ts.map +1 -1
  337. package/types/vite/plugins/devtoolsPlugin.d.ts +5 -0
  338. package/types/vite/plugins/devtoolsPlugin.d.ts.map +1 -0
  339. package/types/vite/plugins/fileSystemRouterPlugin.d.ts.map +1 -1
  340. package/types/vite/plugins/virtualEntryPlugin.d.ts +1 -3
  341. package/types/vite/plugins/virtualEntryPlugin.d.ts.map +1 -1
  342. package/types/vite/types.d.ts.map +1 -1
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Configuration for a route mask.
3
+ * Route masking displays a different URL in the browser than the actual route being rendered.
4
+ * Uses history.state to store the actual route, so on page reload the router can restore the original route.
5
+ */
6
+ export interface RouteMaskOptions {
7
+ /**
8
+ * The route pattern to match (the actual route being navigated to).
9
+ * Supports dynamic segments like [id] or $id.
10
+ *
11
+ * @example '/photos/[id]/modal' or '/photos/$photoId/modal'
12
+ */
13
+ from: string
14
+
15
+ /**
16
+ * The route pattern to display in the browser URL (the masked URL).
17
+ * Supports dynamic segments that will be populated from the matched params.
18
+ *
19
+ * @example '/photos/[id]' or '/photos/$photoId'
20
+ */
21
+ to: string
22
+
23
+ /**
24
+ * How to handle parameters when masking.
25
+ * - `true` (default): Forward all matched params to the masked URL
26
+ * - `false`: Don't forward any params
27
+ * - Function: Custom param transformation
28
+ *
29
+ * @default true
30
+ */
31
+ params?: boolean | ((matchedParams: Record<string, string>) => Record<string, string>)
32
+
33
+ /**
34
+ * If true, unmask when the page is reloaded (show the masked URL's content).
35
+ * If false (default), keep the actual route on reload (show the original content).
36
+ *
37
+ * When false: Reload at /photos/5 → still shows /photos/5/modal content
38
+ * When true: Reload at /photos/5 → shows /photos/5 content
39
+ *
40
+ * @default false
41
+ */
42
+ unmaskOnReload?: boolean
43
+
44
+ /**
45
+ * If true, encode the actual route as a base64 postfix in the URL pathname instead of history.state.
46
+ *
47
+ * URL will look like: /photos/5__L3Bob3Rvcy81L21vZGFs
48
+ *
49
+ * Benefits:
50
+ * - Server can parse the postfix and render the actual route (no SSR flash)
51
+ * - URL contains the "truth" about what to render
52
+ * - Works consistently across SSR, SSG, and SPA
53
+ * - No query parameter visible
54
+ *
55
+ * Tradeoffs:
56
+ * - URL has a base64 suffix visible after `__`
57
+ *
58
+ * @default false
59
+ */
60
+ useSearchParam?: boolean
61
+ }
62
+
63
+ /**
64
+ * A compiled route mask ready for matching.
65
+ */
66
+ export interface RouteMask extends RouteMaskOptions {
67
+ /** Regex pattern for matching the 'from' route */
68
+ _fromRegex: RegExp
69
+ /** Parameter names extracted from the 'from' pattern */
70
+ _fromParams: string[]
71
+ }
72
+
73
+ /**
74
+ * Creates a route mask for automatic URL masking during navigation.
75
+ *
76
+ * Route masking displays a different URL in the browser than the actual route.
77
+ * Uses browser history.state to store the actual route, enabling:
78
+ * - Modal overlays with clean shareable URLs
79
+ * - Different URL for in-app navigation vs direct access
80
+ *
81
+ * @example
82
+ * ```tsx
83
+ * import { createRouteMask } from 'one'
84
+ *
85
+ * const photoMask = createRouteMask({
86
+ * from: '/photos/[id]/modal',
87
+ * to: '/photos/[id]',
88
+ * params: true,
89
+ * })
90
+ *
91
+ * // In vite.config.ts:
92
+ * one({
93
+ * router: { routeMasks: [photoMask] },
94
+ * })
95
+ * ```
96
+ */
97
+ export function createRouteMask(options: RouteMaskOptions): RouteMask {
98
+ const {
99
+ from,
100
+ to,
101
+ params = true,
102
+ unmaskOnReload = false,
103
+ useSearchParam = false,
104
+ } = options
105
+
106
+ // Extract parameter names and build regex from the 'from' pattern
107
+ const fromParams: string[] = []
108
+ const fromRegexStr = from
109
+ .split('/')
110
+ .map((segment) => {
111
+ // Handle catch-all segments [...rest] or $...rest
112
+ if (segment.startsWith('[...') && segment.endsWith(']')) {
113
+ const paramName = segment.slice(4, -1)
114
+ fromParams.push(paramName)
115
+ return '(.+)'
116
+ }
117
+ if (segment.startsWith('$...')) {
118
+ const paramName = segment.slice(4)
119
+ fromParams.push(paramName)
120
+ return '(.+)'
121
+ }
122
+ // Handle dynamic segments [id] or $id
123
+ if (segment.startsWith('[') && segment.endsWith(']')) {
124
+ const paramName = segment.slice(1, -1)
125
+ fromParams.push(paramName)
126
+ return '([^/]+)'
127
+ }
128
+ if (segment.startsWith('$')) {
129
+ const paramName = segment.slice(1)
130
+ fromParams.push(paramName)
131
+ return '([^/]+)'
132
+ }
133
+ // Static segment - escape regex special chars
134
+ return segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
135
+ })
136
+ .join('/')
137
+
138
+ return {
139
+ from,
140
+ to,
141
+ params,
142
+ unmaskOnReload,
143
+ useSearchParam,
144
+ _fromRegex: new RegExp(`^${fromRegexStr}$`),
145
+ _fromParams: fromParams,
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Matches a pathname against a route mask.
151
+ * Returns the matched params if successful, or null if no match.
152
+ */
153
+ export function matchRouteMask(
154
+ pathname: string,
155
+ mask: RouteMask
156
+ ): Record<string, string> | null {
157
+ const match = pathname.match(mask._fromRegex)
158
+ if (!match) return null
159
+
160
+ const params: Record<string, string> = {}
161
+ mask._fromParams.forEach((paramName, index) => {
162
+ params[paramName] = match[index + 1]
163
+ })
164
+
165
+ return params
166
+ }
167
+
168
+ /**
169
+ * Builds the masked URL from a route mask and matched params.
170
+ */
171
+ export function buildMaskedPath(
172
+ mask: RouteMask,
173
+ matchedParams: Record<string, string>
174
+ ): string {
175
+ // Determine which params to use
176
+ let params: Record<string, string>
177
+ if (mask.params === false) {
178
+ params = {}
179
+ } else if (typeof mask.params === 'function') {
180
+ params = mask.params(matchedParams)
181
+ } else {
182
+ params = matchedParams
183
+ }
184
+
185
+ // Build the masked URL by replacing dynamic segments
186
+ return mask.to
187
+ .split('/')
188
+ .map((segment) => {
189
+ // Handle [...rest] or $...rest
190
+ if (segment.startsWith('[...') && segment.endsWith(']')) {
191
+ const paramName = segment.slice(4, -1)
192
+ return params[paramName] ?? ''
193
+ }
194
+ if (segment.startsWith('$...')) {
195
+ const paramName = segment.slice(4)
196
+ return params[paramName] ?? ''
197
+ }
198
+ // Handle [id] or $id
199
+ if (segment.startsWith('[') && segment.endsWith(']')) {
200
+ const paramName = segment.slice(1, -1)
201
+ return params[paramName] ?? ''
202
+ }
203
+ if (segment.startsWith('$')) {
204
+ const paramName = segment.slice(1)
205
+ return params[paramName] ?? ''
206
+ }
207
+ return segment
208
+ })
209
+ .join('/')
210
+ }
211
+
212
+ /**
213
+ * URL-safe base64 encode for the _unmask suffix.
214
+ * Replaces +/= with URL-safe characters so the param looks like an opaque token.
215
+ */
216
+ export function encodeUnmask(path: string): string {
217
+ return btoa(path).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
218
+ }
219
+
220
+ /**
221
+ * Decode a URL-safe base64 _unmask value back to the original path.
222
+ */
223
+ export function decodeUnmask(encoded: string): string {
224
+ const base64 = encoded.replace(/-/g, '+').replace(/_/g, '/')
225
+ return atob(base64)
226
+ }
227
+
228
+ /**
229
+ * The separator used in the pathname postfix for route unmasking.
230
+ * e.g. /photos/3__L3Bob3Rvcy8zL21vZGFs
231
+ */
232
+ const UNMASK_SEPARATOR = '__'
233
+
234
+ /**
235
+ * Parse the base64-encoded unmask suffix from a pathname.
236
+ * Looks for `__` separator in the last path segment.
237
+ * Returns the decoded actual path, or null if no unmask suffix found.
238
+ *
239
+ * @example
240
+ * parseUnmaskFromPath('/photos/3__L3Bob3Rvcy8zL21vZGFs') // '/photos/3/modal'
241
+ * parseUnmaskFromPath('/photos/3') // null
242
+ */
243
+ export function parseUnmaskFromPath(pathname: string): string | null {
244
+ const lastSlash = pathname.lastIndexOf('/')
245
+ const lastSegment = pathname.slice(lastSlash + 1)
246
+ const separatorIndex = lastSegment.indexOf(UNMASK_SEPARATOR)
247
+
248
+ if (separatorIndex === -1) return null
249
+
250
+ const encoded = lastSegment.slice(separatorIndex + UNMASK_SEPARATOR.length)
251
+ if (!encoded) return null
252
+
253
+ try {
254
+ return decodeUnmask(encoded)
255
+ } catch {
256
+ return null
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Finds a matching route mask for a given pathname.
262
+ * Returns the mask result to apply, or undefined if no match.
263
+ */
264
+ export function findMatchingMask(
265
+ pathname: string,
266
+ routeMasks: RouteMask[]
267
+ ):
268
+ | {
269
+ maskedPath: string
270
+ unmaskOnReload: boolean
271
+ useSearchParam: boolean
272
+ actualPath: string
273
+ }
274
+ | undefined {
275
+ for (const mask of routeMasks) {
276
+ const matchedParams = matchRouteMask(pathname, mask)
277
+ if (matchedParams) {
278
+ const maskedPath = buildMaskedPath(mask, matchedParams)
279
+ const useSearchParam = mask.useSearchParam ?? false
280
+ return {
281
+ // If useSearchParam is true, append base64-encoded actual path as pathname postfix
282
+ // e.g. /photos/3__L3Bob3Rvcy8zL21vZGFs
283
+ maskedPath: useSearchParam
284
+ ? `${maskedPath}${UNMASK_SEPARATOR}${encodeUnmask(pathname)}`
285
+ : maskedPath,
286
+ unmaskOnReload: mask.unmaskOnReload ?? false,
287
+ useSearchParam,
288
+ actualPath: pathname,
289
+ }
290
+ }
291
+ }
292
+ return undefined
293
+ }
@@ -51,11 +51,43 @@ import { preloadRouteModules } from './useViteRoutes'
51
51
  import { getNavigateAction } from './utils/getNavigateAction'
52
52
  import { setClientMatches } from '../useMatches'
53
53
  import type { RouteMatch } from '../useMatches'
54
+ import { type RouteMask, findMatchingMask } from './routeMask'
54
55
 
55
56
  // Module-scoped variables
56
57
  export let routeNode: RouteNode | null = null
57
58
  export let rootComponent: ComponentType
58
59
 
60
+ // Route masks for automatic URL masking
61
+ let routeMasks: RouteMask[] = []
62
+
63
+ /**
64
+ * Set route masks for automatic URL masking during navigation.
65
+ * Route masks transform URLs displayed in the browser without changing the actual route.
66
+ *
67
+ * @example
68
+ * ```tsx
69
+ * import { setRouteMasks, createRouteMask } from 'one'
70
+ *
71
+ * setRouteMasks([
72
+ * createRouteMask({
73
+ * from: '/photos/[id]/modal',
74
+ * to: '/photos/[id]',
75
+ * unmaskOnReload: true,
76
+ * }),
77
+ * ])
78
+ * ```
79
+ */
80
+ export function setRouteMasks(masks: RouteMask[]) {
81
+ routeMasks = masks
82
+ }
83
+
84
+ /**
85
+ * Get the current route masks.
86
+ */
87
+ export function getRouteMasks(): RouteMask[] {
88
+ return routeMasks
89
+ }
90
+
59
91
  // Global registry for protected routes
60
92
  // Key: contextKey (e.g., '/protected-test'), Value: Set of protected route names
61
93
  const protectedRouteRegistry = new Map<string, Set<string>>()
@@ -1002,8 +1034,28 @@ export async function linkTo(
1002
1034
  hashes[rootState.key] = href.slice(hash)
1003
1035
  }
1004
1036
 
1037
+ // Auto-apply route mask if no explicit mask provided and routeMasks are configured
1038
+ let finalOptions = options ?? null
1039
+ if (!finalOptions?.mask && routeMasks.length > 0) {
1040
+ // Extract pathname from href (remove search params and hash)
1041
+ const pathname = extractPathnameFromHref(href)
1042
+ const maskResult = findMatchingMask(pathname, routeMasks)
1043
+ if (maskResult) {
1044
+ finalOptions = {
1045
+ ...finalOptions,
1046
+ mask: {
1047
+ href: maskResult.maskedPath,
1048
+ unmaskOnReload: maskResult.unmaskOnReload,
1049
+ },
1050
+ }
1051
+ if (process.env.ONE_DEBUG_ROUTER) {
1052
+ console.info(`[one] 🎭 Auto-masked ${pathname} → ${maskResult.maskedPath}`)
1053
+ }
1054
+ }
1055
+ }
1056
+
1005
1057
  // a bit hacky until can figure out a reliable way to tie it to the state
1006
- nextOptions = options ?? null
1058
+ nextOptions = finalOptions
1007
1059
 
1008
1060
  startTransition(() => {
1009
1061
  const action = getNavigateAction(state, rootState, event)
@@ -7,7 +7,7 @@ import type {
7
7
  RouteProp,
8
8
  ScreenListeners,
9
9
  } from '@react-navigation/native'
10
- import React, { memo, Suspense, useEffect, useId, useState } from 'react'
10
+ import React, { memo, Suspense, useEffect, useState } from 'react'
11
11
  import { SafeAreaView, ScrollView, Text, TouchableOpacity, View } from 'react-native'
12
12
  import { ServerContextScript } from '../server/ServerContextScript'
13
13
  import { getPageExport } from '../utils/getPageExport'
@@ -27,6 +27,46 @@ import { sortRoutesWithInitial } from './sortRoutes'
27
27
 
28
28
  // `@react-navigation/core` does not expose the Screen or Group components directly, so we have to
29
29
  // do this hack.
30
+
31
+ /**
32
+ * Recursively check if React children contain a <meta charSet /> element.
33
+ * This is used to warn developers if they're missing the charset meta tag
34
+ * which can cause React hydration issues due to encoding mismatch.
35
+ */
36
+ function hasMetaCharset(children: React.ReactNode): boolean {
37
+ if (process.env.NODE_ENV === 'development') {
38
+ if (!children) return false
39
+
40
+ const checkElement = (child: React.ReactNode): boolean => {
41
+ if (!React.isValidElement(child)) return false
42
+
43
+ // check if this is a <meta charSet /> or <meta charset />
44
+ if (child.type === 'meta') {
45
+ const props = child.props as Record<string, unknown>
46
+ if ('charSet' in props || 'charset' in props) {
47
+ return true
48
+ }
49
+ }
50
+
51
+ // recurse into children
52
+ const childProps = child.props as { children?: React.ReactNode }
53
+ if (childProps.children) {
54
+ return hasMetaCharset(childProps.children)
55
+ }
56
+
57
+ return false
58
+ }
59
+
60
+ if (Array.isArray(children)) {
61
+ return children.some(checkElement)
62
+ }
63
+
64
+ return checkElement(children)
65
+ }
66
+
67
+ return true
68
+ }
69
+
30
70
  export const { Screen, Group } = createNavigatorFactory({} as any)()
31
71
 
32
72
  // Cache inline CSS elements at module load (before React hydrates).
@@ -89,10 +129,22 @@ function RootLayoutRenderer({
89
129
  return finalChildren
90
130
  }
91
131
 
132
+ if (process.env.NODE_ENV === 'development') {
133
+ // check if <meta charSet /> is present in head children
134
+ // if not, warn that it must be set in root _layout.tsx or else
135
+ // React will have hydration issues as it switches encoding
136
+ if (!hasMetaCharset(headChildren)) {
137
+ console.warn(
138
+ `[one] Missing <meta charSet="utf-8" /> in your root _layout.tsx <head>. ` +
139
+ `This can cause React hydration issues due to encoding mismatch. ` +
140
+ `Add it as the first element in your <head> tag.`
141
+ )
142
+ }
143
+ }
144
+
92
145
  finalChildren = (
93
146
  <>
94
147
  <head key="head" {...headProps}>
95
- <meta charSet="utf-8" />
96
148
  <DevHead />
97
149
  <script
98
150
  dangerouslySetInnerHTML={{
@@ -55,7 +55,8 @@ export function useTabSlot({
55
55
  style,
56
56
  renderFn = defaultTabsSlotRender,
57
57
  }: TabSlotProps = {}) {
58
- const { state, descriptors } = useNavigatorContext()
58
+ const { state, descriptorsRef } = useNavigatorContext()
59
+ const descriptors = descriptorsRef.current
59
60
  const focusedRouteKey = state.routes[state.index].key
60
61
  const [loaded, setLoaded] = useState({ [focusedRouteKey]: true })
61
62
 
package/src/ui/Tabs.tsx CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  type ReactNode,
19
19
  use,
20
20
  useMemo,
21
+ useRef,
21
22
  } from 'react'
22
23
  import { StyleSheet, View, type ViewProps } from 'react-native'
23
24
  import { useRouteInfo } from '../hooks'
@@ -42,7 +43,7 @@ type NavigatorContextValue = {
42
43
  contextKey: string
43
44
  state: ReturnType<typeof useNavigationBuilder>['state']
44
45
  navigation: ReturnType<typeof useNavigationBuilder>['navigation']
45
- descriptors: ReturnType<typeof useNavigationBuilder>['descriptors']
46
+ descriptorsRef: React.MutableRefObject<ReturnType<typeof useNavigationBuilder>['descriptors']>
46
47
  router: RouterFactory<any, any, any>
47
48
  }
48
49
 
@@ -208,14 +209,20 @@ export function useTabsWithTriggers(
208
209
  NavigationContent: RNNavigationContent,
209
210
  } = navigatorContext
210
211
 
211
- const navigatorContextValue = useMemo<NavigatorContextValue>(
212
+ // HYDRATION FIX: Use ref for descriptors to avoid context invalidation during hydration.
213
+ const descriptorsRef = useRef(descriptors)
214
+ descriptorsRef.current = descriptors
215
+
216
+ const navigatorContextValue = useMemo(
212
217
  () => ({
213
- ...(navigatorContext as unknown as ReturnType<typeof useNavigationBuilder>),
218
+ state,
219
+ navigation,
214
220
  contextKey,
215
221
  router: ExpoTabRouter,
222
+ descriptorsRef,
216
223
  }),
217
- [navigatorContext, contextKey, ExpoTabRouter]
218
- )
224
+ [state, navigation, contextKey, ExpoTabRouter]
225
+ ) as unknown as NavigatorContextValue
219
226
 
220
227
  const NavigationContent = useComponent((children: React.ReactNode) => (
221
228
  <TabTriggerMapContext.Provider value={triggerMap}>
@@ -24,7 +24,7 @@ export const NavigatorContext = React.createContext<{
24
24
  contextKey: string
25
25
  state: NavigatorTypes['state']
26
26
  navigation: NavigatorTypes['navigation']
27
- descriptors: NavigatorTypes['descriptors']
27
+ descriptorsRef: React.MutableRefObject<NavigatorTypes['descriptors']>
28
28
  router: RouterFactory<any, any, any>
29
29
  } | null>(null)
30
30
 
@@ -122,15 +122,22 @@ function QualifiedNavigator({
122
122
  }
123
123
  )
124
124
 
125
+ // HYDRATION FIX: Use ref for descriptors to avoid context invalidation during hydration.
126
+ // The descriptors object changes by reference on each render from useNavigationBuilder,
127
+ // but the actual content is the same. By using a ref, we prevent unnecessary re-renders
128
+ // that cause React to abandon hydration and remount the component tree.
129
+ const descriptorsRef = React.useRef(descriptors)
130
+ descriptorsRef.current = descriptors
131
+
125
132
  const value = React.useMemo(() => {
126
133
  return {
127
134
  contextKey,
128
135
  state,
129
136
  navigation,
130
- descriptors,
137
+ descriptorsRef,
131
138
  router,
132
139
  }
133
- }, [contextKey, state, navigation, descriptors, router])
140
+ }, [contextKey, state, navigation, router])
134
141
 
135
142
  return (
136
143
  <NavigatorContext.Provider value={value}>
@@ -149,7 +156,7 @@ export function useNavigatorContext() {
149
156
 
150
157
  export function useSlot() {
151
158
  const context = useNavigatorContext()
152
- const { state, descriptors } = context
159
+ const { state, descriptorsRef } = context
153
160
 
154
161
  const current = state.routes.find((route, i) => {
155
162
  return state.index === i
@@ -159,15 +166,13 @@ export function useSlot() {
159
166
  return null
160
167
  }
161
168
 
162
- const renderedElement = descriptors[current.key]?.render() ?? null
169
+ const renderedElement = descriptorsRef.current[current.key]?.render() ?? null
163
170
 
164
171
  // Use static key to prevent layout remounts when route keys change during navigation.
165
172
  // Safe because Slot only renders one screen at a time.
173
+ // Use cloneElement to properly clone the React element with a new key.
166
174
  if (renderedElement !== null) {
167
- return {
168
- ...renderedElement,
169
- key: SLOT_STATIC_KEY,
170
- }
175
+ return React.cloneElement(renderedElement, { key: SLOT_STATIC_KEY })
171
176
  }
172
177
 
173
178
  return renderedElement