@alchemy.run/sigil 0.0.0-alpha.4 → 0.0.0-alpha.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 (138) hide show
  1. package/README.md +21 -9
  2. package/THIRD_PARTY_NOTICES.md +39 -1
  3. package/dist/Text-BobFKi74.d.ts +452 -0
  4. package/dist/ansi.d.ts +122 -131
  5. package/dist/ansi.js +86 -6
  6. package/dist/capabilities.d.ts +5 -0
  7. package/dist/capabilities.js +3 -0
  8. package/dist/cell-_ZVhbfl0.js +44 -0
  9. package/dist/color-CkbalRqK.js +2 -0
  10. package/dist/color-policy-BMzMwV7Q.d.ts +22 -0
  11. package/dist/color-policy-SVj1pYTA.js +560 -0
  12. package/dist/color-profile-DHhQHY55.js +36 -0
  13. package/dist/color-profile-u0Nhe9Nv.d.ts +97 -0
  14. package/dist/color.d.ts +21 -0
  15. package/dist/color.js +3 -0
  16. package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
  17. package/dist/detect-Bh4yGP6w.d.ts +186 -0
  18. package/dist/detect-BuTXtY6e.js +373 -0
  19. package/dist/env-YVw64yZS.js +9 -0
  20. package/dist/escapes-CB_6CWOE.d.ts +72 -0
  21. package/dist/geometry-BxXOzJgo.d.ts +11 -0
  22. package/dist/index-DZ88EXJv.d.ts +21 -0
  23. package/dist/index.d.ts +143 -498
  24. package/dist/index.js +1026 -2244
  25. package/dist/osc-CCH7xDoS.js +71 -0
  26. package/dist/osc-Cn0fw77g.d.ts +23 -0
  27. package/dist/paint-C19minOS.d.ts +81 -0
  28. package/dist/query-vaIeGOkH.d.ts +152 -0
  29. package/dist/router.d.ts +392 -0
  30. package/dist/router.js +709 -0
  31. package/dist/sample-Cqw1bjUL.js +445 -0
  32. package/dist/screen-BOSLQ8fF.d.ts +49 -0
  33. package/dist/screen-CiPytswf.js +342 -0
  34. package/dist/screen.d.ts +5 -0
  35. package/dist/screen.js +5 -0
  36. package/dist/semantic-text-style-DIMzC7xt.js +91 -0
  37. package/dist/serialize-BTkAZgw1.js +79 -0
  38. package/dist/session-aZr9O8h3.js +665 -0
  39. package/dist/sgr-BhwaWAJB.js +246 -0
  40. package/dist/store-CgrG9K4y.d.ts +72 -0
  41. package/dist/string-width-CijQwpIk.js +69 -0
  42. package/dist/strip-BvU4toXG.js +6 -0
  43. package/dist/terminal.d.ts +118 -0
  44. package/dist/terminal.js +2 -0
  45. package/dist/tokenize-AjqbvtiT.js +1242 -0
  46. package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
  47. package/dist/truncate-D31fhU6i.js +562 -0
  48. package/dist/use-focus-BzqAJi0n.js +1337 -0
  49. package/package.json +41 -9
  50. package/src/ansi/chalk.ts +5 -3
  51. package/src/ansi/escapes.ts +14 -0
  52. package/src/ansi/graphemes.ts +8 -0
  53. package/src/ansi/hyperlink.ts +44 -0
  54. package/src/ansi/index.ts +3 -1
  55. package/src/ansi/osc.ts +77 -0
  56. package/src/ansi/tokenize.ts +3 -4
  57. package/src/capabilities/color-policy.ts +34 -0
  58. package/src/capabilities/detect.ts +594 -0
  59. package/src/capabilities/index.ts +37 -0
  60. package/src/capabilities/query.ts +657 -0
  61. package/src/capabilities/store.ts +379 -0
  62. package/src/color/index.ts +3 -0
  63. package/src/color/paint.ts +169 -0
  64. package/src/color/palette.ts +48 -0
  65. package/src/color/sample.ts +323 -0
  66. package/src/color.ts +1 -0
  67. package/src/components/AnsiText.tsx +42 -0
  68. package/src/components/App.tsx +98 -10
  69. package/src/components/BackgroundContext.ts +2 -3
  70. package/src/components/Box.tsx +0 -8
  71. package/src/components/CursorContext.ts +1 -1
  72. package/src/components/Hyperlink.tsx +56 -0
  73. package/src/components/TerminalOscContext.ts +25 -0
  74. package/src/components/Text.tsx +22 -45
  75. package/src/components/Transform.tsx +1 -1
  76. package/src/dom.ts +11 -2
  77. package/src/global.d.ts +3 -0
  78. package/src/hooks/use-capabilities.ts +73 -0
  79. package/src/hooks/use-cursor.ts +2 -2
  80. package/src/hooks/use-terminal-osc.ts +59 -0
  81. package/src/index.ts +45 -1
  82. package/src/ink.tsx +213 -153
  83. package/src/{render-node-to-output.ts → paint-tree.ts} +75 -48
  84. package/src/reconciler.ts +24 -3
  85. package/src/render-background.ts +36 -15
  86. package/src/render-border.ts +94 -61
  87. package/src/render-frame.ts +83 -0
  88. package/src/render-to-string.ts +21 -6
  89. package/src/render.ts +15 -6
  90. package/src/router/components.tsx +343 -0
  91. package/src/router/context.ts +41 -0
  92. package/src/router/history.ts +194 -0
  93. package/src/router/hooks.tsx +391 -0
  94. package/src/router/index.ts +34 -0
  95. package/src/router/matcher.ts +571 -0
  96. package/src/screen/ansi.ts +184 -0
  97. package/src/screen/canvas.ts +160 -0
  98. package/src/screen/cell.ts +138 -0
  99. package/src/screen/color-profile.ts +47 -0
  100. package/src/screen/geometry.ts +9 -0
  101. package/src/screen/index.ts +6 -0
  102. package/src/screen/screen.ts +305 -0
  103. package/src/screen/serialize.ts +129 -0
  104. package/src/screen.ts +1 -0
  105. package/src/semantic-text-style.ts +118 -0
  106. package/src/squash-text-nodes.ts +2 -5
  107. package/src/structured-text.ts +325 -0
  108. package/src/styles.ts +19 -14
  109. package/src/terminal/index.ts +2 -0
  110. package/src/terminal/inline-presenter.ts +120 -0
  111. package/src/terminal/input.ts +86 -0
  112. package/src/terminal/render-scheduler.ts +37 -0
  113. package/src/terminal/screen-presenter.ts +188 -0
  114. package/src/terminal/session.ts +407 -0
  115. package/src/terminal.ts +1 -0
  116. package/src/testing/browser.ts +588 -0
  117. package/src/testing/emulators.ts +205 -0
  118. package/src/testing/explorer-app/index.html +12 -0
  119. package/src/testing/explorer-app/main.ts +381 -0
  120. package/src/testing/explorer-app/style.css +194 -0
  121. package/src/testing/explorer-app/tsconfig.json +15 -0
  122. package/src/testing/explorer-app/vite-env.d.ts +1 -0
  123. package/src/testing/index.ts +26 -0
  124. package/src/testing/keys.ts +56 -0
  125. package/src/testing/live.ts +85 -0
  126. package/src/testing/matchers.ts +70 -0
  127. package/src/testing/public.ts +94 -0
  128. package/src/testing/terminal.ts +349 -0
  129. package/src/testing/vitest.ts +157 -0
  130. package/src/transform-adapter.ts +14 -0
  131. package/src/wrap-text.ts +4 -0
  132. package/dist/sgr-CMfEpjSk.d.ts +0 -91
  133. package/dist/truncate-Cr6xVFMa.js +0 -2330
  134. package/src/ansi/supports-color.ts +0 -207
  135. package/src/colorize.ts +0 -60
  136. package/src/log-update.ts +0 -370
  137. package/src/output.ts +0 -308
  138. package/src/renderer.ts +0 -73
