@textjs/app 0.0.0-stage → 1.0.3

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 (335) hide show
  1. package/README.md +34 -2
  2. package/bin/text.js +29 -0
  3. package/bin/text.ts +13 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/cli/build.d.ts +39 -0
  6. package/dist/cli/build.d.ts.map +1 -0
  7. package/dist/cli/build.js +157 -0
  8. package/dist/cli/build.js.map +1 -0
  9. package/dist/cli/config.d.ts +22 -0
  10. package/dist/cli/config.d.ts.map +1 -0
  11. package/dist/cli/config.js +73 -0
  12. package/dist/cli/config.js.map +1 -0
  13. package/dist/cli/dev.d.ts +12 -0
  14. package/dist/cli/dev.d.ts.map +1 -0
  15. package/dist/cli/dev.js +49 -0
  16. package/dist/cli/dev.js.map +1 -0
  17. package/dist/cli/index.d.ts +26 -0
  18. package/dist/cli/index.d.ts.map +1 -0
  19. package/dist/cli/index.js +192 -0
  20. package/dist/cli/index.js.map +1 -0
  21. package/dist/cli/run.d.ts +31 -0
  22. package/dist/cli/run.d.ts.map +1 -0
  23. package/dist/cli/run.js +111 -0
  24. package/dist/cli/run.js.map +1 -0
  25. package/dist/cli/start.d.ts +14 -0
  26. package/dist/cli/start.d.ts.map +1 -0
  27. package/dist/cli/start.js +183 -0
  28. package/dist/cli/start.js.map +1 -0
  29. package/dist/cli/ui.d.ts +35 -0
  30. package/dist/cli/ui.d.ts.map +1 -0
  31. package/dist/cli/ui.js +73 -0
  32. package/dist/cli/ui.js.map +1 -0
  33. package/dist/client.d.ts +34 -0
  34. package/dist/client.d.ts.map +1 -0
  35. package/dist/client.js +93 -0
  36. package/dist/client.js.map +1 -0
  37. package/dist/config.d.ts +143 -0
  38. package/dist/config.d.ts.map +1 -0
  39. package/dist/config.js +28 -0
  40. package/dist/config.js.map +1 -0
  41. package/dist/context.d.ts +44 -0
  42. package/dist/context.d.ts.map +1 -0
  43. package/dist/context.js +26 -0
  44. package/dist/context.js.map +1 -0
  45. package/dist/dev-errors.d.ts +68 -0
  46. package/dist/dev-errors.d.ts.map +1 -0
  47. package/dist/dev-errors.js +121 -0
  48. package/dist/dev-errors.js.map +1 -0
  49. package/dist/devtools.d.ts +64 -0
  50. package/dist/devtools.d.ts.map +1 -0
  51. package/dist/devtools.js +186 -0
  52. package/dist/devtools.js.map +1 -0
  53. package/dist/frame-mirror.d.ts +19 -0
  54. package/dist/frame-mirror.d.ts.map +1 -0
  55. package/dist/frame-mirror.js +141 -0
  56. package/dist/frame-mirror.js.map +1 -0
  57. package/dist/frame-protocol.d.ts +175 -0
  58. package/dist/frame-protocol.d.ts.map +1 -0
  59. package/dist/frame-protocol.js +369 -0
  60. package/dist/frame-protocol.js.map +1 -0
  61. package/dist/history.d.ts +54 -0
  62. package/dist/history.d.ts.map +1 -0
  63. package/dist/history.js +152 -0
  64. package/dist/history.js.map +1 -0
  65. package/dist/index.d.ts +9 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +8 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/link.d.ts +32 -0
  70. package/dist/link.d.ts.map +1 -0
  71. package/dist/link.js +54 -0
  72. package/dist/link.js.map +1 -0
  73. package/dist/live-player.d.ts +83 -0
  74. package/dist/live-player.d.ts.map +1 -0
  75. package/dist/live-player.js +413 -0
  76. package/dist/live-player.js.map +1 -0
  77. package/dist/live-protocol.d.ts +132 -0
  78. package/dist/live-protocol.d.ts.map +1 -0
  79. package/dist/live-protocol.js +278 -0
  80. package/dist/live-protocol.js.map +1 -0
  81. package/dist/live-stub.d.ts +10 -0
  82. package/dist/live-stub.d.ts.map +1 -0
  83. package/dist/live-stub.js +32 -0
  84. package/dist/live-stub.js.map +1 -0
  85. package/dist/mcp/cli.d.ts +22 -0
  86. package/dist/mcp/cli.d.ts.map +1 -0
  87. package/dist/mcp/cli.js +177 -0
  88. package/dist/mcp/cli.js.map +1 -0
  89. package/dist/mcp/frame.d.ts +58 -0
  90. package/dist/mcp/frame.d.ts.map +1 -0
  91. package/dist/mcp/frame.js +169 -0
  92. package/dist/mcp/frame.js.map +1 -0
  93. package/dist/mcp/index.d.ts +15 -0
  94. package/dist/mcp/index.d.ts.map +1 -0
  95. package/dist/mcp/index.js +15 -0
  96. package/dist/mcp/index.js.map +1 -0
  97. package/dist/mcp/inspect.d.ts +95 -0
  98. package/dist/mcp/inspect.d.ts.map +1 -0
  99. package/dist/mcp/inspect.js +386 -0
  100. package/dist/mcp/inspect.js.map +1 -0
  101. package/dist/mcp/keys.d.ts +20 -0
  102. package/dist/mcp/keys.d.ts.map +1 -0
  103. package/dist/mcp/keys.js +87 -0
  104. package/dist/mcp/keys.js.map +1 -0
  105. package/dist/mcp/logs.d.ts +63 -0
  106. package/dist/mcp/logs.d.ts.map +1 -0
  107. package/dist/mcp/logs.js +217 -0
  108. package/dist/mcp/logs.js.map +1 -0
  109. package/dist/mcp/server.d.ts +64 -0
  110. package/dist/mcp/server.d.ts.map +1 -0
  111. package/dist/mcp/server.js +485 -0
  112. package/dist/mcp/server.js.map +1 -0
  113. package/dist/mcp/session.d.ts +230 -0
  114. package/dist/mcp/session.d.ts.map +1 -0
  115. package/dist/mcp/session.js +742 -0
  116. package/dist/mcp/session.js.map +1 -0
  117. package/dist/navigation-errors.d.ts +28 -0
  118. package/dist/navigation-errors.d.ts.map +1 -0
  119. package/dist/navigation-errors.js +43 -0
  120. package/dist/navigation-errors.js.map +1 -0
  121. package/dist/navigation.d.ts +21 -0
  122. package/dist/navigation.d.ts.map +1 -0
  123. package/dist/navigation.js +33 -0
  124. package/dist/navigation.js.map +1 -0
  125. package/dist/router.d.ts +76 -0
  126. package/dist/router.d.ts.map +1 -0
  127. package/dist/router.js +296 -0
  128. package/dist/router.js.map +1 -0
  129. package/dist/routing.d.ts +101 -0
  130. package/dist/routing.d.ts.map +1 -0
  131. package/dist/routing.js +286 -0
  132. package/dist/routing.js.map +1 -0
  133. package/dist/server/adapters/build-step.d.ts +34 -0
  134. package/dist/server/adapters/build-step.d.ts.map +1 -0
  135. package/dist/server/adapters/build-step.js +112 -0
  136. package/dist/server/adapters/build-step.js.map +1 -0
  137. package/dist/server/adapters/bun.d.ts +8 -0
  138. package/dist/server/adapters/bun.d.ts.map +1 -0
  139. package/dist/server/adapters/bun.js +89 -0
  140. package/dist/server/adapters/bun.js.map +1 -0
  141. package/dist/server/adapters/bundle.d.ts +21 -0
  142. package/dist/server/adapters/bundle.d.ts.map +1 -0
  143. package/dist/server/adapters/bundle.js +86 -0
  144. package/dist/server/adapters/bundle.js.map +1 -0
  145. package/dist/server/adapters/cloudflare.d.ts +29 -0
  146. package/dist/server/adapters/cloudflare.d.ts.map +1 -0
  147. package/dist/server/adapters/cloudflare.js +105 -0
  148. package/dist/server/adapters/cloudflare.js.map +1 -0
  149. package/dist/server/adapters/index.d.ts +10 -0
  150. package/dist/server/adapters/index.d.ts.map +1 -0
  151. package/dist/server/adapters/index.js +24 -0
  152. package/dist/server/adapters/index.js.map +1 -0
  153. package/dist/server/adapters/runtime-node.d.ts +16 -0
  154. package/dist/server/adapters/runtime-node.d.ts.map +1 -0
  155. package/dist/server/adapters/runtime-node.js +29 -0
  156. package/dist/server/adapters/runtime-node.js.map +1 -0
  157. package/dist/server/adapters/runtime.d.ts +42 -0
  158. package/dist/server/adapters/runtime.d.ts.map +1 -0
  159. package/dist/server/adapters/runtime.js +115 -0
  160. package/dist/server/adapters/runtime.js.map +1 -0
  161. package/dist/server/adapters/shared.d.ts +66 -0
  162. package/dist/server/adapters/shared.d.ts.map +1 -0
  163. package/dist/server/adapters/shared.js +124 -0
  164. package/dist/server/adapters/shared.js.map +1 -0
  165. package/dist/server/adapters/static.d.ts +9 -0
  166. package/dist/server/adapters/static.d.ts.map +1 -0
  167. package/dist/server/adapters/static.js +70 -0
  168. package/dist/server/adapters/static.js.map +1 -0
  169. package/dist/server/adapters/vercel.d.ts +43 -0
  170. package/dist/server/adapters/vercel.d.ts.map +1 -0
  171. package/dist/server/adapters/vercel.js +119 -0
  172. package/dist/server/adapters/vercel.js.map +1 -0
  173. package/dist/server/bun-jsx.d.ts +18 -0
  174. package/dist/server/bun-jsx.d.ts.map +1 -0
  175. package/dist/server/bun-jsx.js +56 -0
  176. package/dist/server/bun-jsx.js.map +1 -0
  177. package/dist/server/cache-route.d.ts +38 -0
  178. package/dist/server/cache-route.d.ts.map +1 -0
  179. package/dist/server/cache-route.js +109 -0
  180. package/dist/server/cache-route.js.map +1 -0
  181. package/dist/server/cache.d.ts +131 -0
  182. package/dist/server/cache.d.ts.map +1 -0
  183. package/dist/server/cache.js +409 -0
  184. package/dist/server/cache.js.map +1 -0
  185. package/dist/server/frames.d.ts +45 -0
  186. package/dist/server/frames.d.ts.map +1 -0
  187. package/dist/server/frames.js +294 -0
  188. package/dist/server/frames.js.map +1 -0
  189. package/dist/server/http.d.ts +80 -0
  190. package/dist/server/http.d.ts.map +1 -0
  191. package/dist/server/http.js +453 -0
  192. package/dist/server/http.js.map +1 -0
  193. package/dist/server/jsx-dev-runtime.d.ts +5 -0
  194. package/dist/server/jsx-dev-runtime.d.ts.map +1 -0
  195. package/dist/server/jsx-dev-runtime.js +6 -0
  196. package/dist/server/jsx-dev-runtime.js.map +1 -0
  197. package/dist/server/jsx-runtime.d.ts +10 -0
  198. package/dist/server/jsx-runtime.d.ts.map +1 -0
  199. package/dist/server/jsx-runtime.js +11 -0
  200. package/dist/server/jsx-runtime.js.map +1 -0
  201. package/dist/server/live-app.d.ts +87 -0
  202. package/dist/server/live-app.d.ts.map +1 -0
  203. package/dist/server/live-app.js +307 -0
  204. package/dist/server/live-app.js.map +1 -0
  205. package/dist/server/live-build.d.ts +70 -0
  206. package/dist/server/live-build.d.ts.map +1 -0
  207. package/dist/server/live-build.js +222 -0
  208. package/dist/server/live-build.js.map +1 -0
  209. package/dist/server/live-dev.d.ts +62 -0
  210. package/dist/server/live-dev.d.ts.map +1 -0
  211. package/dist/server/live-dev.js +313 -0
  212. package/dist/server/live-dev.js.map +1 -0
  213. package/dist/server/live-manifest.d.ts +23 -0
  214. package/dist/server/live-manifest.d.ts.map +1 -0
  215. package/dist/server/live-manifest.js +7 -0
  216. package/dist/server/live-manifest.js.map +1 -0
  217. package/dist/server/live-prod.d.ts +30 -0
  218. package/dist/server/live-prod.d.ts.map +1 -0
  219. package/dist/server/live-prod.js +97 -0
  220. package/dist/server/live-prod.js.map +1 -0
  221. package/dist/server/live-routes.d.ts +72 -0
  222. package/dist/server/live-routes.d.ts.map +1 -0
  223. package/dist/server/live-routes.js +274 -0
  224. package/dist/server/live-routes.js.map +1 -0
  225. package/dist/server/live-web.d.ts +16 -0
  226. package/dist/server/live-web.d.ts.map +1 -0
  227. package/dist/server/live-web.js +159 -0
  228. package/dist/server/live-web.js.map +1 -0
  229. package/dist/server/live.d.ts +138 -0
  230. package/dist/server/live.d.ts.map +1 -0
  231. package/dist/server/live.js +542 -0
  232. package/dist/server/live.js.map +1 -0
  233. package/dist/server/middleware-node.d.ts +48 -0
  234. package/dist/server/middleware-node.d.ts.map +1 -0
  235. package/dist/server/middleware-node.js +167 -0
  236. package/dist/server/middleware-node.js.map +1 -0
  237. package/dist/server/middleware-runtime.d.ts +97 -0
  238. package/dist/server/middleware-runtime.d.ts.map +1 -0
  239. package/dist/server/middleware-runtime.js +447 -0
  240. package/dist/server/middleware-runtime.js.map +1 -0
  241. package/dist/server/middleware.d.ts +145 -0
  242. package/dist/server/middleware.d.ts.map +1 -0
  243. package/dist/server/middleware.js +365 -0
  244. package/dist/server/middleware.js.map +1 -0
  245. package/dist/server/ppr.d.ts +22 -0
  246. package/dist/server/ppr.d.ts.map +1 -0
  247. package/dist/server/ppr.js +58 -0
  248. package/dist/server/ppr.js.map +1 -0
  249. package/dist/server/scan.d.ts +48 -0
  250. package/dist/server/scan.d.ts.map +1 -0
  251. package/dist/server/scan.js +161 -0
  252. package/dist/server/scan.js.map +1 -0
  253. package/dist/server/server-components.d.ts +65 -0
  254. package/dist/server/server-components.d.ts.map +1 -0
  255. package/dist/server/server-components.js +215 -0
  256. package/dist/server/server-components.js.map +1 -0
  257. package/dist/terminal.d.ts +5 -0
  258. package/dist/terminal.d.ts.map +1 -0
  259. package/dist/terminal.js +5 -0
  260. package/dist/terminal.js.map +1 -0
  261. package/dist/testing.d.ts +5 -0
  262. package/dist/testing.d.ts.map +1 -0
  263. package/dist/testing.js +5 -0
  264. package/dist/testing.js.map +1 -0
  265. package/dist/vite-plugin.d.ts +51 -0
  266. package/dist/vite-plugin.d.ts.map +1 -0
  267. package/dist/vite-plugin.js +403 -0
  268. package/dist/vite-plugin.js.map +1 -0
  269. package/package.json +130 -4
  270. package/src/cli/build.ts +182 -0
  271. package/src/cli/config.ts +72 -0
  272. package/src/cli/dev.ts +54 -0
  273. package/src/cli/index.ts +185 -0
  274. package/src/cli/run.ts +124 -0
  275. package/src/cli/start.ts +194 -0
  276. package/src/cli/ui.ts +93 -0
  277. package/src/client.tsx +122 -0
  278. package/src/config.ts +155 -0
  279. package/src/context.ts +69 -0
  280. package/src/dev-errors.ts +167 -0
  281. package/src/devtools.tsx +419 -0
  282. package/src/frame-mirror.ts +145 -0
  283. package/src/frame-protocol.ts +435 -0
  284. package/src/history.ts +201 -0
  285. package/src/index.ts +9 -0
  286. package/src/link.tsx +108 -0
  287. package/src/live-player.ts +449 -0
  288. package/src/live-protocol.ts +317 -0
  289. package/src/live-stub.tsx +32 -0
  290. package/src/mcp/cli.ts +188 -0
  291. package/src/mcp/frame.ts +224 -0
  292. package/src/mcp/index.ts +15 -0
  293. package/src/mcp/inspect.ts +445 -0
  294. package/src/mcp/keys.ts +86 -0
  295. package/src/mcp/logs.ts +225 -0
  296. package/src/mcp/server.ts +595 -0
  297. package/src/mcp/session.ts +821 -0
  298. package/src/navigation-errors.ts +60 -0
  299. package/src/navigation.ts +44 -0
  300. package/src/router.tsx +497 -0
  301. package/src/routing.ts +344 -0
  302. package/src/server/adapters/build-step.ts +135 -0
  303. package/src/server/adapters/bun.ts +92 -0
  304. package/src/server/adapters/bundle.ts +97 -0
  305. package/src/server/adapters/cloudflare.ts +111 -0
  306. package/src/server/adapters/index.ts +29 -0
  307. package/src/server/adapters/runtime-node.ts +34 -0
  308. package/src/server/adapters/runtime.ts +145 -0
  309. package/src/server/adapters/shared.ts +159 -0
  310. package/src/server/adapters/static.ts +68 -0
  311. package/src/server/adapters/vercel.ts +149 -0
  312. package/src/server/bun-jsx.ts +65 -0
  313. package/src/server/cache-route.ts +139 -0
  314. package/src/server/cache.ts +498 -0
  315. package/src/server/frames.ts +326 -0
  316. package/src/server/http.ts +467 -0
  317. package/src/server/jsx-dev-runtime.ts +9 -0
  318. package/src/server/jsx-runtime.ts +13 -0
  319. package/src/server/live-app.tsx +374 -0
  320. package/src/server/live-build.ts +258 -0
  321. package/src/server/live-dev.ts +357 -0
  322. package/src/server/live-manifest.ts +25 -0
  323. package/src/server/live-prod.ts +120 -0
  324. package/src/server/live-routes.ts +298 -0
  325. package/src/server/live-web.ts +170 -0
  326. package/src/server/live.ts +551 -0
  327. package/src/server/middleware-node.ts +168 -0
  328. package/src/server/middleware-runtime.ts +478 -0
  329. package/src/server/middleware.ts +451 -0
  330. package/src/server/ppr.ts +62 -0
  331. package/src/server/scan.ts +190 -0
  332. package/src/server/server-components.ts +224 -0
  333. package/src/terminal.ts +5 -0
  334. package/src/testing.ts +5 -0
  335. package/src/vite-plugin.ts +413 -0
