soseki 0.0.4 → 0.0.6

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 (352) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/components/browser-router.d.ts +19 -0
  3. package/dist/src/components/browser-router.d.ts.map +1 -0
  4. package/dist/src/components/browser-router.jsx +15 -0
  5. package/dist/src/components/outlet.d.ts +8 -0
  6. package/dist/src/components/outlet.d.ts.map +1 -0
  7. package/dist/src/components/outlet.jsx +15 -0
  8. package/dist/src/components/router.d.ts +21 -0
  9. package/dist/src/components/router.d.ts.map +1 -0
  10. package/dist/src/components/router.jsx +138 -0
  11. package/dist/src/contexts/route-context.d.ts +19 -0
  12. package/dist/src/contexts/route-context.d.ts.map +1 -0
  13. package/dist/src/contexts/route-context.js +6 -0
  14. package/dist/src/contexts/router-context.d.ts +55 -0
  15. package/dist/src/contexts/router-context.d.ts.map +1 -0
  16. package/dist/src/contexts/router-context.js +8 -0
  17. package/dist/src/core/_compare-route-paths.d.ts +11 -0
  18. package/dist/src/core/_compare-route-paths.d.ts.map +1 -0
  19. package/dist/src/core/_compare-route-paths.js +92 -0
  20. package/dist/src/core/_match-route-path.d.ts +22 -0
  21. package/dist/src/core/_match-route-path.d.ts.map +1 -0
  22. package/dist/src/core/_match-route-path.js +26 -0
  23. package/dist/src/core/_process-routes.d.ts +14 -0
  24. package/dist/src/core/_process-routes.d.ts.map +1 -0
  25. package/dist/src/core/_process-routes.js +45 -0
  26. package/dist/src/core/_singleton.d.ts +12 -0
  27. package/dist/src/core/_singleton.d.ts.map +1 -0
  28. package/dist/src/core/_singleton.js +18 -0
  29. package/dist/src/core/_unreachable.d.ts +16 -0
  30. package/dist/src/core/_unreachable.d.ts.map +1 -0
  31. package/dist/src/core/_unreachable.js +8 -0
  32. package/dist/src/core/_valibot.d.ts +25 -0
  33. package/dist/src/core/_valibot.d.ts.map +1 -0
  34. package/dist/src/core/_valibot.js +30 -0
  35. package/dist/src/core/errors.d.ts +182 -0
  36. package/dist/src/core/errors.d.ts.map +1 -0
  37. package/dist/src/core/errors.js +166 -0
  38. package/dist/src/core/expect-history-entry.d.ts +59 -0
  39. package/dist/src/core/expect-history-entry.d.ts.map +1 -0
  40. package/dist/src/core/expect-history-entry.js +42 -0
  41. package/dist/src/core/form-data-to-html-form-element.d.ts +11 -0
  42. package/dist/src/core/form-data-to-html-form-element.d.ts.map +1 -0
  43. package/dist/src/core/form-data-to-html-form-element.js +31 -0
  44. package/dist/src/core/history-entry-id-schema.d.ts +17 -0
  45. package/dist/src/core/history-entry-id-schema.d.ts.map +1 -0
  46. package/dist/src/core/history-entry-id-schema.js +9 -0
  47. package/dist/src/core/history-entry-url-schema.d.ts +18 -0
  48. package/dist/src/core/history-entry-url-schema.d.ts.map +1 -0
  49. package/dist/src/core/history-entry-url-schema.js +20 -0
  50. package/dist/src/core/init-loaders.d.ts +28 -0
  51. package/dist/src/core/init-loaders.d.ts.map +1 -0
  52. package/dist/src/core/init-loaders.js +30 -0
  53. package/dist/src/core/match-routes.d.ts +28 -0
  54. package/dist/src/core/match-routes.d.ts.map +1 -0
  55. package/dist/src/core/match-routes.js +31 -0
  56. package/dist/src/core/readonly-form-data.types.d.ts +74 -0
  57. package/dist/src/core/readonly-form-data.types.d.ts.map +1 -0
  58. package/dist/src/core/readonly-url.types.d.ts +164 -0
  59. package/dist/src/core/readonly-url.types.d.ts.map +1 -0
  60. package/dist/src/core/redirect-response.d.ts +37 -0
  61. package/dist/src/core/redirect-response.d.ts.map +1 -0
  62. package/dist/src/core/redirect-response.js +19 -0
  63. package/dist/src/core/route-path.d.ts +57 -0
  64. package/dist/src/core/route-path.d.ts.map +1 -0
  65. package/dist/src/core/route-path.js +93 -0
  66. package/dist/src/core/route-request.d.ts +149 -0
  67. package/dist/src/core/route-request.d.ts.map +1 -0
  68. package/dist/src/core/route-request.js +41 -0
  69. package/dist/src/core/route.types.d.ts +296 -0
  70. package/dist/src/core/route.types.d.ts.map +1 -0
  71. package/dist/src/core/start-action.d.ts +53 -0
  72. package/dist/src/core/start-action.d.ts.map +1 -0
  73. package/dist/src/core/start-action.js +95 -0
  74. package/dist/src/core/start-loaders.d.ts +63 -0
  75. package/dist/src/core/start-loaders.d.ts.map +1 -0
  76. package/dist/src/core/start-loaders.js +138 -0
  77. package/dist/{core.d.ts → src/core.d.ts} +7 -11
  78. package/dist/src/core.d.ts.map +1 -0
  79. package/dist/{core.js → src/core.js} +2 -4
  80. package/dist/src/engines/engine.types.d.ts +216 -0
  81. package/dist/src/engines/engine.types.d.ts.map +1 -0
  82. package/dist/src/engines/navigation-api-engine.d.ts +50 -0
  83. package/dist/src/engines/navigation-api-engine.d.ts.map +1 -0
  84. package/dist/src/engines/navigation-api-engine.js +411 -0
  85. package/dist/src/engines.d.ts +4 -0
  86. package/dist/src/engines.d.ts.map +1 -0
  87. package/dist/src/engines.js +1 -0
  88. package/dist/src/hooks/_use-singleton.d.ts +11 -0
  89. package/dist/src/hooks/_use-singleton.d.ts.map +1 -0
  90. package/dist/src/hooks/_use-singleton.js +26 -0
  91. package/dist/src/hooks/use-action-data.d.ts +17 -0
  92. package/dist/src/hooks/use-action-data.d.ts.map +1 -0
  93. package/dist/src/hooks/use-action-data.js +16 -0
  94. package/dist/src/hooks/use-form-action.d.ts +7 -0
  95. package/dist/src/hooks/use-form-action.d.ts.map +1 -0
  96. package/dist/src/hooks/use-form-action.js +9 -0
  97. package/dist/src/hooks/use-loader-data.d.ts +9 -0
  98. package/dist/src/hooks/use-loader-data.d.ts.map +1 -0
  99. package/dist/src/hooks/use-loader-data.js +20 -0
  100. package/dist/src/hooks/use-navigate.d.ts +53 -0
  101. package/dist/src/hooks/use-navigate.d.ts.map +1 -0
  102. package/dist/src/hooks/use-navigate.js +43 -0
  103. package/dist/{hooks → src/hooks}/use-params.d.ts +2 -2
  104. package/dist/src/hooks/use-params.d.ts.map +1 -0
  105. package/dist/{hooks → src/hooks}/use-params.js +1 -1
  106. package/dist/src/hooks/use-route-context.d.ts +10 -0
  107. package/dist/src/hooks/use-route-context.d.ts.map +1 -0
  108. package/dist/src/hooks/use-route-context.js +17 -0
  109. package/dist/src/hooks/use-router-context.d.ts +12 -0
  110. package/dist/src/hooks/use-router-context.d.ts.map +1 -0
  111. package/dist/src/hooks/use-router-context.js +20 -0
  112. package/dist/src/hooks/use-submit.d.ts +59 -0
  113. package/dist/src/hooks/use-submit.d.ts.map +1 -0
  114. package/dist/src/hooks/use-submit.js +38 -0
  115. package/dist/src/soseki.d.ts +31 -0
  116. package/dist/src/soseki.d.ts.map +1 -0
  117. package/dist/src/soseki.js +12 -0
  118. package/dist/src/utils/redirect.d.ts +11 -0
  119. package/dist/src/utils/redirect.d.ts.map +1 -0
  120. package/dist/src/utils/redirect.js +12 -0
  121. package/package.json +48 -30
  122. package/src/components/browser-router.tsx +8 -11
  123. package/src/components/outlet.tsx +3 -2
  124. package/src/components/router.tsx +139 -145
  125. package/src/contexts/route-context.ts +6 -5
  126. package/src/contexts/router-context.ts +36 -19
  127. package/src/core/_compare-route-paths.ts +48 -34
  128. package/src/core/_match-route-path.ts +21 -15
  129. package/src/core/_process-routes.ts +44 -46
  130. package/src/core/_singleton.ts +13 -38
  131. package/src/core/_unreachable.ts +12 -7
  132. package/src/core/_valibot.ts +19 -116
  133. package/src/core/errors.ts +150 -495
  134. package/src/core/expect-history-entry.ts +40 -41
  135. package/src/core/form-data-to-html-form-element.ts +37 -0
  136. package/src/core/history-entry-id-schema.ts +6 -11
  137. package/src/core/history-entry-url-schema.ts +25 -18
  138. package/src/core/init-loaders.ts +35 -57
  139. package/src/core/match-routes.ts +33 -65
  140. package/src/core/readonly-form-data.types.ts +48 -28
  141. package/src/core/readonly-url.types.ts +57 -28
  142. package/src/core/redirect-response.ts +26 -15
  143. package/src/core/route-path.ts +114 -0
  144. package/src/core/route-request.ts +144 -32
  145. package/src/core/route.types.ts +250 -226
  146. package/src/core/start-action.ts +164 -0
  147. package/src/core/start-loaders.ts +190 -212
  148. package/src/core.ts +8 -15
  149. package/src/engines/engine.types.ts +204 -166
  150. package/src/engines/navigation-api-engine.ts +332 -233
  151. package/src/engines.ts +4 -0
  152. package/src/hooks/_use-singleton.ts +30 -0
  153. package/src/hooks/use-action-data.ts +21 -26
  154. package/src/hooks/use-form-action.ts +4 -5
  155. package/src/hooks/use-loader-data.ts +16 -18
  156. package/src/hooks/use-navigate.ts +69 -28
  157. package/src/hooks/use-params.ts +4 -4
  158. package/src/hooks/use-route-context.ts +20 -0
  159. package/src/hooks/use-router-context.ts +25 -0
  160. package/src/hooks/use-submit.ts +48 -53
  161. package/src/soseki.ts +27 -34
  162. package/src/utils/redirect.ts +5 -5
  163. package/dist/components/action-id.d.ts +0 -19
  164. package/dist/components/action-id.d.ts.map +0 -1
  165. package/dist/components/action-id.jsx +0 -14
  166. package/dist/components/browser-router.d.ts +0 -17
  167. package/dist/components/browser-router.d.ts.map +0 -1
  168. package/dist/components/browser-router.jsx +0 -13
  169. package/dist/components/hidden-input.d.ts +0 -20
  170. package/dist/components/hidden-input.d.ts.map +0 -1
  171. package/dist/components/hidden-input.jsx +0 -8
  172. package/dist/components/outlet.d.ts +0 -8
  173. package/dist/components/outlet.d.ts.map +0 -1
  174. package/dist/components/outlet.jsx +0 -15
  175. package/dist/components/router.d.ts +0 -23
  176. package/dist/components/router.d.ts.map +0 -1
  177. package/dist/components/router.jsx +0 -128
  178. package/dist/contexts/route-context.d.ts +0 -19
  179. package/dist/contexts/route-context.d.ts.map +0 -1
  180. package/dist/contexts/route-context.js +0 -6
  181. package/dist/contexts/router-context.d.ts +0 -46
  182. package/dist/contexts/router-context.d.ts.map +0 -1
  183. package/dist/contexts/router-context.js +0 -8
  184. package/dist/core/_action-id-registry.d.ts +0 -10
  185. package/dist/core/_action-id-registry.d.ts.map +0 -1
  186. package/dist/core/_action-id-registry.js +0 -8
  187. package/dist/core/_capture-stack-trace.d.ts +0 -8
  188. package/dist/core/_capture-stack-trace.d.ts.map +0 -1
  189. package/dist/core/_capture-stack-trace.js +0 -12
  190. package/dist/core/_compare-route-paths.d.ts +0 -11
  191. package/dist/core/_compare-route-paths.d.ts.map +0 -1
  192. package/dist/core/_compare-route-paths.js +0 -80
  193. package/dist/core/_create-html-form-element-form-form-data.d.ts +0 -9
  194. package/dist/core/_create-html-form-element-form-form-data.d.ts.map +0 -1
  195. package/dist/core/_create-html-form-element-form-form-data.js +0 -27
  196. package/dist/core/_encode-pathname.d.ts +0 -10
  197. package/dist/core/_encode-pathname.d.ts.map +0 -1
  198. package/dist/core/_encode-pathname.js +0 -16
  199. package/dist/core/_is-error.d.ts +0 -3
  200. package/dist/core/_is-error.d.ts.map +0 -1
  201. package/dist/core/_is-error.js +0 -13
  202. package/dist/core/_is-promise-like.d.ts +0 -8
  203. package/dist/core/_is-promise-like.d.ts.map +0 -1
  204. package/dist/core/_is-promise-like.js +0 -12
  205. package/dist/core/_match-route-path.d.ts +0 -19
  206. package/dist/core/_match-route-path.d.ts.map +0 -1
  207. package/dist/core/_match-route-path.js +0 -22
  208. package/dist/core/_process-routes.d.ts +0 -9
  209. package/dist/core/_process-routes.d.ts.map +0 -1
  210. package/dist/core/_process-routes.js +0 -47
  211. package/dist/core/_singleton.d.ts +0 -18
  212. package/dist/core/_singleton.d.ts.map +0 -1
  213. package/dist/core/_singleton.js +0 -37
  214. package/dist/core/_unreachable.d.ts +0 -12
  215. package/dist/core/_unreachable.d.ts.map +0 -1
  216. package/dist/core/_unreachable.js +0 -8
  217. package/dist/core/_use-singleton.d.ts +0 -11
  218. package/dist/core/_use-singleton.d.ts.map +0 -1
  219. package/dist/core/_use-singleton.js +0 -21
  220. package/dist/core/_valibot.d.ts +0 -52
  221. package/dist/core/_valibot.d.ts.map +0 -1
  222. package/dist/core/_valibot.js +0 -107
  223. package/dist/core/_weak-id-registry.d.ts +0 -76
  224. package/dist/core/_weak-id-registry.d.ts.map +0 -1
  225. package/dist/core/_weak-id-registry.js +0 -67
  226. package/dist/core/constants.d.ts +0 -5
  227. package/dist/core/constants.d.ts.map +0 -1
  228. package/dist/core/constants.js +0 -4
  229. package/dist/core/data-map.types.d.ts +0 -23
  230. package/dist/core/data-map.types.d.ts.map +0 -1
  231. package/dist/core/data-map.types.js +0 -1
  232. package/dist/core/data-store.types.d.ts +0 -22
  233. package/dist/core/data-store.types.d.ts.map +0 -1
  234. package/dist/core/data-store.types.js +0 -1
  235. package/dist/core/deferred-promise.d.ts +0 -203
  236. package/dist/core/deferred-promise.d.ts.map +0 -1
  237. package/dist/core/deferred-promise.js +0 -200
  238. package/dist/core/errors.d.ts +0 -303
  239. package/dist/core/errors.d.ts.map +0 -1
  240. package/dist/core/errors.js +0 -400
  241. package/dist/core/expect-history-entry.d.ts +0 -52
  242. package/dist/core/expect-history-entry.d.ts.map +0 -1
  243. package/dist/core/expect-history-entry.js +0 -38
  244. package/dist/core/history-entry-id-schema.d.ts +0 -17
  245. package/dist/core/history-entry-id-schema.d.ts.map +0 -1
  246. package/dist/core/history-entry-id-schema.js +0 -9
  247. package/dist/core/history-entry-url-schema.d.ts +0 -20
  248. package/dist/core/history-entry-url-schema.d.ts.map +0 -1
  249. package/dist/core/history-entry-url-schema.js +0 -16
  250. package/dist/core/init-loaders.d.ts +0 -37
  251. package/dist/core/init-loaders.d.ts.map +0 -1
  252. package/dist/core/init-loaders.js +0 -38
  253. package/dist/core/match-routes.d.ts +0 -31
  254. package/dist/core/match-routes.d.ts.map +0 -1
  255. package/dist/core/match-routes.js +0 -54
  256. package/dist/core/readonly-form-data.types.d.ts +0 -32
  257. package/dist/core/readonly-form-data.types.d.ts.map +0 -1
  258. package/dist/core/readonly-url.types.d.ts +0 -135
  259. package/dist/core/readonly-url.types.d.ts.map +0 -1
  260. package/dist/core/redirect-response.d.ts +0 -29
  261. package/dist/core/redirect-response.d.ts.map +0 -1
  262. package/dist/core/redirect-response.js +0 -17
  263. package/dist/core/route-request.d.ts +0 -52
  264. package/dist/core/route-request.d.ts.map +0 -1
  265. package/dist/core/route-request.js +0 -26
  266. package/dist/core/route.types.d.ts +0 -309
  267. package/dist/core/route.types.d.ts.map +0 -1
  268. package/dist/core/start-actions.d.ts +0 -60
  269. package/dist/core/start-actions.d.ts.map +0 -1
  270. package/dist/core/start-actions.js +0 -186
  271. package/dist/core/start-loaders.d.ts +0 -69
  272. package/dist/core/start-loaders.d.ts.map +0 -1
  273. package/dist/core/start-loaders.js +0 -154
  274. package/dist/core.d.ts.map +0 -1
  275. package/dist/engines/engine.types.d.ts +0 -190
  276. package/dist/engines/engine.types.d.ts.map +0 -1
  277. package/dist/engines/navigation-api-engine.d.ts +0 -48
  278. package/dist/engines/navigation-api-engine.d.ts.map +0 -1
  279. package/dist/engines/navigation-api-engine.js +0 -332
  280. package/dist/hooks/_use-route-context.d.ts +0 -10
  281. package/dist/hooks/_use-route-context.d.ts.map +0 -1
  282. package/dist/hooks/_use-route-context.js +0 -17
  283. package/dist/hooks/_use-router-context.d.ts +0 -10
  284. package/dist/hooks/_use-router-context.d.ts.map +0 -1
  285. package/dist/hooks/_use-router-context.js +0 -18
  286. package/dist/hooks/use-action-data.d.ts +0 -23
  287. package/dist/hooks/use-action-data.d.ts.map +0 -1
  288. package/dist/hooks/use-action-data.js +0 -16
  289. package/dist/hooks/use-form-action.d.ts +0 -7
  290. package/dist/hooks/use-form-action.d.ts.map +0 -1
  291. package/dist/hooks/use-form-action.js +0 -10
  292. package/dist/hooks/use-loader-data.d.ts +0 -11
  293. package/dist/hooks/use-loader-data.d.ts.map +0 -1
  294. package/dist/hooks/use-loader-data.js +0 -19
  295. package/dist/hooks/use-navigate.d.ts +0 -39
  296. package/dist/hooks/use-navigate.d.ts.map +0 -1
  297. package/dist/hooks/use-navigate.js +0 -26
  298. package/dist/hooks/use-params.d.ts.map +0 -1
  299. package/dist/hooks/use-pathname.d.ts +0 -7
  300. package/dist/hooks/use-pathname.d.ts.map +0 -1
  301. package/dist/hooks/use-pathname.js +0 -9
  302. package/dist/hooks/use-submit.d.ts +0 -66
  303. package/dist/hooks/use-submit.d.ts.map +0 -1
  304. package/dist/hooks/use-submit.js +0 -35
  305. package/dist/soseki.d.ts +0 -42
  306. package/dist/soseki.d.ts.map +0 -1
  307. package/dist/soseki.js +0 -19
  308. package/dist/utils/get-action-id.d.ts +0 -8
  309. package/dist/utils/get-action-id.d.ts.map +0 -1
  310. package/dist/utils/get-action-id.js +0 -11
  311. package/dist/utils/href.d.ts +0 -11
  312. package/dist/utils/href.d.ts.map +0 -1
  313. package/dist/utils/href.js +0 -12
  314. package/dist/utils/redirect.d.ts +0 -11
  315. package/dist/utils/redirect.d.ts.map +0 -1
  316. package/dist/utils/redirect.js +0 -12
  317. package/dist/utils/route-index.d.ts +0 -41
  318. package/dist/utils/route-index.d.ts.map +0 -1
  319. package/dist/utils/route-index.js +0 -12
  320. package/dist/utils/route-route.d.ts +0 -62
  321. package/dist/utils/route-route.d.ts.map +0 -1
  322. package/dist/utils/route-route.js +0 -25
  323. package/dist/utils/set-action-id.d.ts +0 -9
  324. package/dist/utils/set-action-id.d.ts.map +0 -1
  325. package/dist/utils/set-action-id.js +0 -12
  326. package/src/components/action-id.tsx +0 -35
  327. package/src/components/hidden-input.tsx +0 -39
  328. package/src/core/_action-id-registry.ts +0 -11
  329. package/src/core/_capture-stack-trace.ts +0 -12
  330. package/src/core/_create-html-form-element-form-form-data.ts +0 -32
  331. package/src/core/_encode-pathname.ts +0 -17
  332. package/src/core/_is-error.ts +0 -16
  333. package/src/core/_is-promise-like.ts +0 -14
  334. package/src/core/_use-singleton.ts +0 -24
  335. package/src/core/_weak-id-registry.ts +0 -125
  336. package/src/core/constants.ts +0 -4
  337. package/src/core/data-map.types.ts +0 -28
  338. package/src/core/data-store.types.ts +0 -25
  339. package/src/core/deferred-promise.ts +0 -408
  340. package/src/core/start-actions.ts +0 -274
  341. package/src/hooks/_use-route-context.ts +0 -19
  342. package/src/hooks/_use-router-context.ts +0 -25
  343. package/src/hooks/use-pathname.ts +0 -10
  344. package/src/utils/get-action-id.ts +0 -12
  345. package/src/utils/href.ts +0 -17
  346. package/src/utils/route-index.ts +0 -70
  347. package/src/utils/route-route.ts +0 -111
  348. package/src/utils/set-action-id.ts +0 -14
  349. /package/dist/{core → src/core}/readonly-form-data.types.js +0 -0
  350. /package/dist/{core → src/core}/readonly-url.types.js +0 -0
  351. /package/dist/{core → src/core}/route.types.js +0 -0
  352. /package/dist/{engines → src/engines}/engine.types.js +0 -0