package/dist/router.js ADDED
@@ -0,0 +1,709 @@
1
+ import { f as Text, n as useInput, t as useFocus } from "./use-focus-BzqAJi0n.js";
2
+ import { Children, Fragment, createContext, isValidElement, startTransition, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
4
+ //#region src/router/history.ts
5
+ /**
6
+ Splits a path string into its pathname and search parts.
7
+ */
8
+ const parsePath = (path) => {
9
+ const parsedPath = {};
10
+ if (path) {
11
+ const searchIndex = path.indexOf("?");
12
+ if (searchIndex >= 0) {
13
+ parsedPath.search = path.slice(searchIndex);
14
+ path = path.slice(0, searchIndex);
15
+ }
16
+ if (path) parsedPath.pathname = path;
17
+ }
18
+ return parsedPath;
19
+ };
20
+ /**
21
+ Joins a `Path` back into a single string.
22
+ */
23
+ const createPath = ({ pathname = "/", search = "" }) => pathname + (search && search !== "?" ? search.startsWith("?") ? search : `?${search}` : "");
24
+ const createMemoryHistory = ({ initialEntries = ["/"], initialIndex } = {}) => {
25
+ let keyCounter = 0;
26
+ const createKey = () => `k${keyCounter++}`;
27
+ const createLocation = (to, state = null) => {
28
+ const path = typeof to === "string" ? parsePath(to) : to;
29
+ const location = {
30
+ pathname: path.pathname ?? "/",
31
+ search: path.search ?? "",
32
+ state,
33
+ key: createKey()
34
+ };
35
+ if (!location.pathname.startsWith("/")) throw new Error(`Route pathnames must be absolute, but "${location.pathname}" was used to initialize the navigation stack.`);
36
+ return location;
37
+ };
38
+ const entries = initialEntries.map((entry) => createLocation(entry, typeof entry === "string" ? null : entry.state ?? null));
39
+ let index = Math.min(Math.max(initialIndex ?? entries.length - 1, 0), entries.length - 1);
40
+ let action = "POP";
41
+ let listener = null;
42
+ return {
43
+ get index() {
44
+ return index;
45
+ },
46
+ get action() {
47
+ return action;
48
+ },
49
+ get location() {
50
+ return entries[index];
51
+ },
52
+ get canGoBack() {
53
+ return index > 0;
54
+ },
55
+ get canGoForward() {
56
+ return index < entries.length - 1;
57
+ },
58
+ push(to, state) {
59
+ action = "PUSH";
60
+ const nextLocation = createLocation(to, state);
61
+ index += 1;
62
+ entries.splice(index, entries.length, nextLocation);
63
+ listener?.({
64
+ action,
65
+ location: nextLocation,
66
+ delta: 1
67
+ });
68
+ },
69
+ replace(to, state) {
70
+ action = "REPLACE";
71
+ const nextLocation = createLocation(to, state);
72
+ entries[index] = nextLocation;
73
+ listener?.({
74
+ action,
75
+ location: nextLocation,
76
+ delta: 0
77
+ });
78
+ },
79
+ go(delta) {
80
+ action = "POP";
81
+ const nextIndex = Math.min(Math.max(index + delta, 0), entries.length - 1);
82
+ const actualDelta = nextIndex - index;
83
+ index = nextIndex;
84
+ listener?.({
85
+ action,
86
+ location: entries[index],
87
+ delta: actualDelta
88
+ });
89
+ },
90
+ listen(newListener) {
91
+ if (listener) throw new Error("A memory history only supports one listener at a time.");
92
+ listener = newListener;
93
+ return () => {
94
+ listener = null;
95
+ };
96
+ }
97
+ };
98
+ };
99
+ //#endregion
100
+ //#region src/router/matcher.ts
101
+ const joinPaths = (paths) => paths.join("/").replace(/\/\/+/g, "/");
102
+ const normalizePathname = (pathname) => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
103
+ const normalizeSearch = (search) => !search || search === "?" ? "" : search.startsWith("?") ? search : `?${search}`;
104
+ /**
105
+ Matches a set of (possibly nested) routes against a location and returns the
106
+ chain of matches from the root route down to the leaf, or `null` if nothing
107
+ matches.
108
+ */
109
+ function matchRoutes(routes, locationArg) {
110
+ const pathname = (typeof locationArg === "string" ? parsePath(locationArg) : locationArg).pathname ?? "/";
111
+ const branches = flattenRoutes(routes);
112
+ rankRouteBranches(branches);
113
+ let matches = null;
114
+ for (let index = 0; matches == null && index < branches.length; index++) matches = matchRouteBranch(branches[index], pathname);
115
+ return matches;
116
+ }
117
+ function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "", hasParentOptionalSegments = false) {
118
+ const flattenRoute = (route, index, insideOptional, relativePath = route.path ?? "") => {
119
+ const meta = {
120
+ relativePath,
121
+ childrenIndex: index,
122
+ route
123
+ };
124
+ if (meta.relativePath.startsWith("/")) {
125
+ if (!meta.relativePath.startsWith(parentPath)) {
126
+ if (insideOptional) return;
127
+ throw new Error(`Absolute route path "${meta.relativePath}" nested under path "${parentPath}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`);
128
+ }
129
+ meta.relativePath = meta.relativePath.slice(parentPath.length);
130
+ }
131
+ const path = joinPaths([parentPath, meta.relativePath]);
132
+ const routesMeta = parentsMeta.concat(meta);
133
+ if (route.children && route.children.length > 0) {
134
+ if (route.index) throw new Error(`Index routes must not have child routes. Please remove all child routes from route path "${path}".`);
135
+ flattenRoutes(route.children, branches, routesMeta, path, insideOptional);
136
+ }
137
+ if (route.path == null && !route.index) return;
138
+ branches.push({
139
+ path,
140
+ score: computeScore(path, route.index),
141
+ routesMeta
142
+ });
143
+ };
144
+ routes.forEach((route, index) => {
145
+ if (route.path === "" || !route.path?.includes("?")) flattenRoute(route, index, hasParentOptionalSegments);
146
+ else for (const exploded of explodeOptionalSegments(route.path)) flattenRoute(route, index, true, exploded);
147
+ });
148
+ return branches;
149
+ }
150
+ function explodeOptionalSegments(path) {
151
+ const segments = path.split("/");
152
+ if (segments.length === 0) return [];
153
+ const [first = "", ...rest] = segments;
154
+ const isOptional = first.endsWith("?");
155
+ const required = first.replace(/\?$/, "");
156
+ if (rest.length === 0) return isOptional ? [required, ""] : [required];
157
+ const restExploded = explodeOptionalSegments(rest.join("/"));
158
+ const result = [];
159
+ result.push(...restExploded.map((subpath) => subpath === "" ? required : [required, subpath].join("/")));
160
+ if (isOptional) result.push(...restExploded);
161
+ return result.map((exploded) => path.startsWith("/") && exploded === "" ? "/" : exploded);
162
+ }
163
+ const paramRe = /^:[\w-]+$/;
164
+ const dynamicSegmentValue = 3;
165
+ const indexRouteValue = 2;
166
+ const emptySegmentValue = 1;
167
+ const staticSegmentValue = 10;
168
+ const splatPenalty = -2;
169
+ const isSplat = (segment) => segment === "*";
170
+ function computeScore(path, index) {
171
+ const segments = path.split("/");
172
+ let initialScore = segments.length;
173
+ if (segments.some(isSplat)) initialScore += splatPenalty;
174
+ if (index) initialScore += indexRouteValue;
175
+ return segments.filter((segment) => !isSplat(segment)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
176
+ }
177
+ function rankRouteBranches(branches) {
178
+ branches.sort((a, b) => a.score !== b.score ? b.score - a.score : compareIndexes(a.routesMeta.map((meta) => meta.childrenIndex), b.routesMeta.map((meta) => meta.childrenIndex)));
179
+ }
180
+ function compareIndexes(a, b) {
181
+ return a.length === b.length && a.slice(0, -1).every((n, index) => n === b[index]) ? a[a.length - 1] - b[b.length - 1] : 0;
182
+ }
183
+ function matchRouteBranch(branch, pathname) {
184
+ const { routesMeta } = branch;
185
+ const matchedParams = {};
186
+ let matchedPathname = "/";
187
+ const matches = [];
188
+ for (let index = 0; index < routesMeta.length; index++) {
189
+ const meta = routesMeta[index];
190
+ const end = index === routesMeta.length - 1;
191
+ const remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
192
+ const match = matchPath({
193
+ path: meta.relativePath,
194
+ end
195
+ }, remainingPathname);
196
+ if (!match) return null;
197
+ Object.assign(matchedParams, match.params);
198
+ matches.push({
199
+ params: matchedParams,
200
+ pathname: joinPaths([matchedPathname, match.pathname]),
201
+ pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
202
+ route: meta.route
203
+ });
204
+ if (match.pathnameBase !== "/") matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
205
+ }
206
+ return matches;
207
+ }
208
+ /**
209
+ Matches a single path pattern against a pathname. Returns the match with
210
+ extracted params, or `null` if the pattern does not match.
211
+ */
212
+ function matchPath(pattern, pathname) {
213
+ if (typeof pattern === "string") pattern = {
214
+ path: pattern,
215
+ end: true
216
+ };
217
+ const [matcher, compiledParams] = compilePath(pattern.path, pattern.end);
218
+ const match = pathname.match(matcher);
219
+ if (!match) return null;
220
+ const matchedPathname = match[0];
221
+ let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
222
+ const captureGroups = match.slice(1);
223
+ return {
224
+ params: compiledParams.reduce((memo, { paramName, isOptional }, index) => {
225
+ const value = captureGroups[index];
226
+ if (paramName === "*") {
227
+ const splatValue = value || "";
228
+ pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
229
+ }
230
+ memo[paramName] = isOptional && !value ? void 0 : value || "";
231
+ return memo;
232
+ }, {}),
233
+ pathname: matchedPathname,
234
+ pathnameBase,
235
+ pattern
236
+ };
237
+ }
238
+ function compilePath(path, end = true) {
239
+ if (path !== "*" && path.endsWith("*") && !path.endsWith("/*")) throw new Error(`Route path "${path}" is invalid because the \`*\` character must always follow a \`/\` in the pattern. Please change the route path to "${path.replace(/\*$/, "/*")}".`);
240
+ const params = [];
241
+ let regexpSource = "^" + path.replace(/\/*\*?$/, "").replace(/^\/*/, "/").replace(/[\\.*+^${}|()[\]]/g, "\\$&").replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
242
+ params.push({
243
+ paramName,
244
+ isOptional: isOptional != null
245
+ });
246
+ return isOptional ? "/?([^\\/]+)?" : "/([^\\/]+)";
247
+ }).replace(/\/([\w-]+)\?(\/|$)/g, "(/$1)?$2");
248
+ if (path.endsWith("*")) {
249
+ params.push({ paramName: "*" });
250
+ regexpSource += path === "*" || path === "/*" ? "(.*)$" : "(?:\\/(.+)|\\/*)$";
251
+ } else if (end) regexpSource += "\\/*$";
252
+ else if (path !== "" && path !== "/") regexpSource += "(?:(?=\\/|$))";
253
+ return [new RegExp(regexpSource), params];
254
+ }
255
+ /**
256
+ Interpolates params into a route path pattern.
257
+
258
+ ```ts
259
+ generatePath("/users/:id", { id: "42" }); // "/users/42"
260
+ ```
261
+ */
262
+ function generatePath(originalPath, params = {}) {
263
+ const path = originalPath;
264
+ if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) throw new Error(`Route path "${path}" is invalid because the \`*\` character must always follow a \`/\` in the pattern. Please change the route path to "${path.replace(/\*$/, "/*")}".`);
265
+ return (path.startsWith("/") ? "/" : "") + path.split(/\/+/).map((segment, index, array) => {
266
+ if (index === array.length - 1 && segment === "*") return params["*"] ?? "";
267
+ const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
268
+ if (keyMatch) {
269
+ const key = keyMatch[1];
270
+ const optional = keyMatch[2];
271
+ const param = params[key];
272
+ if (optional !== "?" && param == null) throw new Error(`Missing ":${key}" param`);
273
+ return param ?? "";
274
+ }
275
+ return segment.replace(/\?$/g, "");
276
+ }).filter((segment) => !!segment).join("/");
277
+ }
278
+ /**
279
+ Resolves a `To` value against a starting pathname, handling `.` and `..`
280
+ segments.
281
+ */
282
+ function resolvePath(to, fromPathname = "/") {
283
+ const { pathname: toPathname, search = "" } = typeof to === "string" ? parsePath(to) : to;
284
+ return {
285
+ pathname: toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname,
286
+ search: normalizeSearch(search)
287
+ };
288
+ }
289
+ function resolvePathname(relativePath, fromPathname) {
290
+ const segments = fromPathname.replace(/\/+$/, "").split("/");
291
+ for (const segment of relativePath.split("/")) if (segment === "..") {
292
+ if (segments.length > 1) segments.pop();
293
+ } else if (segment !== ".") segments.push(segment);
294
+ return segments.length > 1 ? segments.join("/") : "/";
295
+ }
296
+ function getPathContributingMatches(matches) {
297
+ return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
298
+ }
299
+ function getResolveToMatches(matches) {
300
+ const pathMatches = getPathContributingMatches(matches);
301
+ return pathMatches.map((match, index) => index === pathMatches.length - 1 ? match.pathname : match.pathnameBase);
302
+ }
303
+ function resolveTo(toArg, routePathnames, locationPathname) {
304
+ const to = typeof toArg === "string" ? parsePath(toArg) : { ...toArg };
305
+ if (to.pathname?.includes("?")) throw new Error(`Cannot include a '?' character in a manually specified \`to.pathname\` field [${JSON.stringify(to)}]. Please separate it out to the \`to.search\` field.`);
306
+ const isEmptyPath = toArg === "" || to.pathname === "";
307
+ const toPathname = isEmptyPath ? "/" : to.pathname;
308
+ let from;
309
+ if (toPathname == null) from = locationPathname;
310
+ else {
311
+ let routePathnameIndex = routePathnames.length - 1;
312
+ if (toPathname.startsWith("..")) {
313
+ const toSegments = toPathname.split("/");
314
+ while (toSegments[0] === "..") {
315
+ toSegments.shift();
316
+ routePathnameIndex -= 1;
317
+ }
318
+ to.pathname = toSegments.join("/");
319
+ }
320
+ from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
321
+ }
322
+ const path = resolvePath(to, from);
323
+ const hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
324
+ const hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
325
+ if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) path.pathname += "/";
326
+ return path;
327
+ }
328
+ //#endregion
329
+ //#region src/router/context.ts
330
+ const NavigationContext = createContext(null);
331
+ const LocationContext = createContext(null);
332
+ const RouteContext = createContext({
333
+ outlet: null,
334
+ matches: []
335
+ });
336
+ const OutletContext = createContext(null);
337
+ //#endregion
338
+ //#region src/router/hooks.tsx
339
+ /** @jsxImportSource react */
340
+ const warned = /* @__PURE__ */ new Set();
341
+ const warnOnce = (key, message) => {
342
+ if (!warned.has(key)) {
343
+ warned.add(key);
344
+ console.warn(message);
345
+ }
346
+ };
347
+ /**
348
+ Returns `true` when rendered inside a `<MemoryRouter>`. Useful for components
349
+ that optionally integrate with routing.
350
+ */
351
+ const useInRouterContext = () => useContext(LocationContext) != null;
352
+ /**
353
+ Returns the current `Location`.
354
+ */
355
+ const useLocation = () => {
356
+ const locationContext = useContext(LocationContext);
357
+ if (!locationContext) throw new Error("useLocation() may be used only in the context of a <MemoryRouter> component.");
358
+ return locationContext.location;
359
+ };
360
+ /**
361
+ Returns the type of navigation that produced the current location: `"POP"`,
362
+ `"PUSH"`, or `"REPLACE"`.
363
+ */
364
+ const useNavigationType = () => {
365
+ const locationContext = useContext(LocationContext);
366
+ if (!locationContext) throw new Error("useNavigationType() may be used only in the context of a <MemoryRouter> component.");
367
+ return locationContext.navigationType;
368
+ };
369
+ const useNavigationContext = (hookName) => {
370
+ const navigationContext = useContext(NavigationContext);
371
+ if (!navigationContext) throw new Error(`${hookName} may be used only in the context of a <MemoryRouter> component.`);
372
+ return navigationContext;
373
+ };
374
+ /**
375
+ Returns a stable function for imperative navigation.
376
+ */
377
+ const useNavigate = () => {
378
+ const { navigator } = useNavigationContext("useNavigate()");
379
+ const { matches } = useContext(RouteContext);
380
+ const { pathname: locationPathname } = useLocation();
381
+ const routePathnamesJson = JSON.stringify(getResolveToMatches(matches));
382
+ const activeRef = useRef(false);
383
+ useLayoutEffect(() => {
384
+ activeRef.current = true;
385
+ });
386
+ return useCallback((to, options = {}) => {
387
+ if (!activeRef.current) return;
388
+ if (typeof to === "number") {
389
+ navigator.go(to);
390
+ return;
391
+ }
392
+ const path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname);
393
+ (options.replace ? navigator.replace : navigator.push)(path, options.state);
394
+ }, [
395
+ navigator,
396
+ routePathnamesJson,
397
+ locationPathname
398
+ ]);
399
+ };
400
+ /**
401
+ Returns whether the navigation stack has entries behind/ahead of the current
402
+ one — i.e. whether `navigate(-1)` / `navigate(1)` will move anywhere. Handy
403
+ for "Esc goes back, unless at root" bindings.
404
+ */
405
+ const useNavigationStack = () => {
406
+ const { navigator } = useNavigationContext("useNavigationStack()");
407
+ useLocation();
408
+ return {
409
+ canGoBack: navigator.canGoBack(),
410
+ canGoForward: navigator.canGoForward()
411
+ };
412
+ };
413
+ /**
414
+ Returns the params from all dynamic segments matched by the current route and
415
+ its ancestors.
416
+ */
417
+ const useParams = () => {
418
+ const { matches } = useContext(RouteContext);
419
+ const routeMatch = matches[matches.length - 1];
420
+ return routeMatch ? routeMatch.params : {};
421
+ };
422
+ /**
423
+ Matches a path pattern against the current location's pathname. Returns the
424
+ match (with params) or `null`.
425
+ */
426
+ const useMatch = (pattern) => {
427
+ const { pathname } = useLocation();
428
+ return matchPath(pattern, pathname);
429
+ };
430
+ /**
431
+ Resolves a `To` value against the current route, exactly as `useNavigate`
432
+ would. Useful for building navigation UI.
433
+ */
434
+ const useResolvedPath = (to) => {
435
+ const { matches } = useContext(RouteContext);
436
+ const { pathname: locationPathname } = useLocation();
437
+ return resolveTo(to, getResolveToMatches(matches), locationPathname);
438
+ };
439
+ /**
440
+ Returns the element for the child route at this level of the route hierarchy,
441
+ or `null` if there is none. Used internally by `<Outlet>`.
442
+ */
443
+ const useOutlet = (context) => {
444
+ const { outlet } = useContext(RouteContext);
445
+ if (outlet) return /* @__PURE__ */ jsx(OutletContext.Provider, {
446
+ value: context,
447
+ children: outlet
448
+ });
449
+ return outlet;
450
+ };
451
+ /**
452
+ Returns the value passed to the nearest parent `<Outlet context={...}>`.
453
+ */
454
+ const useOutletContext = () => useContext(OutletContext);
455
+ /**
456
+ Creates a `URLSearchParams` from common initializer shapes, including
457
+ `{ key: ["a", "b"] }` for repeated keys.
458
+ */
459
+ const createSearchParams = (init = "") => new URLSearchParams(typeof init === "string" || Array.isArray(init) || init instanceof URLSearchParams ? init : Object.keys(init).flatMap((key) => {
460
+ const value = init[key];
461
+ return Array.isArray(value) ? value.map((v) => [key, v]) : [[key, value]];
462
+ }));
463
+ /**
464
+ Returns the current location's search params and a setter that navigates to
465
+ the same pathname with the new params.
466
+ */
467
+ const useSearchParams = (defaultInit) => {
468
+ const defaultSearchParamsRef = useRef(createSearchParams(defaultInit));
469
+ const hasSetSearchParamsRef = useRef(false);
470
+ const location = useLocation();
471
+ const searchParams = useMemo(() => {
472
+ const params = createSearchParams(location.search);
473
+ if (!hasSetSearchParamsRef.current) {
474
+ for (const key of defaultSearchParamsRef.current.keys()) if (!params.has(key)) for (const value of defaultSearchParamsRef.current.getAll(key)) params.append(key, value);
475
+ }
476
+ return params;
477
+ }, [location.search]);
478
+ const navigate = useNavigate();
479
+ return [searchParams, useCallback((nextInit, navigateOptions) => {
480
+ const newSearchParams = createSearchParams(typeof nextInit === "function" ? nextInit(new URLSearchParams(searchParams)) : nextInit);
481
+ hasSetSearchParamsRef.current = true;
482
+ navigate(`?${newSearchParams.toString()}`, navigateOptions);
483
+ }, [navigate, searchParams])];
484
+ };
485
+ /**
486
+ Matches a set of route objects against the current location (or an override)
487
+ and returns the rendered element tree. The plain-object alternative to
488
+ `<Routes>`.
489
+ */
490
+ const useRoutes = (routes, locationArg) => {
491
+ if (!useInRouterContext()) throw new Error("useRoutes() may be used only in the context of a <MemoryRouter> component.");
492
+ const { matches: parentMatches } = useContext(RouteContext);
493
+ const routeMatch = parentMatches[parentMatches.length - 1];
494
+ const parentParams = routeMatch ? routeMatch.params : {};
495
+ const parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
496
+ const parentRoute = routeMatch?.route;
497
+ const locationFromContext = useLocation();
498
+ let location;
499
+ if (locationArg) {
500
+ const parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
501
+ if (parentPathnameBase !== "/" && !parsedLocationArg.pathname?.startsWith(parentPathnameBase)) throw new Error(`When overriding the location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the pathname that was matched by all parent routes. The current pathname base is "${parentPathnameBase}" but pathname "${parsedLocationArg.pathname}" was given in the \`location\` prop.`);
502
+ location = parsedLocationArg;
503
+ } else location = locationFromContext;
504
+ const pathname = location.pathname ?? "/";
505
+ let remainingPathname = pathname;
506
+ if (parentPathnameBase !== "/") {
507
+ const parentSegments = parentPathnameBase.replace(/^\//, "").split("/");
508
+ remainingPathname = `/${pathname.replace(/^\//, "").split("/").slice(parentSegments.length).join("/")}`;
509
+ }
510
+ const matches = matchRoutes(routes, { pathname: remainingPathname });
511
+ const parentPath = parentRoute?.path ?? "";
512
+ if (parentRoute && !parentPath.endsWith("*")) warnOnce(`descendant-routes:${routeMatch.pathname}`, `You rendered descendant <Routes> (or called useRoutes()) at "${routeMatch.pathname}" (under <Route path="${parentPath}">) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. Please change the parent to <Route path="${parentPath === "/" ? "*" : `${parentPath}/*`}">.`);
513
+ if (matches == null && !parentRoute) warnOnce(`no-match:${pathname}${location.search ?? ""}`, `No routes matched location "${pathname}${location.search ?? ""}".`);
514
+ return renderMatches(matches && matches.map((match) => ({
515
+ ...match,
516
+ params: {
517
+ ...parentParams,
518
+ ...match.params
519
+ },
520
+ pathname: joinPaths([parentPathnameBase, match.pathname]),
521
+ pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, match.pathnameBase])
522
+ })), parentMatches);
523
+ };
524
+ function renderMatches(matches, parentMatches) {
525
+ if (matches == null) return null;
526
+ return matches.reduceRight((outlet, match, index) => {
527
+ const matchesUpToHere = parentMatches.concat(matches.slice(0, index + 1));
528
+ return /* @__PURE__ */ jsx(RouteContext.Provider, {
529
+ value: {
530
+ outlet,
531
+ matches: matchesUpToHere
532
+ },
533
+ children: match.route.element ?? outlet
534
+ });
535
+ }, null);
536
+ }
537
+ //#endregion
538
+ //#region src/router/components.tsx
539
+ /** @jsxImportSource react */
540
+ /**
541
+ The routing container for a Sigil app. Stores the navigation stack in memory —
542
+ routes aren't URLs, they're screen states.
543
+
544
+ ```tsx
545
+ <MemoryRouter>
546
+ <Routes>
547
+ <Route path="/" element={<Home />} />
548
+ <Route path="/settings" element={<Settings />} />
549
+ </Routes>
550
+ </MemoryRouter>
551
+ ```
552
+ */
553
+ function MemoryRouter({ initialEntries, initialIndex, children }) {
554
+ const historyRef = useRef(null);
555
+ historyRef.current ??= createMemoryHistory({
556
+ initialEntries,
557
+ initialIndex
558
+ });
559
+ const history = historyRef.current;
560
+ const [state, setState] = useState({
561
+ action: history.action,
562
+ location: history.location
563
+ });
564
+ useLayoutEffect(() => history.listen(({ action, location }) => {
565
+ startTransition(() => {
566
+ setState({
567
+ action,
568
+ location
569
+ });
570
+ });
571
+ }), [history]);
572
+ const navigator = useMemo(() => ({
573
+ push: (to, historyState) => history.push(to, historyState),
574
+ replace: (to, historyState) => history.replace(to, historyState),
575
+ go: (delta) => history.go(delta),
576
+ canGoBack: () => history.canGoBack,
577
+ canGoForward: () => history.canGoForward
578
+ }), [history]);
579
+ const navigationContext = useMemo(() => ({ navigator }), [navigator]);
580
+ const locationContext = useMemo(() => ({
581
+ location: state.location,
582
+ navigationType: state.action
583
+ }), [state]);
584
+ return /* @__PURE__ */ jsx(NavigationContext.Provider, {
585
+ value: navigationContext,
586
+ children: /* @__PURE__ */ jsx(LocationContext.Provider, {
587
+ value: locationContext,
588
+ children
589
+ })
590
+ });
591
+ }
592
+ /**
593
+ Declares a route. Only valid as a child of `<Routes>` or another `<Route>`.
594
+ */
595
+ function Route(_props) {
596
+ throw new Error("A <Route> is only ever to be used as the child of a <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.");
597
+ }
598
+ function createRoutesFromChildren(children) {
599
+ const routes = [];
600
+ Children.forEach(children, (element) => {
601
+ if (!isValidElement(element)) return;
602
+ if (element.type === Fragment) {
603
+ routes.push(...createRoutesFromChildren(element.props.children));
604
+ return;
605
+ }
606
+ if (element.type !== Route) throw new Error(`[${typeof element.type === "string" ? element.type : element.type.name ?? "unknown"}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>.`);
607
+ const props = element.props;
608
+ const route = {
609
+ path: props.path,
610
+ index: props.index,
611
+ element: props.element
612
+ };
613
+ if (props.children) route.children = createRoutesFromChildren(props.children);
614
+ routes.push(route);
615
+ });
616
+ return routes;
617
+ }
618
+ /**
619
+ Renders the branch of child `<Route>` elements that best matches the current
620
+ location.
621
+ */
622
+ function Routes({ children, location }) {
623
+ return useRoutes(createRoutesFromChildren(children), location);
624
+ }
625
+ /**
626
+ Renders the matching child route of a parent route, or nothing if no child
627
+ matches.
628
+ */
629
+ function Outlet(props) {
630
+ return useOutlet(props.context);
631
+ }
632
+ /**
633
+ Navigates as soon as it renders. The component form of `useNavigate`, for
634
+ declarative redirects:
635
+
636
+ ```tsx
637
+ <Route path="/" element={<Navigate to="/home" replace />} />
638
+ ```
639
+ */
640
+ function Navigate({ to, replace, state }) {
641
+ const navigate = useNavigate();
642
+ const { pathname, search } = useResolvedPath(to);
643
+ useEffect(() => {
644
+ navigate({
645
+ pathname,
646
+ search
647
+ }, {
648
+ replace,
649
+ state
650
+ });
651
+ }, [
652
+ navigate,
653
+ pathname,
654
+ search,
655
+ replace,
656
+ state
657
+ ]);
658
+ return null;
659
+ }
660
+ /**
661
+ A focusable navigation element — the terminal's `<a>` tag. Focus it with
662
+ <kbd>Tab</kbd> and activate it with <kbd>Enter</kbd>. By default the focused
663
+ link renders inverse; pass a function as `children` (or any `Text` props) to
664
+ customize.
665
+
666
+ ```tsx
667
+ <Link to="/settings">Settings</Link>
668
+ ```
669
+ */
670
+ function Link({ to, replace = false, state, autoFocus = false, id, children, ...textProps }) {
671
+ const navigate = useNavigate();
672
+ const path = useResolvedPath(to);
673
+ const { pathname: locationPathname } = useLocation();
674
+ const { isFocused } = useFocus({
675
+ autoFocus,
676
+ id
677
+ });
678
+ const toPathname = normalizePathname(path.pathname);
679
+ const isActive = locationPathname === toPathname || locationPathname.startsWith(toPathname) && locationPathname.charAt(toPathname.length) === "/";
680
+ const activate = useCallback(() => {
681
+ navigate({
682
+ pathname: path.pathname,
683
+ search: path.search
684
+ }, {
685
+ replace,
686
+ state
687
+ });
688
+ }, [
689
+ navigate,
690
+ path.pathname,
691
+ path.search,
692
+ replace,
693
+ state
694
+ ]);
695
+ useInput((_input, key) => {
696
+ if (key.return) activate();
697
+ }, { isActive: isFocused });
698
+ if (typeof children === "function") return /* @__PURE__ */ jsx(Fragment$1, { children: children({
699
+ isFocused,
700
+ isActive
701
+ }) });
702
+ return /* @__PURE__ */ jsx(Text, {
703
+ inverse: isFocused,
704
+ ...textProps,
705
+ children
706
+ });
707
+ }
708
+ //#endregion
709
+ export { Link, MemoryRouter, Navigate, Outlet, Route, Routes, createPath, createSearchParams, generatePath, matchPath, matchRoutes, parsePath, resolvePath, useInRouterContext, useLocation, useMatch, useNavigate, useNavigationStack, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes, useSearchParams };