@tyndall/react 0.0.1

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.
@@ -0,0 +1,30 @@
1
+ import { type Router } from "./router.js";
2
+ type ElementType = string | symbol | ((props: Record<string, unknown>) => unknown);
3
+ export interface ElementLike {
4
+ $$typeof: symbol;
5
+ type: ElementType;
6
+ key: string | null;
7
+ ref: unknown | null;
8
+ props: Record<string, unknown>;
9
+ }
10
+ export interface LinkProps {
11
+ href: string;
12
+ prefetch?: boolean;
13
+ children?: unknown;
14
+ onClick?: (event: unknown) => void;
15
+ onMouseEnter?: (event: unknown) => void;
16
+ onFocus?: (event: unknown) => void;
17
+ }
18
+ export declare const Link: ({ href, prefetch, children, onClick, ...rest }: LinkProps) => ElementLike;
19
+ export interface HeadProps {
20
+ children?: unknown;
21
+ }
22
+ export declare const Head: ({ children }: HeadProps) => ElementLike;
23
+ export interface RouterProviderProps {
24
+ router?: Router;
25
+ initialHref?: string;
26
+ children?: unknown;
27
+ }
28
+ export declare const RouterProvider: ({ router, initialHref, children }: RouterProviderProps) => unknown;
29
+ export {};
30
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAE9E,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC;AAEnF,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAmBD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAmBD,eAAO,MAAM,IAAI,GAAI,gDAAgD,SAAS,KAAG,WA0DhF,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,IAAI,GAAI,cAAc,SAAS,KAAG,WACN,CAAC;AAE1C,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,eAAO,MAAM,cAAc,GAAI,mCAAmC,mBAAmB,KAAG,OAOvF,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { createRouter, getRouter, setRouter } from "./router.js";
2
+ const REACT_ELEMENT = Symbol.for("react.element");
3
+ // Marker component: render nothing, but allow head extraction from its children.
4
+ const HeadMarker = (_props) => null;
5
+ HeadMarker.__hyperHead = true;
6
+ const createElement = (type, props, children) => ({
7
+ $$typeof: REACT_ELEMENT,
8
+ type,
9
+ key: null,
10
+ ref: null,
11
+ props: { ...props, children },
12
+ });
13
+ const shouldHandleClick = (event) => {
14
+ if (event.defaultPrevented) {
15
+ return false;
16
+ }
17
+ if (event.button !== undefined && event.button !== 0) {
18
+ return false;
19
+ }
20
+ return !(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey);
21
+ };
22
+ export const Link = ({ href, prefetch, children, onClick, ...rest }) => {
23
+ const shouldPrefetch = prefetch !== false;
24
+ const schedulePrefetch = () => {
25
+ if (!shouldPrefetch) {
26
+ return;
27
+ }
28
+ // Prefer idle time to keep hover prefetch from impacting interactivity.
29
+ const run = () => void getRouter().prefetch(href);
30
+ const idle = globalThis
31
+ .requestIdleCallback;
32
+ if (typeof idle === "function") {
33
+ idle(run);
34
+ return;
35
+ }
36
+ setTimeout(run, 0);
37
+ };
38
+ const handleClick = (event) => {
39
+ onClick?.(event);
40
+ if (!shouldHandleClick(event)) {
41
+ return;
42
+ }
43
+ event.preventDefault?.();
44
+ void getRouter().push(href);
45
+ };
46
+ const handleMouseEnter = (event) => {
47
+ rest.onMouseEnter?.(event);
48
+ schedulePrefetch();
49
+ };
50
+ const handleFocus = (event) => {
51
+ rest.onFocus?.(event);
52
+ schedulePrefetch();
53
+ };
54
+ return createElement("a", {
55
+ href,
56
+ // Keep a stable marker for island-mode delegated navigation handlers.
57
+ "data-hyper-link": "true",
58
+ onClick: handleClick,
59
+ onMouseEnter: handleMouseEnter,
60
+ onFocus: handleFocus,
61
+ prefetch,
62
+ ...rest,
63
+ }, children);
64
+ };
65
+ export const Head = ({ children }) => createElement(HeadMarker, {}, children);
66
+ export const RouterProvider = ({ router, initialHref, children }) => {
67
+ if (router) {
68
+ setRouter(router);
69
+ }
70
+ else if (initialHref) {
71
+ setRouter(createRouter(initialHref));
72
+ }
73
+ return children ?? null;
74
+ };
@@ -0,0 +1,27 @@
1
+ import type { DynamicManifestEnvelope, DynamicManifestResolver } from "./router.js";
2
+ export interface DynamicManifestCacheOptions {
3
+ ttlMs?: number;
4
+ maxEntries?: number;
5
+ now?: () => number;
6
+ }
7
+ export interface DynamicManifestCacheEntry {
8
+ envelope: DynamicManifestEnvelope;
9
+ etag?: string;
10
+ expiresAt: number;
11
+ }
12
+ export interface DynamicManifestCache {
13
+ get: (path: string) => DynamicManifestCacheEntry | null;
14
+ set: (path: string, envelope: DynamicManifestEnvelope, etag?: string) => void;
15
+ delete: (path: string) => void;
16
+ clear: () => void;
17
+ }
18
+ export declare const createDynamicManifestCache: (options?: DynamicManifestCacheOptions) => DynamicManifestCache;
19
+ export interface DynamicManifestResolverOptions {
20
+ baseUrl?: string;
21
+ cache?: DynamicManifestCache;
22
+ fetcher?: typeof fetch;
23
+ ttlMs?: number;
24
+ maxEntries?: number;
25
+ }
26
+ export declare const createDynamicManifestResolver: (options?: DynamicManifestResolverOptions) => DynamicManifestResolver;
27
+ //# sourceMappingURL=dynamic-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dynamic-manifest.d.ts","sourceRoot":"","sources":["../src/dynamic-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAEpF,MAAM,WAAW,2BAA2B;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,uBAAuB,CAAC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,yBAAyB,GAAG,IAAI,CAAC;IACxD,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,uBAAuB,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9E,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,eAAO,MAAM,0BAA0B,GACrC,UAAS,2BAAgC,KACxC,oBA6CF,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,oBAAoB,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAeD,eAAO,MAAM,6BAA6B,GACxC,UAAS,8BAAmC,KAC3C,uBAmCF,CAAC"}
@@ -0,0 +1,86 @@
1
+ export const createDynamicManifestCache = (options = {}) => {
2
+ const ttlMs = options.ttlMs ?? 5 * 60000;
3
+ const maxEntries = options.maxEntries ?? 100;
4
+ const now = options.now ?? (() => Date.now());
5
+ const entries = new Map();
6
+ const touch = (path, entry) => {
7
+ entries.delete(path);
8
+ entries.set(path, entry);
9
+ };
10
+ return {
11
+ get: (path) => {
12
+ const entry = entries.get(path);
13
+ if (!entry) {
14
+ return null;
15
+ }
16
+ if (entry.expiresAt <= now()) {
17
+ entries.delete(path);
18
+ return null;
19
+ }
20
+ touch(path, entry);
21
+ return entry;
22
+ },
23
+ set: (path, envelope, etag) => {
24
+ const entry = {
25
+ envelope,
26
+ etag,
27
+ expiresAt: now() + ttlMs,
28
+ };
29
+ entries.set(path, entry);
30
+ if (entries.size > maxEntries) {
31
+ const oldestKey = entries.keys().next().value;
32
+ if (oldestKey) {
33
+ entries.delete(oldestKey);
34
+ }
35
+ }
36
+ },
37
+ delete: (path) => {
38
+ entries.delete(path);
39
+ },
40
+ clear: () => {
41
+ entries.clear();
42
+ },
43
+ };
44
+ };
45
+ const resolveBaseUrl = (baseUrl) => {
46
+ if (baseUrl) {
47
+ return baseUrl;
48
+ }
49
+ if (typeof globalThis !== "undefined" && "location" in globalThis) {
50
+ const location = globalThis.location;
51
+ if (location?.origin) {
52
+ return location.origin;
53
+ }
54
+ }
55
+ return "http://localhost";
56
+ };
57
+ export const createDynamicManifestResolver = (options = {}) => {
58
+ const cache = options.cache ??
59
+ createDynamicManifestCache({
60
+ ttlMs: options.ttlMs,
61
+ maxEntries: options.maxEntries,
62
+ });
63
+ const fetcher = options.fetcher ?? fetch;
64
+ const baseUrl = resolveBaseUrl(options.baseUrl);
65
+ return async (path) => {
66
+ const cached = cache.get(path);
67
+ const url = new URL("/api/pages/resolve", baseUrl);
68
+ url.searchParams.set("path", path);
69
+ const headers = {};
70
+ if (cached?.etag) {
71
+ headers["If-None-Match"] = cached.etag;
72
+ }
73
+ const response = await fetcher(url.toString(), { method: "GET", headers });
74
+ if (response.status === 304 && cached) {
75
+ cache.set(path, cached.envelope, cached.etag);
76
+ return cached.envelope;
77
+ }
78
+ if (!response.ok) {
79
+ return null;
80
+ }
81
+ const envelope = (await response.json());
82
+ const etag = response.headers.get("ETag") ?? response.headers.get("etag") ?? undefined;
83
+ cache.set(path, envelope, etag);
84
+ return envelope;
85
+ };
86
+ };
@@ -0,0 +1,3 @@
1
+ import type { HeadDescriptor } from "@tyndall/core";
2
+ export declare const applyHead: (descriptor: HeadDescriptor) => void;
3
+ //# sourceMappingURL=head-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head-manager.d.ts","sourceRoot":"","sources":["../src/head-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAyBpD,eAAO,MAAM,SAAS,GAAI,YAAY,cAAc,KAAG,IActD,CAAC"}
@@ -0,0 +1,34 @@
1
+ const HEAD_ATTR = "data-hyper-head";
2
+ const removeManagedHead = (head) => {
3
+ const managed = head.querySelectorAll?.(`[${HEAD_ATTR}]`) ?? [];
4
+ for (const node of managed) {
5
+ node.remove?.();
6
+ }
7
+ };
8
+ const appendEntries = (head, tag, entries) => {
9
+ if (!entries?.length) {
10
+ return;
11
+ }
12
+ for (const entry of entries) {
13
+ const element = document.createElement(tag);
14
+ for (const [key, value] of Object.entries(entry)) {
15
+ element.setAttribute(key, value);
16
+ }
17
+ element.setAttribute(HEAD_ATTR, "");
18
+ head.appendChild(element);
19
+ }
20
+ };
21
+ export const applyHead = (descriptor) => {
22
+ if (typeof document === "undefined" || !document.head) {
23
+ return;
24
+ }
25
+ if (descriptor.title !== undefined) {
26
+ document.title = descriptor.title;
27
+ }
28
+ const head = document.head;
29
+ // Remove previously managed nodes to keep navigation updates deterministic.
30
+ removeManagedHead(head);
31
+ appendEntries(head, "meta", descriptor.meta);
32
+ appendEntries(head, "link", descriptor.link);
33
+ appendEntries(head, "script", descriptor.script);
34
+ };
package/dist/head.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { HeadDescriptor } from "@tyndall/core";
2
+ export declare const collectHeadFromTree: (node: unknown) => HeadDescriptor;
3
+ //# sourceMappingURL=head.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"head.d.ts","sourceRoot":"","sources":["../src/head.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AA8DpD,eAAO,MAAM,mBAAmB,GAAI,MAAM,OAAO,KAAG,cAsEnD,CAAC"}
package/dist/head.js ADDED
@@ -0,0 +1,112 @@
1
+ const REACT_ELEMENT = Symbol.for("react.element");
2
+ const REACT_TRANSITIONAL_ELEMENT = Symbol.for("react.transitional.element");
3
+ const HEAD_ELEMENT = Symbol.for("hyper.head");
4
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
+ const isElementLike = (value) => isRecord(value) &&
6
+ (value.$$typeof === REACT_ELEMENT || value.$$typeof === REACT_TRANSITIONAL_ELEMENT) &&
7
+ "type" in value &&
8
+ "props" in value;
9
+ const isHeadMarker = (element) => {
10
+ if (element.type === HEAD_ELEMENT) {
11
+ return true;
12
+ }
13
+ if (typeof element.type === "function") {
14
+ return Boolean(element.type.__hyperHead);
15
+ }
16
+ return false;
17
+ };
18
+ const extractAttributes = (props) => {
19
+ const attrs = {};
20
+ for (const [key, value] of Object.entries(props)) {
21
+ if (key === "children" || value === undefined || value === null) {
22
+ continue;
23
+ }
24
+ if (typeof value === "function") {
25
+ continue;
26
+ }
27
+ attrs[key] = String(value);
28
+ }
29
+ return attrs;
30
+ };
31
+ const extractText = (value) => {
32
+ if (value === null || value === undefined || typeof value === "boolean") {
33
+ return "";
34
+ }
35
+ if (Array.isArray(value)) {
36
+ return value.map(extractText).join("");
37
+ }
38
+ if (typeof value === "string" || typeof value === "number") {
39
+ return String(value);
40
+ }
41
+ if (isElementLike(value)) {
42
+ return extractText(value.props.children);
43
+ }
44
+ return "";
45
+ };
46
+ export const collectHeadFromTree = (node) => {
47
+ const head = {};
48
+ const meta = [];
49
+ const link = [];
50
+ const script = [];
51
+ const visit = (value, inHead) => {
52
+ if (value === null || value === undefined || typeof value === "boolean") {
53
+ return;
54
+ }
55
+ if (Array.isArray(value)) {
56
+ for (const entry of value) {
57
+ visit(entry, inHead);
58
+ }
59
+ return;
60
+ }
61
+ if (isElementLike(value)) {
62
+ if (isHeadMarker(value)) {
63
+ visit(value.props.children, true);
64
+ return;
65
+ }
66
+ if (inHead) {
67
+ if (value.type === "title") {
68
+ const text = extractText(value.props.children);
69
+ if (text) {
70
+ head.title = text;
71
+ }
72
+ return;
73
+ }
74
+ if (value.type === "meta") {
75
+ const attrs = extractAttributes(value.props);
76
+ if (Object.keys(attrs).length) {
77
+ meta.push(attrs);
78
+ }
79
+ return;
80
+ }
81
+ if (value.type === "link") {
82
+ const attrs = extractAttributes(value.props);
83
+ if (Object.keys(attrs).length) {
84
+ link.push(attrs);
85
+ }
86
+ return;
87
+ }
88
+ if (value.type === "script") {
89
+ const attrs = extractAttributes(value.props);
90
+ if (Object.keys(attrs).length) {
91
+ script.push(attrs);
92
+ }
93
+ return;
94
+ }
95
+ }
96
+ if ("children" in value.props) {
97
+ visit(value.props.children, inHead);
98
+ }
99
+ }
100
+ };
101
+ visit(node, false);
102
+ if (meta.length) {
103
+ head.meta = meta;
104
+ }
105
+ if (link.length) {
106
+ head.link = link;
107
+ }
108
+ if (script.length) {
109
+ head.script = script;
110
+ }
111
+ return head;
112
+ };
@@ -0,0 +1,15 @@
1
+ export { createReactAdapter } from "./adapter.js";
2
+ export type { ReactAdapterOptions } from "./adapter.js";
3
+ export { createReactUiAdapterFactory, createReactAdapterRegistry } from "./registry.js";
4
+ export { Link, Head, RouterProvider } from "./components.js";
5
+ export type { LinkProps, HeadProps, RouterProviderProps, ElementLike } from "./components.js";
6
+ export { createRouter, getRouter, useRouter } from "./router.js";
7
+ export type { ClientRenderMode, ClientRouteModule, ClientRouteModuleContext, ClientRouteModuleLoader, ClientRouteModuleLoaderMap, ClientRouteModuleResolver, ClientRouteModuleResolverContext, ClientRouteRenderResult, DynamicManifestEnvelope, DynamicManifestRenderer, DynamicManifestResolver, HydrationMode, NavigationMode, RenderPolicy, RoutePayload, RoutePayloadApplier, RoutePayloadResolver, Router, RouterOptions, RouterQuery, } from "./router.js";
8
+ export { createClientRouteModuleResolver } from "./router.js";
9
+ export { installRouterIslands } from "./islands.js";
10
+ export { collectHeadFromTree } from "./head.js";
11
+ export { applyHead } from "./head-manager.js";
12
+ export { createDynamicManifestCache, createDynamicManifestResolver, } from "./dynamic-manifest.js";
13
+ export type { DynamicManifestCache, DynamicManifestCacheEntry, DynamicManifestCacheOptions, DynamicManifestResolverOptions, } from "./dynamic-manifest.js";
14
+ export * from "./types.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAExF,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC7D,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9F,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EACV,gBAAgB,EAChB,iBAAiB,EACjB,wBAAwB,EACxB,uBAAuB,EACvB,0BAA0B,EAC1B,yBAAyB,EACzB,gCAAgC,EAChC,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,uBAAuB,EACvB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,MAAM,EACN,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,+BAA+B,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,oBAAoB,EACpB,yBAAyB,EACzB,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAE/B,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export { createReactAdapter } from "./adapter.js";
2
+ export { createReactUiAdapterFactory, createReactAdapterRegistry } from "./registry.js";
3
+ export { Link, Head, RouterProvider } from "./components.js";
4
+ export { createRouter, getRouter, useRouter } from "./router.js";
5
+ export { createClientRouteModuleResolver } from "./router.js";
6
+ export { installRouterIslands } from "./islands.js";
7
+ export { collectHeadFromTree } from "./head.js";
8
+ export { applyHead } from "./head-manager.js";
9
+ export { createDynamicManifestCache, createDynamicManifestResolver, } from "./dynamic-manifest.js";
10
+ export * from "./types.js";
@@ -0,0 +1,3 @@
1
+ import type { Router } from "./router.js";
2
+ export declare const installRouterIslands: (router: Router) => void;
3
+ //# sourceMappingURL=islands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"islands.d.ts","sourceRoot":"","sources":["../src/islands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAiD1C,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,KAAG,IAqCrD,CAAC"}
@@ -0,0 +1,78 @@
1
+ const ISLANDS_BOOTSTRAP_KEY = "__HYPER_ROUTER_ISLANDS__";
2
+ const isPrimaryClick = (event) => event.button === 0 &&
3
+ !event.defaultPrevented &&
4
+ !event.metaKey &&
5
+ !event.ctrlKey &&
6
+ !event.shiftKey &&
7
+ !event.altKey;
8
+ const resolveInternalLink = (eventTarget) => {
9
+ if (!(eventTarget instanceof Element)) {
10
+ return null;
11
+ }
12
+ const anchor = eventTarget.closest("a[data-hyper-link]");
13
+ if (!anchor) {
14
+ return null;
15
+ }
16
+ if (anchor.target && anchor.target !== "_self") {
17
+ return null;
18
+ }
19
+ if (anchor.hasAttribute("download")) {
20
+ return null;
21
+ }
22
+ const href = anchor.getAttribute("href");
23
+ if (!href) {
24
+ return null;
25
+ }
26
+ const url = new URL(href, window.location.href);
27
+ if (url.origin !== window.location.origin) {
28
+ return null;
29
+ }
30
+ return anchor;
31
+ };
32
+ const prefetchInternalLink = (router, eventTarget) => {
33
+ const anchor = resolveInternalLink(eventTarget);
34
+ if (!anchor) {
35
+ return;
36
+ }
37
+ const href = anchor.getAttribute("href");
38
+ if (!href) {
39
+ return;
40
+ }
41
+ void router.prefetch(href);
42
+ };
43
+ export const installRouterIslands = (router) => {
44
+ if (typeof window === "undefined" || typeof document === "undefined") {
45
+ return;
46
+ }
47
+ const state = window;
48
+ if (state[ISLANDS_BOOTSTRAP_KEY]) {
49
+ return;
50
+ }
51
+ state[ISLANDS_BOOTSTRAP_KEY] = true;
52
+ // Keep SSR island pages interactive without hydrating the full app tree.
53
+ document.addEventListener("click", (event) => {
54
+ if (!isPrimaryClick(event)) {
55
+ return;
56
+ }
57
+ const anchor = resolveInternalLink(event.target);
58
+ if (!anchor) {
59
+ return;
60
+ }
61
+ const href = anchor.getAttribute("href");
62
+ if (!href) {
63
+ return;
64
+ }
65
+ event.preventDefault();
66
+ if (anchor.getAttribute("data-hyper-replace") === "true") {
67
+ void router.replace(href);
68
+ return;
69
+ }
70
+ void router.push(href);
71
+ });
72
+ document.addEventListener("mouseover", (event) => {
73
+ prefetchInternalLink(router, event.target);
74
+ });
75
+ document.addEventListener("focusin", (event) => {
76
+ prefetchInternalLink(router, event.target);
77
+ });
78
+ };
@@ -0,0 +1,10 @@
1
+ import type { RouteGraph, RouteRecord } from "@tyndall/core";
2
+ export interface ReactRouterMatchResult {
3
+ route: RouteRecord;
4
+ params: Record<string, string | string[]>;
5
+ }
6
+ export interface ReactRouterRouteMatcher {
7
+ match: (pathname: string) => ReactRouterMatchResult | null;
8
+ }
9
+ export declare const createReactRouterRouteMatcher: (routeGraph: RouteGraph) => ReactRouterRouteMatcher;
10
+ //# sourceMappingURL=react-router-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react-router-bridge.d.ts","sourceRoot":"","sources":["../src/react-router-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AAa3E,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC3C;AA2JD,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,sBAAsB,GAAG,IAAI,CAAC;CAC5D;AAED,eAAO,MAAM,6BAA6B,GACxC,YAAY,UAAU,KACrB,uBA2BF,CAAC"}
@@ -0,0 +1,148 @@
1
+ import { matchRoutes } from "react-router";
2
+ const segmentOrder = (segment) => {
3
+ if (segment.type === "root") {
4
+ return 0;
5
+ }
6
+ if (segment.type === "static") {
7
+ return 1;
8
+ }
9
+ if (segment.type === "dynamic") {
10
+ return 2;
11
+ }
12
+ return 3;
13
+ };
14
+ const compareSegments = (left, right) => {
15
+ const order = segmentOrder(left) - segmentOrder(right);
16
+ if (order !== 0) {
17
+ return order;
18
+ }
19
+ if (left.type === "root" || right.type === "root") {
20
+ return 0;
21
+ }
22
+ if (left.type === "static" && right.type === "static") {
23
+ return left.value.localeCompare(right.value);
24
+ }
25
+ if (left.type === "dynamic" && right.type === "dynamic") {
26
+ return left.name.localeCompare(right.name);
27
+ }
28
+ if (left.type === "catchAll" && right.type === "catchAll") {
29
+ return left.name.localeCompare(right.name);
30
+ }
31
+ return 0;
32
+ };
33
+ const sameSegment = (left, right) => {
34
+ if (left.type !== right.type) {
35
+ return false;
36
+ }
37
+ if (left.type === "static" && right.type === "static") {
38
+ return left.value === right.value;
39
+ }
40
+ if (left.type === "dynamic" && right.type === "dynamic") {
41
+ return left.name === right.name;
42
+ }
43
+ if (left.type === "catchAll" && right.type === "catchAll") {
44
+ return left.name === right.name;
45
+ }
46
+ return false;
47
+ };
48
+ const createRootNode = () => ({
49
+ segment: { type: "root" },
50
+ children: [],
51
+ });
52
+ const findOrCreateChild = (parent, segment) => {
53
+ const existing = parent.children.find((child) => child.segment.type !== "root" && sameSegment(child.segment, segment));
54
+ if (existing) {
55
+ return existing;
56
+ }
57
+ const child = { segment, children: [] };
58
+ parent.children.push(child);
59
+ parent.children.sort((a, b) => compareSegments(a.segment, b.segment));
60
+ return child;
61
+ };
62
+ const buildRouteTree = (routeGraph) => {
63
+ const root = createRootNode();
64
+ const orderedRoutes = [...routeGraph.routes].sort((a, b) => a.id.localeCompare(b.id));
65
+ for (const route of orderedRoutes) {
66
+ let cursor = root;
67
+ for (const segment of route.segments) {
68
+ cursor = findOrCreateChild(cursor, segment);
69
+ }
70
+ cursor.route = route;
71
+ }
72
+ return root;
73
+ };
74
+ const segmentToPath = (segment) => {
75
+ if (segment.type === "static") {
76
+ return segment.value;
77
+ }
78
+ if (segment.type === "dynamic") {
79
+ return `:${segment.name}`;
80
+ }
81
+ return "*";
82
+ };
83
+ const createHandle = (route) => route ? { hyperRoute: route } : undefined;
84
+ const nodeToRouteObject = (node) => {
85
+ const childRoutes = node.children.map((child) => nodeToRouteObject(child));
86
+ const children = childRoutes.length > 0 ? childRoutes : undefined;
87
+ if (node.segment.type === "root") {
88
+ // Keep a single root route so nested routeGraph paths can be matched as one tree.
89
+ return {
90
+ path: "/",
91
+ handle: createHandle(node.route),
92
+ children,
93
+ };
94
+ }
95
+ return {
96
+ path: segmentToPath(node.segment),
97
+ handle: createHandle(node.route),
98
+ children,
99
+ };
100
+ };
101
+ const hasHyperRouteHandle = (value) => typeof value === "object" &&
102
+ value !== null &&
103
+ "hyperRoute" in value &&
104
+ typeof value.hyperRoute?.id === "string";
105
+ const normalizeParams = (route, params) => {
106
+ const normalized = {};
107
+ for (const segment of route.segments) {
108
+ if (segment.type === "dynamic") {
109
+ const value = params[segment.name];
110
+ if (typeof value === "string") {
111
+ normalized[segment.name] = value;
112
+ }
113
+ continue;
114
+ }
115
+ if (segment.type === "catchAll") {
116
+ // react-router exposes splat as "*" so remap back to routeGraph's named catch-all param.
117
+ const splat = params["*"];
118
+ if (typeof splat === "string" && splat.length > 0) {
119
+ normalized[segment.name] = splat.split("/").filter((entry) => entry.length > 0);
120
+ }
121
+ }
122
+ }
123
+ return normalized;
124
+ };
125
+ export const createReactRouterRouteMatcher = (routeGraph) => {
126
+ const tree = buildRouteTree(routeGraph);
127
+ const routes = [nodeToRouteObject(tree)];
128
+ return {
129
+ match: (pathname) => {
130
+ const matches = matchRoutes(routes, pathname);
131
+ if (!matches || matches.length === 0) {
132
+ return null;
133
+ }
134
+ for (let index = matches.length - 1; index >= 0; index -= 1) {
135
+ const candidate = matches[index];
136
+ if (!candidate || !hasHyperRouteHandle(candidate.route.handle)) {
137
+ continue;
138
+ }
139
+ const route = candidate.route.handle.hyperRoute;
140
+ return {
141
+ route,
142
+ params: normalizeParams(route, candidate.params),
143
+ };
144
+ }
145
+ return null;
146
+ },
147
+ };
148
+ };