@@ -1,96 +1,92 @@
1
- import actionIdRegistry from "../core/_action-id-registry.js";
2
- import createHtmlFormElementFormFormData from "../core/_create-html-form-element-form-form-data.js";
1
+ import unreachable from "../core/_unreachable.js";
3
2
  import * as v from "../core/_valibot.js";
4
- import { ACTION_ID_FORM_DATA_NAME } from "../core/constants.js";
5
3
  import { NavigationApiNotSupportedError } from "../core/errors.js";
6
4
  import expectHistoryEntry from "../core/expect-history-entry.js";
7
- import HistoryEntryIdSchema, { type HistoryEntryId } from "../core/history-entry-id-schema.js";
5
+ import createHtmlFormElementFormFormData from "../core/form-data-to-html-form-element.js";
6
+ import type { HistoryEntryId } from "../core/history-entry-id-schema.js";
7
+ import HistoryEntryIdSchema from "../core/history-entry-id-schema.js";
8
8
  import HistoryEntryUrlSchema from "../core/history-entry-url-schema.js";
9
9
  import initLoaders from "../core/init-loaders.js";
10
10
  import matchRoutes from "../core/match-routes.js";
11
- import type { IAction } from "../core/route.types.js";
12
- import startActions from "../core/start-actions.js";
11
+ import RoutePath from "../core/route-path.js";
12
+ import startAction from "../core/start-action.js";
13
13
  import startLoaders from "../core/start-loaders.js";