package/src/history.ts ADDED
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Router histories: `window.history` (pushState / replaceState / popstate)
3
+ * and an in-memory history for tests and embedded previews.
4
+ */
5
+
6
+ import { normalizeBasePath } from "./config";
7
+
8
+ export interface RouterLocation {
9
+ pathname: string;
10
+ /** Including the leading `?` (or `""`). */
11
+ search: string;
12
+ /** Including the leading `#` (or `""`). */
13
+ hash: string;
14
+ /** `pathname + search + hash`. */
15
+ href: string;
16
+ /** Unique per history entry. */
17
+ key: string;
18
+ }
19
+
20
+ export type HistoryAction = "push" | "replace" | "pop";
21
+
22
+ export interface RouterHistory {
23
+ readonly location: RouterLocation;
24
+ readonly action: HistoryAction;
25
+ push(href: string, state?: unknown): void;
26
+ replace(href: string, state?: unknown): void;
27
+ go(delta: number): void;
28
+ back(): void;
29
+ forward(): void;
30
+ /** Subscribe to location changes (returns an unsubscribe function). */
31
+ listen(listener: (location: RouterLocation, action: HistoryAction) => void): () => void;
32
+ /** Stable getter for `useSyncExternalStore`. */
33
+ getLocation(): RouterLocation;
34
+ /** Turn an app href into a document URL (applies `basePath`). */
35
+ createHref(href: string): string;
36
+ dispose?(): void;
37
+ }
38
+
39
+ let keyCounter = 0;
40
+ const nextKey = (): string => (++keyCounter).toString(36);
41
+
42
+ /** Parse an app-relative href against the current pathname. */
43
+ export function parseHref(href: string, from = "/"): Omit<RouterLocation, "key"> {
44
+ const url = new URL(href, `http://text.local${from.startsWith("/") ? from : `/${from}`}`);
45
+ let pathname = url.pathname.replace(/\/{2,}/g, "/");
46
+ if (pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
47
+ const search = url.search === "?" ? "" : url.search;
48
+ return { pathname, search, hash: url.hash, href: pathname + search + url.hash };
49
+ }
50
+
51
+ /** `true` for absolute URLs on another origin (`https://…`, `mailto:`). */
52
+ export function isExternalHref(href: string, origin?: string): boolean {
53
+ if (!/^[a-z][a-z0-9+.-]*:/i.test(href) && !href.startsWith("//")) return false;
54
+ if (origin === undefined) return true;
55
+ try {
56
+ return new URL(href, origin).origin !== origin;
57
+ } catch {
58
+ return true;
59
+ }
60
+ }
61
+
62
+ function makeLocation(href: string, from: string): RouterLocation {
63
+ return { ...parseHref(href, from), key: nextKey() };
64
+ }
65
+
66
+ class Emitter {
67
+ private readonly listeners = new Set<(location: RouterLocation, action: HistoryAction) => void>();
68
+ listen(listener: (location: RouterLocation, action: HistoryAction) => void): () => void {
69
+ this.listeners.add(listener);
70
+ return () => this.listeners.delete(listener);
71
+ }
72
+ emit(location: RouterLocation, action: HistoryAction): void {
73
+ for (const l of [...this.listeners]) l(location, action);
74
+ }
75
+ }
76
+
77
+ export interface MemoryHistory extends RouterHistory {
78
+ readonly entries: readonly RouterLocation[];
79
+ readonly index: number;
80
+ }
81
+
82
+ export interface MemoryHistoryOptions {
83
+ initialEntries?: string[];
84
+ initialIndex?: number;
85
+ }
86
+
87
+ /** An in-memory history: full App Router navigation without touching `window.location`. */
88
+ export function createMemoryHistory(initial: string | MemoryHistoryOptions = "/"): MemoryHistory {
89
+ const opts: MemoryHistoryOptions = typeof initial === "string" ? { initialEntries: [initial] } : initial;
90
+ const raw = opts.initialEntries && opts.initialEntries.length > 0 ? opts.initialEntries : ["/"];
91
+ let entries: RouterLocation[] = raw.map((h) => makeLocation(h, "/"));
92
+ let index = Math.max(0, Math.min(entries.length - 1, opts.initialIndex ?? entries.length - 1));
93
+ let action: HistoryAction = "pop";
94
+ const emitter = new Emitter();
95
+ const current = (): RouterLocation => entries[index] as RouterLocation;
96
+ const go = (delta: number): void => {
97
+ const next = Math.max(0, Math.min(entries.length - 1, index + delta));
98
+ if (next === index) return;
99
+ index = next;
100
+ action = "pop";
101
+ emitter.emit(current(), action);
102
+ };
103
+
104
+ return {
105
+ get location() {
106
+ return current();
107
+ },
108
+ get action() {
109
+ return action;
110
+ },
111
+ get entries() {
112
+ return entries;
113
+ },
114
+ get index() {
115
+ return index;
116
+ },
117
+ getLocation: () => current(),
118
+ createHref: (href) => parseHref(href, current().pathname).href,
119
+ push(href) {
120
+ const loc = makeLocation(href, current().pathname);
121
+ entries = [...entries.slice(0, index + 1), loc];
122
+ index = entries.length - 1;
123
+ action = "push";
124
+ emitter.emit(loc, action);
125
+ },
126
+ replace(href) {
127
+ const loc = makeLocation(href, current().pathname);
128
+ entries = entries.slice();
129
+ entries[index] = loc;
130
+ action = "replace";
131
+ emitter.emit(loc, action);
132
+ },
133
+ go,
134
+ back: () => go(-1),
135
+ forward: () => go(1),
136
+ listen: (l) => emitter.listen(l),
137
+ };
138
+ }
139
+
140
+ export interface BrowserHistoryOptions {
141
+ /** Mount point, e.g. `"/console"`. */
142
+ basePath?: string;
143
+ window?: Window;
144
+ }
145
+
146
+ /** Sync the App Router with `window.location` and the browser Back / Forward buttons. */
147
+ export function createBrowserHistory(options: BrowserHistoryOptions = {}): RouterHistory {
148
+ const win = options.window ?? (typeof window !== "undefined" ? window : undefined);
149
+ if (!win) return createMemoryHistory("/");
150
+ const base = normalizeBasePath(options.basePath);
151
+ const emitter = new Emitter();
152
+ let action: HistoryAction = "pop";
153
+
154
+ const strip = (pathname: string): string => {
155
+ if (base && (pathname === base || pathname.startsWith(`${base}/`))) return pathname.slice(base.length) || "/";
156
+ return pathname;
157
+ };
158
+ const read = (): RouterLocation => {
159
+ const l = win.location;
160
+ const state = win.history.state as { __textjs?: string } | null;
161
+ return { ...parseHref(strip(l.pathname) + l.search + l.hash, "/"), key: state?.__textjs ?? "default" };
162
+ };
163
+ let current = read();
164
+ const createHref = (href: string): string => base + parseHref(href, current.pathname).href;
165
+
166
+ const onPop = (): void => {
167
+ current = read();
168
+ action = "pop";
169
+ emitter.emit(current, action);
170
+ };
171
+ win.addEventListener("popstate", onPop);
172
+
173
+ const write = (href: string, state: unknown, kind: "push" | "replace"): void => {
174
+ const key = nextKey();
175
+ const data = { ...(typeof state === "object" && state !== null ? state : {}), __textjs: key };
176
+ const url = createHref(href);
177
+ if (kind === "push") win.history.pushState(data, "", url);
178
+ else win.history.replaceState(data, "", url);
179
+ current = { ...parseHref(href, current.pathname), key };
180
+ action = kind;
181
+ emitter.emit(current, action);
182
+ };
183
+
184
+ return {
185
+ get location() {
186
+ return current;
187
+ },
188
+ get action() {
189
+ return action;
190
+ },
191
+ getLocation: () => current,
192
+ createHref,
193
+ push: (href, state) => write(href, state, "push"),
194
+ replace: (href, state) => write(href, state, "replace"),
195
+ go: (delta) => win.history.go(delta),
196
+ back: () => win.history.back(),
197
+ forward: () => win.history.forward(),
198
+ listen: (l) => emitter.listen(l),
199
+ dispose: () => win.removeEventListener("popstate", onPop),
200
+ };
201
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * ▼ Text.js — The Console Framework for the Web.
3
+ */
4
+
5
+ export * from "@textjs/react";
6
+ export { VERSION, ENGINE_NAME, BRAND_MARK } from "@textjs/core";
7
+ export { defineConfig, type TextConfig, type Metadata, type ViewportConfig, type Rewrite, type ProxyTarget } from "./config";
8
+ export { useAppTheme, type AppShellState } from "./context";
9
+ export type { PageProps, LayoutProps, ErrorComponentProps } from "./router";
package/src/link.tsx ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * `@textjs/app/link` — focusable client-side navigation for console apps.
3
+ */
4
+
5
+ import { useRef, type ReactNode } from "react";
6
+ import { Text, useFocus, useInput, useTheme, type TextProps } from "@textjs/react";
7
+ import { useOptionalRouterState } from "./context";
8
+ import { parseHref } from "./history";
9
+
10
+ export interface LinkProps extends Omit<TextProps, "children" | "allowLink"> {
11
+ href: string;
12
+ /** Replace the current history entry instead of pushing. */
13
+ replace?: boolean;
14
+ /** Include in Tab order; `Enter` / `Space` navigate while focused (default `true`). */
15
+ focusable?: boolean;
16
+ autoFocus?: boolean;
17
+ /** Focus id for `useFocusManager().focus(id)`. */
18
+ id?: string;
19
+ /** Preload lazy route modules on mount (default `true`). */
20
+ prefetch?: boolean;
21
+ /** Single key that follows the link from anywhere, e.g. `"1"`. */
22
+ shortcut?: string;
23
+ /** Class names applied when `href` matches the current pathname. */
24
+ activeClassName?: string;
25
+ /** Called before navigating. */
26
+ onNavigate?: () => void;
27
+ children?: ReactNode;
28
+ }
29
+
30
+ /**
31
+ * ```tsx
32
+ * <Link href="/deployments">Deployments</Link>
33
+ * ```
34
+ */
35
+ export function Link({
36
+ href,
37
+ replace = false,
38
+ focusable = true,
39
+ autoFocus,
40
+ id,
41
+ prefetch = true,
42
+ shortcut,
43
+ activeClassName,
44
+ onNavigate,
45
+ className,
46
+ children,
47
+ ...textProps
48
+ }: LinkProps): ReactNode {
49
+ const theme = useTheme();
50
+ const state = useOptionalRouterState();
51
+ const focus = useFocus({ id, autoFocus, isActive: focusable });
52
+ const latest = useRef({ href, replace, onNavigate, state });
53
+ latest.current = { href, replace, onNavigate, state };
54
+ const prefetched = useRef(false);
55
+
56
+ if (prefetch && state && !prefetched.current) {
57
+ prefetched.current = true;
58
+ queueMicrotask(() => state.router.prefetch(href));
59
+ }
60
+
61
+ const navigate = (): void => {
62
+ const { href: to, replace: rep, onNavigate: cb, state: s } = latest.current;
63
+ cb?.();
64
+ if (s) {
65
+ if (rep) s.router.replace(to);
66
+ else s.router.push(to);
67
+ } else if (typeof window !== "undefined") window.location.assign(to);
68
+ };
69
+
70
+ useInput(
71
+ (e) => {
72
+ if (e.key === "return" || e.key === "space") {
73
+ e.stopPropagation();
74
+ navigate();
75
+ }
76
+ },
77
+ { isActive: focus.isFocused },
78
+ );
79
+
80
+ useInput(
81
+ (e) => {
82
+ if (shortcut && e.input === shortcut && !e.ctrl && !e.meta && !e.alt) {
83
+ e.stopPropagation();
84
+ navigate();
85
+ }
86
+ },
87
+ { isActive: shortcut !== undefined },
88
+ );
89
+
90
+ const active = state !== null && parseHref(href, state.location.pathname).pathname === state.location.pathname;
91
+ const cls = [className, active ? activeClassName : undefined].filter(Boolean).join(" ") || undefined;
92
+
93
+ return (
94
+ <Text
95
+ {...textProps}
96
+ underline={textProps.underline ?? (focus.isFocused || /\bno-underline\b/.test(cls ?? "") ? undefined : true)}
97
+ color={textProps.color ?? (cls ? undefined : "accent")}
98
+ className={cls}
99
+ bg={focus.isFocused ? theme.accent : textProps.bg}
100
+ fg={focus.isFocused ? theme.accentFg : textProps.fg}
101
+ allowLink
102
+ >
103
+ {children ?? href}
104
+ </Text>
105
+ );
106
+ }
107
+
108
+ export default Link;