vorma 0.0.0-pre.0 → 0.83.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 (225) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +48 -0
  3. package/internal/framework/_typescript/client/index.ts +64 -0
  4. package/internal/framework/_typescript/client/src/asset_manager.ts +67 -0
  5. package/internal/framework/_typescript/client/src/client.ts +1201 -0
  6. package/internal/framework/_typescript/client/src/client_loaders.ts +249 -0
  7. package/internal/framework/_typescript/client/src/component_loader.ts +105 -0
  8. package/internal/framework/_typescript/client/src/error_boundary.ts +7 -0
  9. package/internal/framework/_typescript/client/src/events.ts +54 -0
  10. package/internal/framework/_typescript/client/src/global_loading_indicator/global_loading_indicator.ts +125 -0
  11. package/internal/framework/_typescript/client/src/hard_reload.ts +1 -0
  12. package/internal/framework/_typescript/client/src/head_elements/head_elements.ts +193 -0
  13. package/internal/framework/_typescript/client/src/history/history.ts +118 -0
  14. package/internal/framework/_typescript/client/src/history/npm_history_types.ts +83 -0
  15. package/internal/framework/_typescript/client/src/hmr/hmr.ts +71 -0
  16. package/internal/framework/_typescript/client/src/init_client.ts +134 -0
  17. package/internal/framework/_typescript/client/src/links.ts +218 -0
  18. package/internal/framework/_typescript/client/src/redirects/redirects.ts +203 -0
  19. package/internal/framework/_typescript/client/src/rendering.ts +135 -0
  20. package/internal/framework/_typescript/client/src/resolve_public_href.ts +15 -0
  21. package/internal/framework/_typescript/client/src/scroll_state_manager.ts +100 -0
  22. package/internal/framework/_typescript/client/src/static_route_defs/route_def_helpers.ts +22 -0
  23. package/internal/framework/_typescript/client/src/ui_lib_impl_helpers/link_components.ts +131 -0
  24. package/internal/framework/_typescript/client/src/ui_lib_impl_helpers/route_components.ts +56 -0
  25. package/internal/framework/_typescript/client/src/ui_lib_impl_helpers/typed_navigate.ts +58 -0
  26. package/internal/framework/_typescript/client/src/utils/errors.ts +10 -0
  27. package/internal/framework/_typescript/client/src/utils/logging.ts +7 -0
  28. package/internal/framework/_typescript/client/src/vorma_app_helpers/vorma_app_helpers.ts +290 -0
  29. package/internal/framework/_typescript/client/src/vorma_ctx/vorma_ctx.ts +128 -0
  30. package/internal/framework/_typescript/client/src/window_focus_revalidation/window_focus_revalidation.ts +32 -0
  31. package/internal/framework/_typescript/client/tsconfig.json +3 -0
  32. package/internal/framework/_typescript/create/main.ts +378 -0
  33. package/internal/framework/_typescript/create/package.json +33 -0
  34. package/internal/framework/_typescript/create/pnpm-lock.yaml +70 -0
  35. package/internal/framework/_typescript/create/tsconfig.json +3 -0
  36. package/internal/framework/_typescript/preact/index.tsx +10 -0
  37. package/internal/framework/_typescript/preact/src/helpers.ts +113 -0
  38. package/internal/framework/_typescript/preact/src/link.tsx +107 -0
  39. package/internal/framework/_typescript/preact/src/preact.tsx +191 -0
  40. package/internal/framework/_typescript/preact/tsconfig.json +7 -0
  41. package/internal/framework/_typescript/react/index.tsx +10 -0
  42. package/internal/framework/_typescript/react/src/helpers.ts +118 -0
  43. package/internal/framework/_typescript/react/src/link.tsx +115 -0
  44. package/internal/framework/_typescript/react/src/react.tsx +299 -0
  45. package/internal/framework/_typescript/react/tsconfig.json +6 -0
  46. package/internal/framework/_typescript/solid/index.tsx +10 -0
  47. package/internal/framework/_typescript/solid/src/helpers.ts +114 -0
  48. package/internal/framework/_typescript/solid/src/link.tsx +104 -0
  49. package/internal/framework/_typescript/solid/src/solid.tsx +204 -0
  50. package/internal/framework/_typescript/solid/tsconfig.json +7 -0
  51. package/internal/framework/_typescript/vite/tsconfig.json +3 -0
  52. package/internal/framework/_typescript/vite/vite.ts +93 -0
  53. package/internal/site/frontend/assets/vorma-banner.webp +0 -0
  54. package/kit/_typescript/converters/converters.ts +152 -0
  55. package/kit/_typescript/cookies/cookies.ts +18 -0
  56. package/kit/_typescript/csrf/csrf.ts +10 -0
  57. package/kit/_typescript/debounce/debounce.ts +17 -0
  58. package/kit/_typescript/fmt/fmt.ts +3 -0
  59. package/kit/_typescript/json/deep_equals.ts +54 -0
  60. package/kit/_typescript/json/json.ts +3 -0
  61. package/kit/_typescript/json/search_param_serializer.ts +49 -0
  62. package/kit/_typescript/json/stringify_stable.ts +43 -0
  63. package/kit/_typescript/listeners/listeners.ts +16 -0
  64. package/kit/_typescript/matcher/find_best_match.ts +205 -0
  65. package/kit/_typescript/matcher/find_nested_matches.ts +357 -0
  66. package/kit/_typescript/matcher/parse_segments.ts +30 -0
  67. package/kit/_typescript/matcher/register.ts +271 -0
  68. package/kit/_typescript/theme/theme.ts +177 -0
  69. package/kit/_typescript/tsconfig.json +3 -0
  70. package/kit/_typescript/url/url.ts +132 -0
  71. package/npm_dist/internal/framework/_typescript/client/index.d.ts +17 -0
  72. package/npm_dist/internal/framework/_typescript/client/index.d.ts.map +1 -0
  73. package/npm_dist/internal/framework/_typescript/client/index.js +2489 -0
  74. package/npm_dist/internal/framework/_typescript/client/index.js.map +7 -0
  75. package/npm_dist/internal/framework/_typescript/client/src/asset_manager.d.ts +6 -0
  76. package/npm_dist/internal/framework/_typescript/client/src/asset_manager.d.ts.map +1 -0
  77. package/npm_dist/internal/framework/_typescript/client/src/client.d.ts +119 -0
  78. package/npm_dist/internal/framework/_typescript/client/src/client.d.ts.map +1 -0
  79. package/npm_dist/internal/framework/_typescript/client/src/client_loaders.d.ts +18 -0
  80. package/npm_dist/internal/framework/_typescript/client/src/client_loaders.d.ts.map +1 -0
  81. package/npm_dist/internal/framework/_typescript/client/src/component_loader.d.ts +10 -0
  82. package/npm_dist/internal/framework/_typescript/client/src/component_loader.d.ts.map +1 -0
  83. package/npm_dist/internal/framework/_typescript/client/src/error_boundary.d.ts +3 -0
  84. package/npm_dist/internal/framework/_typescript/client/src/error_boundary.d.ts.map +1 -0
  85. package/npm_dist/internal/framework/_typescript/client/src/events.d.ts +26 -0
  86. package/npm_dist/internal/framework/_typescript/client/src/events.d.ts.map +1 -0
  87. package/npm_dist/internal/framework/_typescript/client/src/global_loading_indicator/global_loading_indicator.d.ts +12 -0
  88. package/npm_dist/internal/framework/_typescript/client/src/global_loading_indicator/global_loading_indicator.d.ts.map +1 -0
  89. package/npm_dist/internal/framework/_typescript/client/src/hard_reload.d.ts +2 -0
  90. package/npm_dist/internal/framework/_typescript/client/src/hard_reload.d.ts.map +1 -0
  91. package/npm_dist/internal/framework/_typescript/client/src/head_elements/head_elements.d.ts +7 -0
  92. package/npm_dist/internal/framework/_typescript/client/src/head_elements/head_elements.d.ts.map +1 -0
  93. package/npm_dist/internal/framework/_typescript/client/src/history/history.d.ts +14 -0
  94. package/npm_dist/internal/framework/_typescript/client/src/history/history.d.ts.map +1 -0
  95. package/npm_dist/internal/framework/_typescript/client/src/history/npm_history_types.d.ts +84 -0
  96. package/npm_dist/internal/framework/_typescript/client/src/history/npm_history_types.d.ts.map +1 -0
  97. package/npm_dist/internal/framework/_typescript/client/src/hmr/hmr.d.ts +3 -0
  98. package/npm_dist/internal/framework/_typescript/client/src/hmr/hmr.d.ts.map +1 -0
  99. package/npm_dist/internal/framework/_typescript/client/src/init_client.d.ts +9 -0
  100. package/npm_dist/internal/framework/_typescript/client/src/init_client.d.ts.map +1 -0
  101. package/npm_dist/internal/framework/_typescript/client/src/links.d.ts +33 -0
  102. package/npm_dist/internal/framework/_typescript/client/src/links.d.ts.map +1 -0
  103. package/npm_dist/internal/framework/_typescript/client/src/redirects/redirects.d.ts +26 -0
  104. package/npm_dist/internal/framework/_typescript/client/src/redirects/redirects.d.ts.map +1 -0
  105. package/npm_dist/internal/framework/_typescript/client/src/rendering.d.ts +18 -0
  106. package/npm_dist/internal/framework/_typescript/client/src/rendering.d.ts.map +1 -0
  107. package/npm_dist/internal/framework/_typescript/client/src/resolve_public_href.d.ts +2 -0
  108. package/npm_dist/internal/framework/_typescript/client/src/resolve_public_href.d.ts.map +1 -0
  109. package/npm_dist/internal/framework/_typescript/client/src/scroll_state_manager.d.ts +22 -0
  110. package/npm_dist/internal/framework/_typescript/client/src/scroll_state_manager.d.ts.map +1 -0
  111. package/npm_dist/internal/framework/_typescript/client/src/static_route_defs/route_def_helpers.d.ts +12 -0
  112. package/npm_dist/internal/framework/_typescript/client/src/static_route_defs/route_def_helpers.d.ts.map +1 -0
  113. package/npm_dist/internal/framework/_typescript/client/src/ui_lib_impl_helpers/link_components.d.ts +28 -0
  114. package/npm_dist/internal/framework/_typescript/client/src/ui_lib_impl_helpers/link_components.d.ts.map +1 -0
  115. package/npm_dist/internal/framework/_typescript/client/src/ui_lib_impl_helpers/route_components.d.ts +18 -0
  116. package/npm_dist/internal/framework/_typescript/client/src/ui_lib_impl_helpers/route_components.d.ts.map +1 -0
  117. package/npm_dist/internal/framework/_typescript/client/src/ui_lib_impl_helpers/typed_navigate.d.ts +11 -0
  118. package/npm_dist/internal/framework/_typescript/client/src/ui_lib_impl_helpers/typed_navigate.d.ts.map +1 -0
  119. package/npm_dist/internal/framework/_typescript/client/src/utils/errors.d.ts +3 -0
  120. package/npm_dist/internal/framework/_typescript/client/src/utils/errors.d.ts.map +1 -0
  121. package/npm_dist/internal/framework/_typescript/client/src/utils/logging.d.ts +3 -0
  122. package/npm_dist/internal/framework/_typescript/client/src/utils/logging.d.ts.map +1 -0
  123. package/npm_dist/internal/framework/_typescript/client/src/vorma_app_helpers/vorma_app_helpers.d.ts +119 -0
  124. package/npm_dist/internal/framework/_typescript/client/src/vorma_app_helpers/vorma_app_helpers.d.ts.map +1 -0
  125. package/npm_dist/internal/framework/_typescript/client/src/vorma_ctx/vorma_ctx.d.ts +88 -0
  126. package/npm_dist/internal/framework/_typescript/client/src/vorma_ctx/vorma_ctx.d.ts.map +1 -0
  127. package/npm_dist/internal/framework/_typescript/client/src/window_focus_revalidation/window_focus_revalidation.d.ts +10 -0
  128. package/npm_dist/internal/framework/_typescript/client/src/window_focus_revalidation/window_focus_revalidation.d.ts.map +1 -0
  129. package/npm_dist/internal/framework/_typescript/create/main.d.ts +3 -0
  130. package/npm_dist/internal/framework/_typescript/create/main.d.ts.map +1 -0
  131. package/npm_dist/internal/framework/_typescript/preact/index.d.ts +4 -0
  132. package/npm_dist/internal/framework/_typescript/preact/index.d.ts.map +1 -0
  133. package/npm_dist/internal/framework/_typescript/preact/index.js +283 -0
  134. package/npm_dist/internal/framework/_typescript/preact/index.js.map +7 -0
  135. package/npm_dist/internal/framework/_typescript/preact/src/helpers.d.ts +21 -0
  136. package/npm_dist/internal/framework/_typescript/preact/src/helpers.d.ts.map +1 -0
  137. package/npm_dist/internal/framework/_typescript/preact/src/link.d.ts +11 -0
  138. package/npm_dist/internal/framework/_typescript/preact/src/link.d.ts.map +1 -0
  139. package/npm_dist/internal/framework/_typescript/preact/src/preact.d.ts +21 -0
  140. package/npm_dist/internal/framework/_typescript/preact/src/preact.d.ts.map +1 -0
  141. package/npm_dist/internal/framework/_typescript/react/index.d.ts +4 -0
  142. package/npm_dist/internal/framework/_typescript/react/index.d.ts.map +1 -0
  143. package/npm_dist/internal/framework/_typescript/react/index.js +370 -0
  144. package/npm_dist/internal/framework/_typescript/react/index.js.map +7 -0
  145. package/npm_dist/internal/framework/_typescript/react/src/helpers.d.ts +21 -0
  146. package/npm_dist/internal/framework/_typescript/react/src/helpers.d.ts.map +1 -0
  147. package/npm_dist/internal/framework/_typescript/react/src/link.d.ts +11 -0
  148. package/npm_dist/internal/framework/_typescript/react/src/link.d.ts.map +1 -0
  149. package/npm_dist/internal/framework/_typescript/react/src/react.d.ts +20 -0
  150. package/npm_dist/internal/framework/_typescript/react/src/react.d.ts.map +1 -0
  151. package/npm_dist/internal/framework/_typescript/solid/index.d.ts +4 -0
  152. package/npm_dist/internal/framework/_typescript/solid/index.d.ts.map +1 -0
  153. package/npm_dist/internal/framework/_typescript/solid/index.js +314 -0
  154. package/npm_dist/internal/framework/_typescript/solid/index.js.map +7 -0
  155. package/npm_dist/internal/framework/_typescript/solid/src/helpers.d.ts +22 -0
  156. package/npm_dist/internal/framework/_typescript/solid/src/helpers.d.ts.map +1 -0
  157. package/npm_dist/internal/framework/_typescript/solid/src/link.d.ts +11 -0
  158. package/npm_dist/internal/framework/_typescript/solid/src/link.d.ts.map +1 -0
  159. package/npm_dist/internal/framework/_typescript/solid/src/solid.d.ts +22 -0
  160. package/npm_dist/internal/framework/_typescript/solid/src/solid.d.ts.map +1 -0
  161. package/npm_dist/internal/framework/_typescript/vite/vite.d.ts +11 -0
  162. package/npm_dist/internal/framework/_typescript/vite/vite.d.ts.map +1 -0
  163. package/npm_dist/internal/framework/_typescript/vite/vite.js +82 -0
  164. package/npm_dist/internal/framework/_typescript/vite/vite.js.map +7 -0
  165. package/npm_dist/kit/_typescript/chunk-YBAPNBS2.js +202 -0
  166. package/npm_dist/kit/_typescript/chunk-YBAPNBS2.js.map +7 -0
  167. package/npm_dist/kit/_typescript/converters/converters.d.ts +26 -0
  168. package/npm_dist/kit/_typescript/converters/converters.d.ts.map +1 -0
  169. package/npm_dist/kit/_typescript/converters/converters.js +99 -0
  170. package/npm_dist/kit/_typescript/converters/converters.js.map +7 -0
  171. package/npm_dist/kit/_typescript/cookies/cookies.d.ts +13 -0
  172. package/npm_dist/kit/_typescript/cookies/cookies.d.ts.map +1 -0
  173. package/npm_dist/kit/_typescript/cookies/cookies.js +13 -0
  174. package/npm_dist/kit/_typescript/cookies/cookies.js.map +7 -0
  175. package/npm_dist/kit/_typescript/csrf/csrf.d.ts +5 -0
  176. package/npm_dist/kit/_typescript/csrf/csrf.d.ts.map +1 -0
  177. package/npm_dist/kit/_typescript/csrf/csrf.js +11 -0
  178. package/npm_dist/kit/_typescript/csrf/csrf.js.map +7 -0
  179. package/npm_dist/kit/_typescript/debounce/debounce.d.ts +4 -0
  180. package/npm_dist/kit/_typescript/debounce/debounce.d.ts.map +1 -0
  181. package/npm_dist/kit/_typescript/debounce/debounce.js +16 -0
  182. package/npm_dist/kit/_typescript/debounce/debounce.js.map +7 -0
  183. package/npm_dist/kit/_typescript/fmt/fmt.d.ts +2 -0
  184. package/npm_dist/kit/_typescript/fmt/fmt.d.ts.map +1 -0
  185. package/npm_dist/kit/_typescript/fmt/fmt.js +8 -0
  186. package/npm_dist/kit/_typescript/fmt/fmt.js.map +7 -0
  187. package/npm_dist/kit/_typescript/json/deep_equals.d.ts +7 -0
  188. package/npm_dist/kit/_typescript/json/deep_equals.d.ts.map +1 -0
  189. package/npm_dist/kit/_typescript/json/json.d.ts +4 -0
  190. package/npm_dist/kit/_typescript/json/json.d.ts.map +1 -0
  191. package/npm_dist/kit/_typescript/json/json.js +110 -0
  192. package/npm_dist/kit/_typescript/json/json.js.map +7 -0
  193. package/npm_dist/kit/_typescript/json/search_param_serializer.d.ts +2 -0
  194. package/npm_dist/kit/_typescript/json/search_param_serializer.d.ts.map +1 -0
  195. package/npm_dist/kit/_typescript/json/stringify_stable.d.ts +7 -0
  196. package/npm_dist/kit/_typescript/json/stringify_stable.d.ts.map +1 -0
  197. package/npm_dist/kit/_typescript/listeners/listeners.d.ts +2 -0
  198. package/npm_dist/kit/_typescript/listeners/listeners.d.ts.map +1 -0
  199. package/npm_dist/kit/_typescript/listeners/listeners.js +20 -0
  200. package/npm_dist/kit/_typescript/listeners/listeners.js.map +7 -0
  201. package/npm_dist/kit/_typescript/matcher/find_best_match.d.ts +10 -0
  202. package/npm_dist/kit/_typescript/matcher/find_best_match.d.ts.map +1 -0
  203. package/npm_dist/kit/_typescript/matcher/find_best_match.js +146 -0
  204. package/npm_dist/kit/_typescript/matcher/find_best_match.js.map +7 -0
  205. package/npm_dist/kit/_typescript/matcher/find_nested_matches.d.ts +14 -0
  206. package/npm_dist/kit/_typescript/matcher/find_nested_matches.d.ts.map +1 -0
  207. package/npm_dist/kit/_typescript/matcher/find_nested_matches.js +248 -0
  208. package/npm_dist/kit/_typescript/matcher/find_nested_matches.js.map +7 -0
  209. package/npm_dist/kit/_typescript/matcher/parse_segments.d.ts +2 -0
  210. package/npm_dist/kit/_typescript/matcher/parse_segments.d.ts.map +1 -0
  211. package/npm_dist/kit/_typescript/matcher/register.d.ts +54 -0
  212. package/npm_dist/kit/_typescript/matcher/register.d.ts.map +1 -0
  213. package/npm_dist/kit/_typescript/matcher/register.js +21 -0
  214. package/npm_dist/kit/_typescript/matcher/register.js.map +7 -0
  215. package/npm_dist/kit/_typescript/theme/theme.d.ts +24 -0
  216. package/npm_dist/kit/_typescript/theme/theme.d.ts.map +1 -0
  217. package/npm_dist/kit/_typescript/theme/theme.js +133 -0
  218. package/npm_dist/kit/_typescript/theme/theme.js.map +7 -0
  219. package/npm_dist/kit/_typescript/url/url.d.ts +30 -0
  220. package/npm_dist/kit/_typescript/url/url.d.ts.map +1 -0
  221. package/npm_dist/kit/_typescript/url/url.js +100 -0
  222. package/npm_dist/kit/_typescript/url/url.js.map +7 -0
  223. package/package.json +135 -3
  224. package/tsconfig.base.json +17 -0
  225. package/index.js +0 -1