14
- import type {
15
- IEngine,
16
- InitEngineArgs,
17
- NavigateArgs,
18
- RouterState,
19
- StartEngineArgs,
20
- SubmitArgs,
21
- } from "./engine.types.js";
14
+ import type { IEngine } from "./engine.types.js";
22
15
 
23
16
  /**
24
- * Navigation API を利用してルーティングの基幹処理を行うエンジンクラスです。
17
+ * モダンブラウザーに搭載されている標準の `Navigation API` を活用し、クライアントサイドにおける高度な SPA ルーティングを制御するエンジンクラスです。
25
18
  */
26
19
  export default class NavigationApiEngine implements IEngine {
27
20
  /**
28
- * Navigation API の実体です。
21
+ * ブラウザー標準の `navigation` オブジェクトへの参照を保持します。
29
22
  */
30
- private nav: Navigation;
23
+ private navigation: Navigation;
31
24
 
32
25
  /**
33
- * すでにイベントリスナーを登録済みのエントリー ID を管理するセットです。
26
+ * メモリーリークを防ぐため、すでに dispose リスナーを登録済みの履歴エントリー ID を追跡するセットです。
34
27
  */
35
28
  private subscribedEntryIds: Set<HistoryEntryId>;
36
29
 
37
30
  /**
38
- * 実行中のナビゲーションをキャンセルするためのコントローラーです。
31
+ * 連続して発生した画面遷移を適切にキャンセルするための、最新ナビゲーション用の中断コントローラーです。
39
32
  */
40
33
  private navAbortController: AbortController | null;
41
34
 
42
35
  /**
43
- * NavigationApiEngine クラスの新しいインスタンスを初期化します。
36
+ * `NavigationApiEngine` クラスのインスタンスを初期化します。
37
+ *
38
+ * 実行環境が `Navigation API` をサポートしていない場合はエラーを投げます。
44
39
  */
45
40
  public constructor() {
46
- let nav: Navigation | undefined;
47
- try {
48
- nav = navigation;
49
- } catch {
41
+ let navigation_: Navigation | undefined;
42
+
43
+ // グローバル環境または window オブジェクトから navigation インスタンスの回収を試みます。
44
+ for (const getNav of [() => navigation, () => window.navigation]) {
50
45
  try {
51
- nav = window.navigation;
46
+ navigation_ = getNav();
47
+ if (typeof navigation_ !== "undefined") {
48
+ break;
49
+ }
52
50
  } catch {}
53
51
  }
54
- // オブジェクトの存在確認を行い、非対応環境なら例外を投げます。
55
- if (!(nav && typeof nav === "object")) {
52
+
53
+ // オブジェクトとして存在しない場合はサポート外の環境とみなします。
54
+ if (typeof navigation_ !== "object") {
56
55
  throw new NavigationApiNotSupportedError();
57
56
  }
58
57
 
59
- this.nav = nav;
58
+ this.navigation = navigation_;
60
59
  this.subscribedEntryIds = new Set();
61
60
  this.navAbortController = null;
62
61
  }
63
62
 
64
63
  /**
65
- * 現在のロケーションに基づいた初期のルーター状態を生成します。
64
+ * 現在のページ URL に基づき、ルーターの初期状態を構築・登録します。
66
65
  *
67
- * @param args 状態生成に必要なルート定義やデータ保持用のマップです。
68
- * @returns 生成されたルーター状態、またはマッチしなかった場合は `null` を返します。
66
+ * @param args ルート定義配列、共通データストア、初期化用のアボートシグナルを含むオブジェクトです。
67
+ * @returns 構築された初期の `RouterState`。適合するルートがないか、履歴が無い場合は `null` を返します。
69
68
  */
70
- public init(args: InitEngineArgs): RouterState | null {
71
- const currentEntry = expectHistoryEntry(this.nav.currentEntry);
69
+ init(args: IEngine.InitArgs): IEngine.InitReturn {
70
+ const currentEntry = expectHistoryEntry(this.navigation.currentEntry);
72
71
  if (!currentEntry) {
73
72
  return null;
74
73
  }
75
74
 
76
- const {
77
- routes,
78
- getSignal,
79
- loaderDataStore,
80
- } = args;
81
- const currentRoutes = matchRoutes(routes, currentEntry.url.pathname);
75
+ const { routes, getSignal, loaderDataStore } = args;
76
+ const currentRoutes = matchRoutes(routes, currentEntry.url);
82
77
  if (!currentRoutes) {
83
78
  return null;
84
79
  }
85
80
 
86
- // 非同期でローダーの初期化を開始します。
87
- initLoaders({
88
- entry: currentEntry,
89
- routes: currentRoutes,
81
+ // 初期表示に必要なすべてのローダーを一斉に並行起動します。
82
+ const dataMap = initLoaders(currentRoutes, {
83
+ url: currentEntry.url,
90
84
  signal: getSignal(),
91
- dataStore: loaderDataStore,
92
85
  });
93
86
 
87
+ // 起動したローダーの結果(NinjaPromise)のマップを、現在の履歴 ID をキーとしてキャッシュします。
88
+ loaderDataStore.set(currentEntry.id, dataMap);
89
+
94
90
  return {
95
91
  entry: currentEntry,
96
92
  routes: currentRoutes,
@@ -98,169 +94,244 @@ export default class NavigationApiEngine implements IEngine {
98
94
  }
99
95
 
100
96
  /**
101
- * エンジンの動作を開始し、ナビゲーションイベントの監視を行います。
97
+ * ブラウザーの継続的なナビゲーションイベント(リンククリック、フォーム送信、履歴移動)の監視を開始します。
102
98
  *
103
- * @param args エンジンの開始に必要な設定とデータ管理用のオブジェクトです。
99
+ * @param args ルート配列、UI側への状態反映関数、各種ストア、シグナル取得関数を含むオブジェクトです。
104
100
  */
105
- public start(args: StartEngineArgs): void {
106
- const {
107
- routes,
108
- update,
109
- getSignal,
110
- actionDataStore,
111
- loaderDataStore,
112
- } = args;
101
+ start(args: IEngine.StartArgs): IEngine.StartReturn {
102
+ const { routes, update, getSignal, actionDataStore, loaderDataStore } = args;
113
103
 
114
104
  /**
115
- * ナビゲーションが発生した際のハンドラーです。
116
- *
117
- * @param event ナビゲーションイベントです。
105
+ * ユーザーのアクションによって発生したすべての遷移要求をインターセプトして処理する、ルーティングの中枢ハンドラーです。
118
106
  */
119
107
  const handleNavigate = (event: NavigateEvent): void => {
120
- // インターセプトできない場合や、ハッシュ変更、ダウンロードリクエストの場合は処理をスキップします。
108
+ // 処理すべきでない通常のブラウザー固有のナビゲーション(ハッシュ変更、ファイルのダウンロードなど)は、
109
+ // 標準の挙動を妨げないようにインターセプトせず即座にスルーします。
121
110
  // 参照: https://developer.mozilla.org/docs/Web/API/Navigation_API#handling_a_navigation_using_intercept
122
111
  if (
123
- !event.isTrusted
124
- || !event.canIntercept
125
- || event.hashChange
126
- || event.downloadRequest !== null
112
+ !event.isTrusted ||
113
+ !event.canIntercept ||
114
+ event.hashChange ||
115
+ event.downloadRequest !== null
127
116
  ) {
128
117
  return;
129
118
  }
130
119
 
131
- const currentEntry = expectHistoryEntry(this.nav.currentEntry);
120
+ const currentEntry = expectHistoryEntry(this.navigation.currentEntry);
132
121
  if (!currentEntry) {
122
+ // 現在のエントリーが存在しない場合は、ルーターを未マッチ状態(null)にリセットして制御をブラウザーに返します。
133
123
  event.intercept({
134
124
  async handler() {
135
125
  update(null);
136
126
  },
137
127
  });
128
+
138
129
  return;
139
130
  }
140
131
 
132
+ // 移動先の URL を検証し、適合するルート定義があるかを探索します。
141
133
  const destUrl = v.expect(HistoryEntryUrlSchema(), event.destination.url);
142
- const destRoutes = matchRoutes(routes, destUrl.pathname);
134
+ const destRoutes = matchRoutes(routes, destUrl);
143
135
  if (!destRoutes) {
136
+ // 移動先のルート定義が見つからない場合は、ルーターを未マッチ状態(null)にリセットして制御をブラウザーに返します。
144
137
  event.intercept({
145
138
  async handler() {
146
139
  update(null);
147
140
  },
148
141
  });
142
+
149
143
  return;
150
144
  }
151
145
 
152
- // 進行中の古い処理をキャンセルし、新しいコントローラーを生成します。
146
+ // 前回の遷移から短時間で遷移する場合を考慮し、進行中だった以前の古い非同期処理をすべて安全に中断します。
153
147
  this.navAbortController?.abort();
154
- const { signal } = this.navAbortController = new AbortController();
148
+ this.navAbortController = new AbortController();
149
+ const { signal } = this.navAbortController;
155
150
  const { formData } = event;
156
151
  const prevEntryInHandler = currentEntry;
152
+
153
+ // 分岐 A: フォームデータが伴う場合 = データ変更要求(HTTP POST / Action 契機)
157
154
  if (formData) {
158
155
  const { sourceElement } = event;
156
+
157
+ // submit メソッドによってプログラムから動的生成されたフォーム要素であれば、用済みのため DOM から削除します。
159
158
  if (sourceElement?.hasAttribute("data-sosekisubmit")) {
160
159
  document.body.removeChild(sourceElement);
161
160
  }
162
161
 
163
- let redirectUrl = new URL(currentEntry.url.href);
164
- let actionResultMap: ReadonlyMap<IAction, unknown> | undefined;
165
- event.intercept({
166
- precommitHandler: async controller => {
167
- const entry = {
168
- id: currentEntry.id,
169
- url: destUrl,
170
- };
171
- const waitForComplete = startActions({
172
- entry,
173
- routes: destRoutes,
174
- signal,
175
- formData,
176
- dataStore: actionDataStore,
177
- });
178
- if (!waitForComplete) {
179
- return;
162
+ /**
163
+ * A-1: URL が書き換わる直前の段階で割り込んでアクション関数を実行するハンドラーです。
164
+ */
165
+ const precommitHandler = async (controller: NavigationPrecommitController) => {
166
+ const action = startAction(destRoutes, {
167
+ url: destUrl,
168
+ signal,
169
+ formData,
170
+ });
171
+ if (!action) {
172
+ return;
173
+ }
174
+
175
+ // アクションの実行状態を現在の履歴 ID に紐づけてストアへ保存します。
176
+ actionDataStore
177
+ .getOrInsertComputed(currentEntry.id, () => new Map())
178
+ .set(action.func, action.data);
179
+
180
+ // アクションの開始に伴い、UI 層へローディング状態などの再描画を伝播します。
181
+ update();
182
+
183
+ // 全アクションの処理が完了するまで待機します。
184
+ const actionResponse = await action.idle();
185
+ const redirectUrl = new URL(currentEntry.url.href);
186
+
187
+ switch (action.data.status) {
188
+ case "rejected": {
189
+ // アクションがエラーで失敗した場合は、URL を変更せず現在の元のページに強制リダイレクトさせます。
190
+ const { pathname, search, hash } = currentEntry.url;
191
+ controller.redirect(pathname + search + hash);
192
+
193
+ break;
180
194
  }
181
195
 
182
- update();
183
- const {
184
- redirect = currentEntry.url.pathname,
185
- resultMap,
186
- } = await waitForComplete();
187
- actionResultMap = resultMap;
188
- redirectUrl.pathname = redirect;
189
- controller.redirect(redirect);
190
- },
191
- handler: async () => {
192
- if (!actionResultMap) {
193
- return;
194
- }
196
+ case "fulfilled": {
197
+ // アクションが正常終了した場合、返り値にリダイレクト指示が含まれていればその目的地へ遷移させます。
198
+ // リダイレクトがなければそのまま本来の目的地へとブラウザーのコミット先を書き換えます。
199
+ const { redirectTo = currentEntry.url } = actionResponse;
200
+ const { pathname, search, hash } = redirectTo;
201
+ controller.redirect(pathname + search + hash);
195
202
 
196
- const currentEntry = expectHistoryEntry(this.nav.currentEntry);
197
- if (!currentEntry) {
198
- update(null);
199
- return;
200
- }
201
- if (currentEntry.url.href !== redirectUrl.href) {
202
- return;
203
- }
203
+ redirectUrl.pathname = pathname;
204
+ redirectUrl.search = search;
205
+ redirectUrl.hash = hash;
204
206
 
205
- const currentRoutes = matchRoutes(routes, currentEntry.url.pathname);
206
- if (!currentRoutes) {
207
- update(null);
208
- return;
207
+ break;
209
208
  }
210
209
 
211
- const prevEntry = prevEntryInHandler;
212
- const prevRoutes = matchRoutes(routes, prevEntry.url.pathname);
213
- const waitForComplete = startLoaders({
210
+ default:
211
+ unreachable(action.data.status as never);
212
+ }
213
+
214
+ // 次の描画確定フェーズへコンテキスト情報を引き継ぎます。
215
+ return {
216
+ action: action.func,
217
+ actionData: action.data,
218
+ redirectUrl,
219
+ };
220
+ };
221
+
222
+ /**
223
+ * A-2: アクションが完了し、ブラウザーの URL コミットが確定した後に画面表示を同期させるハンドラーです。
224
+ */
225
+ const handler = async (args: Awaited<ReturnType<typeof precommitHandler>>) => {
226
+ if (!args) {
227
+ return;
228
+ }
229
+
230
+ const { action, actionData, redirectUrl } = args;
231
+
232
+ // 履歴エントリーを再度取得します。
233
+ const currentEntry = expectHistoryEntry(this.navigation.currentEntry);
234
+ if (!currentEntry) {
235
+ // 現在のエントリーが存在しない場合は、ルーターを未マッチ状態(null)にリセットして制御をブラウザーに返します。
236
+ update(null);
237
+ return;
238
+ }
239
+
240
+ // コミットされた実際のブラウザー URL が、想定しているリダイレクト先と一致しない場合は処理を中断します。
241
+ if (currentEntry.url.href !== redirectUrl.href) {
242
+ return;
243
+ }
244
+
245
+ // 確定した新しい履歴 ID に改めてアクション結果をキャッシュします。
246
+ actionDataStore
247
+ .getOrInsertComputed(currentEntry.id, () => new Map())
248
+ .set(action, actionData);
249
+
250
+ const currentRoutes = matchRoutes(routes, currentEntry.url);
251
+ if (!currentRoutes) {
252
+ // 現在のルート定義が見つからない場合は、ルーターを未マッチ状態(null)にリセットして制御をブラウザーに返します。
253
+ update(null);
254
+ return;
255
+ }
256
+
257
+ // アクションの処理結果を反映させるために、該当するルートのローダーを実行します。
258
+ const prevEntry = prevEntryInHandler;
259
+ const prevRoutes = matchRoutes(routes, prevEntry.url);
260
+ const startedLoaders = startLoaders(
261
+ {
214
262
  signal,
215
- formData,
216
263
  prevEntry,
217
264
  prevRoutes,
218
265
  currentEntry,
219
266
  currentRoutes,
220
- actionResultMap,
221
- actionDataStore,
222
267
  loaderDataStore,
223
- });
224
- update({
225
- entry: currentEntry,
226
- routes: currentRoutes,
227
- });
228
- if (!waitForComplete) {
229
- return;
230
- }
231
-
232
- await waitForComplete();
268
+ },
269
+ {
270
+ formData,
271
+ actionData,
272
+ },
273
+ );
274
+
275
+ // 最新の確定状態を UI に通知して画面を再描画します。ローダーの結果の中には実行中のものもありますが、それらの待機処理(描画)は各コンポーネントに任せます。
276
+ update({
277
+ entry: currentEntry,
278
+ routes: currentRoutes,
279
+ });
280
+
281
+ // 全ローダーの完了を待機します。ここで待機することで、全ローダーの実行が完了するまでブラウザーのタブにはローディングスピーナーが表示されます。
282
+ await startedLoaders?.idle();
283
+ };
284
+
285
+ // Navigation API のインターセプト機構に、二段階の処理をバインドします。
286
+ let precommitResult: Awaited<ReturnType<typeof precommitHandler>>;
287
+ event.intercept({
288
+ async precommitHandler(controller) {
289
+ precommitResult = await precommitHandler(controller);
290
+ },
291
+ async handler() {
292
+ await handler(precommitResult);
233
293
  },
234
294
  });
235
295
  } else {
236
- event.intercept({
237
- handler: async () => {
238
- const currentEntry = expectHistoryEntry(this.nav.currentEntry);
239
- if (!currentEntry) {
240
- update(null);
241
- return;
242
- }
243
- if (currentEntry.url.href !== destUrl.href) {
244
- return;
245
- }
296
+ // 分岐 B: フォームデータがない場合 = 通常の画面遷移(HTTP GET / リンククリック・戻る進む契機)
297
+ const handler = async () => {
298
+ const currentEntry = expectHistoryEntry(this.navigation.currentEntry);
299
+ if (!currentEntry) {
300
+ update(null);
301
+ return;
302
+ }
303
+ // 同期がズレている場合はガードします。
304
+ if (currentEntry.url.href !== destUrl.href) {
305
+ return;
306
+ }
307
+
308
+ const prevEntry = prevEntryInHandler;
309
+ const prevRoutes = matchRoutes(routes, prevEntry.url);
310
+ const currentRoutes = destRoutes;
311
+
312
+ // キャッシュの再利用判定を含めて、移動先のローダー関数群を精査・起動します。
313
+ const startedLoaders = startLoaders({
314
+ signal,
315
+ prevEntry,
316
+ prevRoutes,
317
+ currentEntry,
318
+ currentRoutes,
319
+ loaderDataStore,
320
+ });
321
+
322
+ // 最新の確定状態を UI に通知して画面を再描画します。ローダーの結果の中には実行中のものもありますが、それらの待機処理(描画)は各コンポーネントに任せます。
323
+ update({
324
+ entry: currentEntry,
325
+ routes: currentRoutes,
326
+ });
327
+
328
+ // 全ローダーの完了を待機します。ここで待機することで、全ローダーの実行が完了するまでブラウザーのタブにはローディングスピーナーが表示されます。
329
+ await startedLoaders?.idle();
330
+ };
246
331
 
247
- const prevEntry = prevEntryInHandler;
248
- const prevRoutes = matchRoutes(routes, prevEntry.url.pathname);
249
- const currentRoutes = destRoutes;
250
- const waitForComplete = startLoaders({
251
- signal,
252
- prevEntry,
253
- prevRoutes,
254
- currentEntry,
255
- currentRoutes,
256
- actionDataStore,
257
- loaderDataStore,
258
- });
259
- update({
260
- entry: currentEntry,
261
- routes: currentRoutes,
262
- });
263
- await waitForComplete?.();
332
+ event.intercept({
333
+ async handler() {
334
+ await handler();
264
335
  },
265
336
  });
266
337
  }
@@ -268,23 +339,24 @@ export default class NavigationApiEngine implements IEngine {
268
339
 
269
340
  const signal = getSignal();
270
341
 
271
- const handleAbort = () => {
342
+ // 外部からルーター全体の監視終了シグナルを受け取った際、中断処理を連動させます。
343
+ const handleAbort = (): void => {
272
344
  this.navAbortController?.abort();
273
345
  this.navAbortController = null;
274
346
  };
275
347
  signal.addEventListener("abort", handleAbort, { once: true });
276
348
 
277
- this.nav.addEventListener("navigate", handleNavigate, { signal });
278
- // this.nav.addEventListener("navigateerror", console.error, { signal });
349
+ // Navigation API navigate イベントの購読を開始します。
350
+ this.navigation.addEventListener("navigate", handleNavigate, { signal });
279
351
 
280
- // 既存のエントリーに対して dispose リスナーを登録し、メモリーを解放します。
281
- for (const entry of this.nav.entries()) {
352
+ // セッション履歴から溢れて破棄された古い履歴エントリーのデータ(アクション・ローダーのキャッシュ)を自動削除します。
353
+ for (const entry of this.navigation.entries()) {
282
354
  const entryId = v.expect(HistoryEntryIdSchema(), entry.id);
283
355
  if (this.subscribedEntryIds.has(entryId)) {
284
356
  continue;
285
357
  }
286
358
 
287
- const handleDispose = () => {
359
+ const handleDispose = (): void => {
288
360
  this.subscribedEntryIds.delete(entryId);
289
361
  actionDataStore.delete(entryId);
290
362
  loaderDataStore.delete(entryId);
@@ -293,11 +365,9 @@ export default class NavigationApiEngine implements IEngine {
293
365
  this.subscribedEntryIds.add(entryId);
294
366
  }
295
367
 
296
- /**
297
- * 現在のエントリーが変更された際のハンドラーです。
298
- */
299
- const handleCurrentEntryChange = () => {
300
- const currentEntry = expectHistoryEntry(this.nav.currentEntry);
368
+ // ナビゲーションの進行に伴い、新しく生成される履歴エントリーに対しても、動的に破棄イベントの監視網を広げます。
369
+ const handleCurrentEntryChange = (): void => {
370
+ const currentEntry = expectHistoryEntry(this.navigation.currentEntry);
301
371
  if (!currentEntry) {
302
372
  return;
303
373
  }
@@ -305,102 +375,131 @@ export default class NavigationApiEngine implements IEngine {
305
375
  return;
306
376
  }
307
377
 
308
- const handleDispose = () => {
378
+ const handleDispose = (): void => {
309
379
  this.subscribedEntryIds.delete(currentEntry.id);
310
380
  actionDataStore.delete(currentEntry.id);
311
381
  loaderDataStore.delete(currentEntry.id);
312
382
  };
313
- this.nav.currentEntry!.addEventListener("dispose", handleDispose, { signal });
383
+ this.navigation.currentEntry!.addEventListener("dispose", handleDispose, { signal });
314
384
  this.subscribedEntryIds.add(currentEntry.id);
315
385
  };
316
- this.nav.addEventListener("currententrychange", handleCurrentEntryChange, { signal });
386
+ this.navigation.addEventListener("currententrychange", handleCurrentEntryChange, { signal });
317
387
  }
318
388
 
319
389
  /**
320
- * フォームデータやクエリーパラメーターを送信します。
390
+ * フォームデータまたはクエリーパラメータを、ブラウザーの Navigation API のライフサイクルに載せて命令的に送信します。
321
391
  *
322
- * @param args 送信内容と送信先を含む引数です。
392
+ * @param args 送信データの種類に応じたサブミット引数です。
323
393
  */
324
- public submit(args: SubmitArgs): void {
325
- if ("actionId" in args) {
326
- const {
327
- target,
328
- action,
329
- actionId: actionFunction,
330
- } = args;
331
- const form = createHtmlFormElementFormFormData(target);
332
- const actionId = actionFunction && actionIdRegistry.set(actionFunction);
333
- if (actionId !== undefined) {
334
- const input = document.createElement("input");
335
- input.type = "hidden";
336
- input.name = ACTION_ID_FORM_DATA_NAME;
337
- input.value = actionId;
394
+ submit(args: IEngine.SubmitArgs): void {
395
+ switch (args.type) {
396
+ case "FORM_DATA": {
397
+ const { action, target } = args;
398
+ const form = createHtmlFormElementFormFormData(target);
399
+ form.method = "POST";
400
+ form.action = action;
401
+ form.enctype = "multipart/form-data";
402
+ form.dataset["sosekisubmit"] = ""; // 後で削除できるように目印を付与します。
403
+
404
+ // 実際のフォーム送信を行うために DOM へ一時配置して実行します。
405
+ // ハンドラーがこの要素を使用した後に DOM から削除します。
406
+ document.body.appendChild(form);
407
+ form.submit();
408
+
409
+ break;
338
410
  }
339
411
 
340
- form.method = "POST";
341
- form.action = action;
342
- form.enctype = "multipart/form-data";
343
- form.dataset["sosekisubmit"] = "";
344
- document.body.appendChild(form);
345
- form.submit();
346
- } else {
347
- const {
348
- target,
349
- action,
350
- history,
351
- } = args;
352
- const u = new URL("x://y" + action);
353
- u.search = target.toString();
354
- this.navigate({
355
- to: u.href.slice("x://y".length),
356
- history,
357
- });
412
+ case "URL_SEARCH_PARAMS": {
413
+ const { action, target, history } = args;
414
+ const path = new RoutePath(action);
415
+ path.search = target.toString();
416
+
417
+ // Navigation API を用いて、クエリーが上書きされた新しいアドレスへ遷移させます。
418
+ this.navigation.navigate(path.toString(), { history });
419
+
420
+ break;
421
+ }
422
+
423
+ default:
424
+ unreachable(args);
358
425
  }
359
426
  }
360
427
 
361
428
  /**
362
- * 指定されたパスへナビゲートします。
429
+ * 命令的なページ遷移を処理します。
363
430
  *
364
- * @param args 遷移先と遷移オプションを含む引数です。
431
+ * @param args 遷移タイプに応じたナビゲーション引数です。
365
432
  */
366
- public navigate(args: NavigateArgs): void {
367
- if ("delta" in args) {
368
- const currentEntry = expectHistoryEntry(this.nav.currentEntry);
369
- if (!currentEntry) {
370
- return;
371
- }
433
+ navigate(args: IEngine.NavigateArgs): void {
434
+ switch (args.type) {
435
+ case "LINK": {
436
+ const { to, history } = args;
437
+ switch (to.type) {
438
+ case "PATH": {
439
+ // 完全なパス文字列の余分なスラッシュなどをエンコードして直接遷移します。
440
+
441
+ const path = RoutePath.encode(to.path);
442
+ this.navigation.navigate(path, { history });
443
+
444
+ break;
445
+ }
446
+
447
+ case "PARTIAL": {
448
+ // 現在のロケーション情報をベースに、指定されたパーツ(パス名、クエリー、ハッシュのみなど)を部分的にパッチ(上書き)したマージ URL を算出します。
449
+
450
+ const currentPath = new RoutePath(window.location);
451
+ const nextPath = new RoutePath(window.location);
452
+ if (typeof to.pathname === "string") {
453
+ nextPath.pathname = to.pathname;
454
+ }
455
+ if (typeof to.search === "string") {
456
+ nextPath.search = to.search;
457
+ }
458
+ if (typeof to.hash === "string") {
459
+ nextPath.hash = to.hash;
460
+ }
372
461
 
373
- const { delta } = args;
374
- const index = currentEntry.index + delta;
375
- const entry = this.nav.entries().find(e => e.index === index);
376
- if (!entry) {
377
- return;
378
- }
462
+ const nextPathString = nextPath.toString();
379
463
 
380
- this.nav.traverseTo(entry.key);
381
- } else {
382
- const {
383
- to,
384
- history,
385
- } = args;
386
- if (typeof to === "string") {
387
- this.nav.navigate(to, { history });
388
- } else {
389
- const { href } = location;
390
- const u = new URL(href);
391
- if (to.pathname !== undefined) {
392
- u.pathname = to.pathname;
393
- }
394
- if (to.search !== undefined) {
395
- u.search = to.search;
464
+ // 無駄な遷移履歴を作らないように、URL に実際の変化がある場合のみ navigate を実行します。
465
+ if (nextPathString !== currentPath.toString()) {
466
+ this.navigation.navigate(nextPathString, { history });
467
+ }
468
+
469
+ break;
470
+ }
471
+
472
+ default:
473
+ unreachable(to);
396
474
  }
397
- if (to.hash !== undefined) {
398
- u.hash = to.hash;
475
+
476
+ break;
477
+ }
478
+
479
+ case "MOVE": {
480
+ const currentEntry = expectHistoryEntry(this.navigation.currentEntry);
481
+ if (!currentEntry) {
482
+ return;
399
483
  }
400
- if (u.href !== href) {
401
- this.nav.navigate(u.href, { history });
484
+
485
+ const { delta } = args;
486
+
487
+ // 現在のインデックスから相対位置(例: -1 なら 1 つ戻る)を計算し、履歴スタックに該当するインデックスが存在するか探索します。
488
+ const index = currentEntry.index + delta;
489
+ const entry = this.navigation.entries().find((e) => e.index === index);
490
+ if (!entry) {
491
+ // スタックの限界を超える移動要求の場合は何もしません。
492
+ return;
402
493
  }
494
+
495
+ // Navigation API の traverseTo メソッドを使用し、一意の識別キーを指定して目的地へジャンプします。
496
+ this.navigation.traverseTo(entry.key);
497
+
498
+ break;
403
499
  }
500
+
501
+ default:
502
+ unreachable(args);
404
503
  }
405
504
  }
406
505
  }