@@ -0,0 +1,218 @@
1
+ import { getAnchorDetailsFromEvent, getHrefDetails } from "vorma/kit/url";
2
+ import { navigationStateManager, vormaNavigate } from "./client.ts";
3
+ import { saveScrollState } from "./scroll_state_manager.ts";
4
+
5
+ type LinkOnClickCallback<E extends Event> = (event: E) => void | Promise<void>;
6
+
7
+ type LinkOnClickCallbacks<E extends Event> = {
8
+ beforeBegin?: LinkOnClickCallback<E>;
9
+ beforeRender?: LinkOnClickCallback<E>;
10
+ afterRender?: LinkOnClickCallback<E>;
11
+ };
12
+
13
+ type GetPrefetchHandlersInput<E extends Event> = LinkOnClickCallbacks<E> & {
14
+ href: string;
15
+ delayMs?: number;
16
+ scrollToTop?: boolean;
17
+ replace?: boolean;
18
+ search?: string;
19
+ hash?: string;
20
+ state?: unknown;
21
+ };
22
+
23
+ export function __getPrefetchHandlers<E extends Event>(
24
+ input: GetPrefetchHandlersInput<E>,
25
+ ) {
26
+ const hrefDetails = getHrefDetails(input.href);
27
+ if (!hrefDetails.isHTTP) {
28
+ return;
29
+ }
30
+
31
+ // TypeScript type guard -- after this check, we know relativeURL exists
32
+ const { relativeURL } = hrefDetails;
33
+ if (!relativeURL || hrefDetails.isExternal) {
34
+ return;
35
+ }
36
+
37
+ let timer: number | undefined;
38
+ let prefetchStarted = false;
39
+ const delayMs = input.delayMs ?? 100;
40
+
41
+ async function prefetch(e: E): Promise<void> {
42
+ if (prefetchStarted) return;
43
+ prefetchStarted = true;
44
+
45
+ if (input.beforeBegin) {
46
+ await input.beforeBegin(e);
47
+ }
48
+
49
+ const fullUrl = new URL(relativeURL, window.location.href);
50
+ if (input.search !== undefined) fullUrl.search = input.search;
51
+ if (input.hash !== undefined) fullUrl.hash = input.hash;
52
+
53
+ // Use the navigation system
54
+ await navigationStateManager.navigate({
55
+ href: fullUrl.href,
56
+ navigationType: "prefetch",
57
+ state: input.state,
58
+ });
59
+ }
60
+
61
+ function start(e: E): void {
62
+ if (prefetchStarted) return;
63
+ timer = window.setTimeout(() => prefetch(e), delayMs);
64
+ }
65
+
66
+ function stop(): void {
67
+ if (timer) {
68
+ clearTimeout(timer);
69
+ timer = undefined;
70
+ }
71
+
72
+ // Abort prefetch if it exists and hasn't been upgraded
73
+ const targetUrl = new URL(relativeURL, window.location.href).href;
74
+ const nav = navigationStateManager.getNavigation(targetUrl);
75
+ if (nav && nav.type === "prefetch" && nav.intent === "none") {
76
+ nav.control.abortController?.abort();
77
+ navigationStateManager.removeNavigation(targetUrl);
78
+ }
79
+
80
+ prefetchStarted = false;
81
+ }
82
+
83
+ async function onClick(e: E): Promise<void> {
84
+ if (e.defaultPrevented) return;
85
+
86
+ const anchorDetails = getAnchorDetailsFromEvent(
87
+ e as unknown as MouseEvent,
88
+ );
89
+ if (!anchorDetails) return;
90
+
91
+ const { isEligibleForDefaultPrevention, isInternal } = anchorDetails;
92
+ if (!isEligibleForDefaultPrevention || !isInternal) return;
93
+
94
+ if (isJustAHashChange(anchorDetails)) {
95
+ saveScrollState();
96
+ return;
97
+ }
98
+
99
+ e.preventDefault();
100
+
101
+ if (timer) {
102
+ clearTimeout(timer);
103
+ timer = undefined;
104
+ }
105
+
106
+ // Execute callbacks
107
+ if (input.beforeBegin && !prefetchStarted) {
108
+ await input.beforeBegin(e);
109
+ }
110
+
111
+ if (input.beforeRender) {
112
+ await input.beforeRender(e);
113
+ }
114
+
115
+ // Use standard navigation -- it will upgrade the prefetch if it exists
116
+ await vormaNavigate(relativeURL, {
117
+ scrollToTop: input.scrollToTop,
118
+ replace: input.replace,
119
+ search: input.search,
120
+ hash: input.hash,
121
+ state: input.state,
122
+ });
123
+
124
+ if (input.afterRender) {
125
+ await input.afterRender(e);
126
+ }
127
+ }
128
+
129
+ return {
130
+ ...hrefDetails,
131
+ start,
132
+ stop,
133
+ onClick,
134
+ };
135
+ }
136
+
137
+ export function __makeLinkOnClickFn<E extends Event>(
138
+ callbacks: LinkOnClickCallbacks<E> & {
139
+ scrollToTop?: boolean;
140
+ replace?: boolean;
141
+ state?: unknown;
142
+ },
143
+ ) {
144
+ return async (e: E) => {
145
+ if (e.defaultPrevented) return;
146
+
147
+ const anchorDetails = getAnchorDetailsFromEvent(
148
+ e as unknown as MouseEvent,
149
+ );
150
+ if (!anchorDetails) return;
151
+
152
+ const { anchor, isEligibleForDefaultPrevention, isInternal } =
153
+ anchorDetails;
154
+ if (!anchor) return;
155
+
156
+ if (isJustAHashChange(anchorDetails)) {
157
+ saveScrollState();
158
+ return;
159
+ }
160
+
161
+ if (isEligibleForDefaultPrevention && isInternal) {
162
+ e.preventDefault();
163
+
164
+ await callbacks.beforeBegin?.(e);
165
+
166
+ const control = navigationStateManager.beginNavigation({
167
+ href: anchor.href,
168
+ navigationType: "userNavigation",
169
+ scrollToTop: callbacks.scrollToTop,
170
+ replace: callbacks.replace,
171
+ state: callbacks.state,
172
+ });
173
+
174
+ if (!control.promise) return;
175
+
176
+ const res = await control.promise;
177
+
178
+ if (!res) {
179
+ // If not here, loading indicator can get stuck on
180
+ // following redirects
181
+ const targetUrl = new URL(anchor.href, window.location.href)
182
+ .href;
183
+ navigationStateManager.removeNavigation(targetUrl);
184
+ return;
185
+ }
186
+
187
+ await callbacks.beforeRender?.(e);
188
+
189
+ const targetUrl = new URL(anchor.href, window.location.href).href;
190
+ const entry = navigationStateManager.getNavigation(targetUrl);
191
+ if (entry) {
192
+ await navigationStateManager["processNavigationResult"](
193
+ res,
194
+ entry,
195
+ );
196
+ }
197
+
198
+ await callbacks.afterRender?.(e);
199
+ }
200
+ };
201
+ }
202
+
203
+ function isJustAHashChange(
204
+ anchorDetails: ReturnType<typeof getAnchorDetailsFromEvent>,
205
+ ): boolean {
206
+ if (!anchorDetails) return false;
207
+
208
+ const { pathname, search, hash } = new URL(
209
+ anchorDetails.anchor.href,
210
+ window.location.href,
211
+ );
212
+
213
+ return !!(
214
+ hash &&
215
+ pathname === window.location.pathname &&
216
+ search === window.location.search
217
+ );
218
+ }
@@ -0,0 +1,203 @@
1
+ import {
2
+ getHrefDetails,
3
+ getIsGETRequest,
4
+ type HrefDetails,
5
+ } from "vorma/kit/url";
6
+ import { navigationStateManager, type NavigateProps } from "../client.ts";
7
+ import { VORMA_HARD_RELOAD_QUERY_PARAM } from "../hard_reload.ts";
8
+ import { logError, logInfo } from "../utils/logging.ts";
9
+
10
+ export type RedirectData = { href: string; hrefDetails: HrefDetails } & (
11
+ | {
12
+ status: "did";
13
+ }
14
+ | {
15
+ status: "should";
16
+ shouldRedirectStrategy: "hard" | "soft";
17
+ latestBuildID: string;
18
+ }
19
+ );
20
+
21
+ export function getBuildIDFromResponse(response: Response | undefined) {
22
+ return response?.headers.get("X-Vorma-Build-Id") || "";
23
+ }
24
+
25
+ export function parseFetchResponseForRedirectData(
26
+ reqInit: RequestInit,
27
+ res: Response,
28
+ ): RedirectData | null {
29
+ const latestBuildID = getBuildIDFromResponse(res);
30
+
31
+ const vormaReloadTarget = res.headers.get("X-Vorma-Reload");
32
+ if (vormaReloadTarget) {
33
+ const newURL = new URL(vormaReloadTarget, window.location.href);
34
+ const hrefDetails = getHrefDetails(newURL.href);
35
+ if (!hrefDetails.isHTTP) {
36
+ return null;
37
+ }
38
+
39
+ return {
40
+ hrefDetails,
41
+ status: "should",
42
+ href: vormaReloadTarget,
43
+ shouldRedirectStrategy: "hard",
44
+ latestBuildID,
45
+ };
46
+ }
47
+
48
+ if (res.redirected) {
49
+ const newURL = new URL(res.url, window.location.href);
50
+ const hrefDetails = getHrefDetails(newURL.href);
51
+ if (!hrefDetails.isHTTP) {
52
+ return null;
53
+ }
54
+
55
+ const isCurrent = newURL.href === window.location.href;
56
+ if (isCurrent) {
57
+ return { hrefDetails, status: "did", href: newURL.href };
58
+ }
59
+
60
+ const wasGETRequest = getIsGETRequest(reqInit);
61
+ if (!wasGETRequest) {
62
+ logInfo("Not a GET request. No way to handle.");
63
+ return null;
64
+ }
65
+
66
+ return {
67
+ hrefDetails,
68
+ status: "should",
69
+ href: newURL.href,
70
+ shouldRedirectStrategy: hrefDetails.isInternal ? "soft" : "hard",
71
+ latestBuildID,
72
+ };
73
+ }
74
+
75
+ const clientRedirectHeader = res.headers.get("X-Client-Redirect");
76
+
77
+ if (!clientRedirectHeader) {
78
+ return null;
79
+ }
80
+
81
+ const newURL = new URL(clientRedirectHeader, window.location.href);
82
+ const hrefDetails = getHrefDetails(newURL.href);
83
+ if (!hrefDetails.isHTTP) {
84
+ return null;
85
+ }
86
+
87
+ return {
88
+ hrefDetails,
89
+ status: "should",
90
+ href: hrefDetails.absoluteURL,
91
+ shouldRedirectStrategy: hrefDetails.isInternal ? "soft" : "hard",
92
+ latestBuildID,
93
+ };
94
+ }
95
+
96
+ export async function effectuateRedirectDataResult(
97
+ redirectData: RedirectData,
98
+ redirectCount: number,
99
+ originalProps?: NavigateProps,
100
+ ): Promise<RedirectData | null> {
101
+ if (redirectData.status !== "should") {
102
+ return null;
103
+ }
104
+
105
+ // Clean up any active redirect or revalidations when redirecting.
106
+ // Otherwise loading state will get stuck.
107
+ const navEntries = navigationStateManager.getNavigations().entries();
108
+ for (const [key, nav] of navEntries) {
109
+ if (nav.type === "redirect" || nav.type === "revalidation") {
110
+ nav.control.abortController?.abort();
111
+ navigationStateManager.removeNavigation(key);
112
+ }
113
+ }
114
+
115
+ if (redirectData.shouldRedirectStrategy === "hard") {
116
+ if (!redirectData.hrefDetails.isHTTP) return null;
117
+
118
+ if (redirectData.hrefDetails.isExternal) {
119
+ window.location.href = redirectData.href;
120
+ } else {
121
+ const url = new URL(redirectData.href, window.location.href);
122
+ url.searchParams.set(
123
+ VORMA_HARD_RELOAD_QUERY_PARAM,
124
+ redirectData.latestBuildID,
125
+ );
126
+ window.location.href = url.href;
127
+ }
128
+
129
+ return {
130
+ hrefDetails: redirectData.hrefDetails,
131
+ status: "did",
132
+ href: redirectData.href,
133
+ };
134
+ }
135
+
136
+ if (redirectData.shouldRedirectStrategy === "soft") {
137
+ await navigationStateManager.navigate({
138
+ href: redirectData.href,
139
+ navigationType: "redirect",
140
+ redirectCount: redirectCount + 1,
141
+ state: originalProps?.state,
142
+ replace: originalProps?.replace,
143
+ scrollToTop: originalProps?.scrollToTop,
144
+ });
145
+
146
+ return {
147
+ hrefDetails: redirectData.hrefDetails,
148
+ status: "did",
149
+ href: redirectData.href,
150
+ };
151
+ }
152
+
153
+ return null;
154
+ }
155
+
156
+ export async function handleRedirects(props: {
157
+ abortController: AbortController;
158
+ url: URL;
159
+ requestInit?: RequestInit;
160
+ isPrefetch?: boolean;
161
+ redirectCount?: number;
162
+ }): Promise<{ redirectData: RedirectData | null; response?: Response }> {
163
+ const MAX_REDIRECTS = 10;
164
+ const redirectCount = props.redirectCount || 0;
165
+
166
+ if (redirectCount >= MAX_REDIRECTS) {
167
+ logError("Too many redirects");
168
+ return { redirectData: null, response: undefined };
169
+ }
170
+
171
+ // Prepare request
172
+ const bodyParentObj: RequestInit = {};
173
+ const isGET = getIsGETRequest(props.requestInit);
174
+
175
+ if (props.requestInit && (props.requestInit.body !== undefined || !isGET)) {
176
+ if (
177
+ props.requestInit.body instanceof FormData ||
178
+ typeof props.requestInit.body === "string"
179
+ ) {
180
+ bodyParentObj.body = props.requestInit.body;
181
+ } else {
182
+ bodyParentObj.body = JSON.stringify(props.requestInit.body);
183
+ }
184
+ }
185
+
186
+ const headers = new Headers(props.requestInit?.headers);
187
+ // To temporarily test traditional server redirect behavior,
188
+ // you can set this to "0" instead of "1"
189
+ headers.set("X-Accepts-Client-Redirect", "1");
190
+ bodyParentObj.headers = headers;
191
+
192
+ const finalRequestInit = {
193
+ signal: props.abortController.signal,
194
+ ...props.requestInit,
195
+ ...bodyParentObj,
196
+ };
197
+
198
+ // Execute request
199
+ const res = await fetch(props.url, finalRequestInit);
200
+ let redirectData = parseFetchResponseForRedirectData(finalRequestInit, res);
201
+
202
+ return { redirectData, response: res };
203
+ }
@@ -0,0 +1,135 @@
1
+ import { AssetManager } from "./asset_manager.ts";
2
+ import type { VormaNavigationType } from "./client.ts";
3
+ import { deriveAndSetErrorState } from "./client_loaders.ts";
4
+ import { ComponentLoader } from "./component_loader.ts";
5
+ import { dispatchRouteChangeEvent } from "./events.ts";
6
+ import { updateHeadEls } from "./head_elements/head_elements.ts";
7
+ import { HistoryManager } from "./history/history.ts";
8
+ import type { ScrollState } from "./scroll_state_manager.ts";
9
+ import {
10
+ __vormaClientGlobal,
11
+ type GetRouteDataOutput,
12
+ } from "./vorma_ctx/vorma_ctx.ts";
13
+
14
+ type RerenderAppProps = {
15
+ json: GetRouteDataOutput;
16
+ navigationType: VormaNavigationType;
17
+ runHistoryOptions?: {
18
+ href: string;
19
+ scrollStateToRestore?: ScrollState;
20
+ replace?: boolean;
21
+ scrollToTop?: boolean;
22
+ state?: unknown;
23
+ };
24
+ onFinish: () => void;
25
+ };
26
+
27
+ export async function __reRenderApp(props: RerenderAppProps): Promise<void> {
28
+ const shouldUseViewTransitions =
29
+ __vormaClientGlobal.get("useViewTransitions") &&
30
+ !!document.startViewTransition &&
31
+ props.navigationType !== "prefetch" &&
32
+ props.navigationType !== "revalidation";
33
+
34
+ if (shouldUseViewTransitions) {
35
+ const transition = document.startViewTransition(async () => {
36
+ await __reRenderAppInner(props);
37
+ });
38
+ await transition.finished;
39
+ } else {
40
+ await __reRenderAppInner(props);
41
+ }
42
+ }
43
+
44
+ async function __reRenderAppInner(props: RerenderAppProps): Promise<void> {
45
+ const { json, navigationType, runHistoryOptions } = props;
46
+
47
+ // Update global state
48
+ const stateKeys = [
49
+ "outermostServerError",
50
+ "outermostServerErrorIdx",
51
+ "errorExportKeys",
52
+ "matchedPatterns",
53
+ "loadersData",
54
+ "importURLs",
55
+ "exportKeys",
56
+ "hasRootData",
57
+ "params",
58
+ "splatValues",
59
+ ] as const;
60
+
61
+ for (const key of stateKeys) {
62
+ __vormaClientGlobal.set(key, json[key]);
63
+ }
64
+
65
+ deriveAndSetErrorState();
66
+
67
+ // Load components and error boundary
68
+ await ComponentLoader.handleComponents(json.importURLs);
69
+ await ComponentLoader.handleErrorBoundaryComponent(json.importURLs);
70
+
71
+ // Handle history and scroll
72
+ let scrollStateToDispatch: ScrollState | undefined;
73
+
74
+ if (runHistoryOptions) {
75
+ const { href, scrollStateToRestore, replace, scrollToTop } =
76
+ runHistoryOptions;
77
+ const hash = href.split("#")[1];
78
+ const history = HistoryManager.getInstance();
79
+
80
+ if (
81
+ navigationType === "userNavigation" ||
82
+ navigationType === "redirect"
83
+ ) {
84
+ const target = new URL(href, window.location.href).href;
85
+ const current = new URL(window.location.href).href;
86
+
87
+ if (target !== current && !replace) {
88
+ history.push(href, runHistoryOptions.state);
89
+ } else {
90
+ history.replace(href, runHistoryOptions.state);
91
+ }
92
+
93
+ scrollStateToDispatch = hash
94
+ ? { hash }
95
+ : scrollToTop !== false
96
+ ? { x: 0, y: 0 }
97
+ : undefined;
98
+ }
99
+
100
+ if (navigationType === "browserHistory") {
101
+ scrollStateToDispatch =
102
+ scrollStateToRestore ?? (hash ? { hash } : undefined);
103
+ }
104
+ }
105
+
106
+ if (json.title !== undefined) {
107
+ // Changing the title instantly makes it feel faster
108
+ // The temp textarea trick is to decode any HTML entities in the title.
109
+ // This should come after pushing to history though, so that the title is
110
+ // correct in the history entry.
111
+ const tempTxt = document.createElement("textarea");
112
+ tempTxt.innerHTML = json.title?.dangerousInnerHTML || "";
113
+ if (document.title !== tempTxt.value) {
114
+ document.title = tempTxt.value;
115
+ }
116
+ }
117
+
118
+ // Apply CSS
119
+ if (json.cssBundles) {
120
+ AssetManager.applyCSS(json.cssBundles);
121
+ }
122
+
123
+ // Dispatch route change event -- this triggers the actual UI update
124
+ dispatchRouteChangeEvent({ __scrollState: scrollStateToDispatch });
125
+
126
+ // Only update head elements if provided (not undefined)
127
+ if (json.metaHeadEls !== undefined) {
128
+ updateHeadEls("meta", json.metaHeadEls ?? []);
129
+ }
130
+ if (json.restHeadEls !== undefined) {
131
+ updateHeadEls("rest", json.restHeadEls ?? []);
132
+ }
133
+
134
+ props.onFinish();
135
+ }
@@ -0,0 +1,15 @@
1
+ import { __vormaClientGlobal } from "./vorma_ctx/vorma_ctx.ts";
2
+
3
+ export function resolvePublicHref(relativeHref: string): string {
4
+ let baseURL = __vormaClientGlobal.get("viteDevURL");
5
+ if (!baseURL) {
6
+ baseURL = __vormaClientGlobal.get("publicPathPrefix");
7
+ }
8
+ if (baseURL.endsWith("/")) {
9
+ baseURL = baseURL.slice(0, -1);
10
+ }
11
+ let final = relativeHref.startsWith("/")
12
+ ? baseURL + relativeHref
13
+ : baseURL + "/" + relativeHref;
14
+ return final;
15
+ }
@@ -0,0 +1,100 @@
1
+ import { HistoryManager } from "./history/history.ts";
2
+
3
+ export type ScrollState = { x: number; y: number } | { hash: string };
4
+
5
+ class ScrollStateManager {
6
+ private readonly STORAGE_KEY = "__vorma__scrollStateMap";
7
+ private readonly PAGE_REFRESH_KEY = "__vorma__pageRefreshScrollState";
8
+ private readonly MAX_ENTRIES = 50;
9
+
10
+ saveState(key: string, state: ScrollState): void {
11
+ const map = this.getMap();
12
+ map.set(key, state);
13
+
14
+ // Enforce size limit
15
+ if (map.size > this.MAX_ENTRIES) {
16
+ const firstKey = map.keys().next().value;
17
+ if (firstKey) map.delete(firstKey);
18
+ }
19
+
20
+ this.saveMap(map);
21
+ }
22
+
23
+ getState(key: string): ScrollState | undefined {
24
+ return this.getMap().get(key);
25
+ }
26
+
27
+ savePageRefreshState(): void {
28
+ const state = {
29
+ x: window.scrollX,
30
+ y: window.scrollY,
31
+ unix: Date.now(),
32
+ href: window.location.href,
33
+ };
34
+ sessionStorage.setItem(this.PAGE_REFRESH_KEY, JSON.stringify(state));
35
+ }
36
+
37
+ restorePageRefreshState(): void {
38
+ const stored = sessionStorage.getItem(this.PAGE_REFRESH_KEY);
39
+ if (!stored) return;
40
+
41
+ try {
42
+ const state = JSON.parse(stored);
43
+ if (
44
+ state.href === window.location.href &&
45
+ Date.now() - state.unix < 5000
46
+ ) {
47
+ sessionStorage.removeItem(this.PAGE_REFRESH_KEY);
48
+ window.requestAnimationFrame(() => {
49
+ __applyScrollState({ x: state.x, y: state.y });
50
+ });
51
+ }
52
+ } catch {}
53
+ }
54
+
55
+ private getMap(): Map<string, ScrollState> {
56
+ const stored = sessionStorage.getItem(this.STORAGE_KEY);
57
+ if (!stored) return new Map();
58
+
59
+ try {
60
+ return new Map(JSON.parse(stored));
61
+ } catch {
62
+ return new Map();
63
+ }
64
+ }
65
+
66
+ private saveMap(map: Map<string, ScrollState>): void {
67
+ sessionStorage.setItem(
68
+ this.STORAGE_KEY,
69
+ JSON.stringify(Array.from(map.entries())),
70
+ );
71
+ }
72
+ }
73
+
74
+ export const scrollStateManager = new ScrollStateManager();
75
+
76
+ export function __applyScrollState(state?: ScrollState): void {
77
+ if (!state) {
78
+ const id = window.location.hash.slice(1);
79
+ if (id) {
80
+ document.getElementById(id)?.scrollIntoView();
81
+ }
82
+ return;
83
+ }
84
+
85
+ if ("hash" in state) {
86
+ if (state.hash) {
87
+ document.getElementById(state.hash)?.scrollIntoView();
88
+ }
89
+ } else {
90
+ window.scrollTo(state.x, state.y);
91
+ }
92
+ }
93
+
94
+ export function saveScrollState(): void {
95
+ const lastKnownLocation = HistoryManager.getLastKnownLocation();
96
+ scrollStateManager.saveState(lastKnownLocation.key, {
97
+ x: window.scrollX,
98
+ y: window.scrollY,
99
+ });
100
+ }
@@ -0,0 +1,22 @@
1
+ // Used by client route defs file (e.g., vorma.routes.ts)
2
+
3
+ type ImportPromise = Promise<Record<string, any>>;
4
+ type Key<T extends ImportPromise> = keyof Awaited<T>;
5
+
6
+ /**
7
+ * Registers a route with the given route pattern,
8
+ * module import promise, component export key, and
9
+ * optional error boundary export key. Only for use
10
+ * in your centralized build-time route definitions
11
+ * file.
12
+ */
13
+ export function route<IP extends ImportPromise>(
14
+ // oxlint-disable-next-line no-unused-vars
15
+ pattern: string,
16
+ // oxlint-disable-next-line no-unused-vars
17
+ importPromise: IP,
18
+ // oxlint-disable-next-line no-unused-vars
19
+ componentKey: Key<IP>,
20
+ // oxlint-disable-next-line no-unused-vars
21
+ errorBoundaryKey?: Key<IP>,
22
+ ): void {}