@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,4 @@
1
+ import type { UIAdapterFactory, UIAdapterRegistry } from "@tyndall/core";
2
+ export declare const createReactUiAdapterFactory: () => UIAdapterFactory;
3
+ export declare const createReactAdapterRegistry: () => UIAdapterRegistry;
4
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAWzF,eAAO,MAAM,2BAA2B,QAAO,gBAiB9C,CAAC;AAEF,eAAO,MAAM,0BAA0B,QAAO,iBAE5C,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { createReactAdapter } from "./adapter.js";
2
+ const isFunction = (value) => typeof value === "function";
3
+ export const createReactUiAdapterFactory = () => (options) => {
4
+ const normalized = options;
5
+ const routeRender = isFunction(normalized.routeRender)
6
+ ? normalized.routeRender
7
+ : undefined;
8
+ const routeHead = isFunction(normalized.routeHead)
9
+ ? normalized.routeHead
10
+ : undefined;
11
+ return createReactAdapter({
12
+ render: routeRender ? ({ props }) => routeRender(props) : undefined,
13
+ getHead: routeHead ? ({ props }) => routeHead(props) : undefined,
14
+ });
15
+ };
16
+ export const createReactAdapterRegistry = () => ({
17
+ react: createReactUiAdapterFactory(),
18
+ });
@@ -0,0 +1,82 @@
1
+ import { type HeadDescriptor, type ResolverFallbackPolicy, type RouteGraph } from "@tyndall/core";
2
+ export type RouterQuery = Record<string, string | string[]>;
3
+ export type RenderPolicy = "csr" | "ssr" | "auto" | "redirect";
4
+ export type HydrationMode = "full" | "islands";
5
+ export type NavigationMode = "url" | "client";
6
+ export type ClientRenderMode = "payload" | "module";
7
+ export interface DynamicManifestPolicy {
8
+ render?: RenderPolicy;
9
+ redirect?: string;
10
+ }
11
+ export interface DynamicManifestEnvelope {
12
+ policy?: DynamicManifestPolicy;
13
+ meta?: unknown;
14
+ definition?: unknown;
15
+ }
16
+ export type DynamicManifestResolver = (path: string) => Promise<DynamicManifestEnvelope | null>;
17
+ export type DynamicManifestRenderer = (meta: unknown, envelope: DynamicManifestEnvelope) => void | Promise<void>;
18
+ export interface RoutePayload {
19
+ kind: "hyper-route-payload";
20
+ routeId: string;
21
+ appHtml: string;
22
+ propsPayload: string;
23
+ head?: HeadDescriptor;
24
+ hydration?: HydrationMode;
25
+ }
26
+ export interface ClientRouteModuleContext {
27
+ href: string;
28
+ pathname: string;
29
+ routeId: string;
30
+ params: Record<string, string | string[]>;
31
+ query: RouterQuery;
32
+ }
33
+ export interface ClientRouteRenderResult {
34
+ routeId?: string;
35
+ appHtml: string;
36
+ propsPayload?: string;
37
+ head?: HeadDescriptor;
38
+ hydration?: HydrationMode;
39
+ }
40
+ export interface ClientRouteModule {
41
+ render: (ctx: ClientRouteModuleContext) => Promise<ClientRouteRenderResult> | ClientRouteRenderResult;
42
+ }
43
+ export interface ClientRouteModuleResolverContext {
44
+ href: string;
45
+ pathname: string;
46
+ routeId: string;
47
+ params: Record<string, string | string[]>;
48
+ query: RouterQuery;
49
+ }
50
+ export type ClientRouteModuleResolver = (ctx: ClientRouteModuleResolverContext) => Promise<ClientRouteModule | null>;
51
+ export type ClientRouteModuleLoader = () => Promise<unknown>;
52
+ export type ClientRouteModuleLoaderMap = Record<string, ClientRouteModuleLoader>;
53
+ export type RoutePayloadResolver = (href: string) => Promise<RoutePayload | null>;
54
+ export type RoutePayloadApplier = (payload: RoutePayload) => void | Promise<void>;
55
+ export interface RouterOptions {
56
+ routeGraph?: RouteGraph;
57
+ resolveDynamicManifest?: DynamicManifestResolver;
58
+ renderDynamic?: DynamicManifestRenderer;
59
+ resolveRoutePayload?: RoutePayloadResolver;
60
+ applyRoutePayload?: RoutePayloadApplier;
61
+ navigationMode?: NavigationMode;
62
+ clientRenderMode?: ClientRenderMode;
63
+ resolveClientRouteModule?: ClientRouteModuleResolver;
64
+ fallbackToUrl?: boolean;
65
+ buildMode?: "ssg" | "ssr";
66
+ onWarning?: (message: string) => void;
67
+ fallbackPolicy?: ResolverFallbackPolicy;
68
+ }
69
+ export interface Router {
70
+ pathname: string;
71
+ query: RouterQuery;
72
+ push: (href: string) => Promise<void>;
73
+ replace: (href: string) => Promise<void>;
74
+ prefetch: (href: string) => Promise<void>;
75
+ subscribe: (listener: (router: Router) => void) => () => void;
76
+ }
77
+ export declare const createClientRouteModuleResolver: (loaders: ClientRouteModuleLoaderMap) => ClientRouteModuleResolver;
78
+ export declare const createRouter: (initialHref?: string | RouterOptions, options?: RouterOptions) => Router;
79
+ export declare const getRouter: () => Router;
80
+ export declare const setRouter: (router: Router) => void;
81
+ export declare const useRouter: () => Router;
82
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
package/dist/router.js ADDED
@@ -0,0 +1,470 @@
1
+ import { evaluateRenderPolicy, shouldForceDynamicFallback, } from "@tyndall/core";
2
+ import { applyHead } from "./head-manager.js";
3
+ import { createReactRouterRouteMatcher } from "./react-router-bridge.js";
4
+ const NAVIGATION_HEADER = "x-hyper-navigation";
5
+ const NAVIGATION_MODE = "csr";
6
+ const ROUTE_PAYLOAD_EVENT = "hyper:route-payload-applied";
7
+ const inflightPrefetchRequests = new Map();
8
+ const hasBrowserHistory = () => typeof globalThis !== "undefined" &&
9
+ "history" in globalThis &&
10
+ Boolean(globalThis.history);
11
+ const getBaseUrl = () => {
12
+ if (typeof globalThis !== "undefined" && "location" in globalThis) {
13
+ const location = globalThis.location;
14
+ if (location?.origin) {
15
+ return location.origin;
16
+ }
17
+ }
18
+ return "http://localhost";
19
+ };
20
+ const requestPrefetch = async (href) => {
21
+ if (typeof globalThis === "undefined" || typeof fetch === "undefined") {
22
+ return;
23
+ }
24
+ const targetUrl = new URL(href, getBaseUrl());
25
+ const targetPath = `${targetUrl.pathname}${targetUrl.search}`;
26
+ const inflightRequest = inflightPrefetchRequests.get(targetPath);
27
+ if (inflightRequest) {
28
+ // Hover/focus prefetch can dispatch repeatedly for the same href; share one request.
29
+ await inflightRequest;
30
+ return;
31
+ }
32
+ const url = new URL("/__hyper/prefetch", getBaseUrl());
33
+ url.searchParams.set("path", targetPath);
34
+ const prefetchRequest = (async () => {
35
+ try {
36
+ await fetch(url.toString(), { method: "POST" });
37
+ }
38
+ catch {
39
+ // Prefetch is best-effort; ignore network errors.
40
+ }
41
+ })();
42
+ const trackedPrefetchRequest = prefetchRequest.finally(() => {
43
+ if (inflightPrefetchRequests.get(targetPath) === trackedPrefetchRequest) {
44
+ inflightPrefetchRequests.delete(targetPath);
45
+ }
46
+ });
47
+ inflightPrefetchRequests.set(targetPath, trackedPrefetchRequest);
48
+ await trackedPrefetchRequest;
49
+ };
50
+ const parseHref = (href, baseUrl) => {
51
+ const url = new URL(href, baseUrl);
52
+ const query = {};
53
+ url.searchParams.forEach((value, key) => {
54
+ const current = query[key];
55
+ if (current === undefined) {
56
+ query[key] = value;
57
+ return;
58
+ }
59
+ if (Array.isArray(current)) {
60
+ current.push(value);
61
+ return;
62
+ }
63
+ query[key] = [current, value];
64
+ });
65
+ return { pathname: url.pathname, query };
66
+ };
67
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
68
+ const isClientRouteRenderResult = (value) => {
69
+ if (!isRecord(value) || typeof value.appHtml !== "string") {
70
+ return false;
71
+ }
72
+ if (value.routeId !== undefined && typeof value.routeId !== "string") {
73
+ return false;
74
+ }
75
+ if (value.propsPayload !== undefined && typeof value.propsPayload !== "string") {
76
+ return false;
77
+ }
78
+ if (value.hydration !== undefined &&
79
+ value.hydration !== "full" &&
80
+ value.hydration !== "islands") {
81
+ return false;
82
+ }
83
+ return true;
84
+ };
85
+ const isRoutePayload = (value) => {
86
+ if (!value || typeof value !== "object") {
87
+ return false;
88
+ }
89
+ const payload = value;
90
+ if (payload.kind !== "hyper-route-payload" ||
91
+ typeof payload.routeId !== "string" ||
92
+ typeof payload.appHtml !== "string" ||
93
+ typeof payload.propsPayload !== "string") {
94
+ return false;
95
+ }
96
+ if (payload.hydration !== undefined &&
97
+ payload.hydration !== "full" &&
98
+ payload.hydration !== "islands") {
99
+ return false;
100
+ }
101
+ return true;
102
+ };
103
+ const resolveRoutePayloadFromServer = async (href, baseUrl) => {
104
+ if (typeof fetch === "undefined") {
105
+ return null;
106
+ }
107
+ const url = new URL(href, baseUrl);
108
+ try {
109
+ const response = await fetch(url.toString(), {
110
+ method: "GET",
111
+ headers: {
112
+ [NAVIGATION_HEADER]: NAVIGATION_MODE,
113
+ accept: "application/json",
114
+ },
115
+ });
116
+ if (!response.ok) {
117
+ return null;
118
+ }
119
+ const contentType = response.headers.get("content-type") ?? "";
120
+ if (!contentType.includes("application/json")) {
121
+ return null;
122
+ }
123
+ const json = (await response.json());
124
+ return isRoutePayload(json) ? json : null;
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ };
130
+ const applyRoutePayloadToDom = (payload) => {
131
+ if (typeof document === "undefined") {
132
+ return;
133
+ }
134
+ const app = document.getElementById("app");
135
+ const hasMountedClientApp = payload.hydration !== "islands" &&
136
+ Boolean(globalThis.__HYPER_CLIENT_APP_MOUNTED__);
137
+ if (app) {
138
+ if (!hasMountedClientApp) {
139
+ app.innerHTML = payload.appHtml;
140
+ }
141
+ app.setAttribute("data-hyper-route", payload.routeId);
142
+ app.setAttribute("data-hyper-hydrated", "false");
143
+ if (payload.hydration) {
144
+ app.setAttribute("data-hyper-hydration", payload.hydration);
145
+ if (payload.hydration === "islands") {
146
+ app.setAttribute("data-hyper-island-root", "router");
147
+ }
148
+ else {
149
+ app.removeAttribute("data-hyper-island-root");
150
+ }
151
+ }
152
+ }
153
+ const propsScript = document.getElementById("__HYPER_PROPS__");
154
+ if (propsScript) {
155
+ propsScript.textContent = payload.propsPayload;
156
+ }
157
+ if (payload.head) {
158
+ applyHead(payload.head);
159
+ }
160
+ globalThis.__HYPER_ROUTE_ID__ = payload.routeId;
161
+ globalThis.__HYPER_HYDRATED__ = false;
162
+ try {
163
+ globalThis.dispatchEvent?.(new CustomEvent(ROUTE_PAYLOAD_EVENT, {
164
+ detail: { routeId: payload.routeId },
165
+ }));
166
+ }
167
+ catch {
168
+ // Keep client navigation flow resilient if event dispatch is unavailable.
169
+ }
170
+ };
171
+ const toRoutePayload = (rendered) => ({
172
+ kind: "hyper-route-payload",
173
+ routeId: rendered.routeId,
174
+ appHtml: rendered.appHtml,
175
+ propsPayload: rendered.propsPayload ?? "{}",
176
+ head: rendered.head,
177
+ hydration: rendered.hydration,
178
+ });
179
+ const isMetaDefinition = (value) => {
180
+ if (!value || typeof value !== "object") {
181
+ return false;
182
+ }
183
+ const record = value;
184
+ return record.kind === "meta" && "root" in record;
185
+ };
186
+ const normalizeLoadedClientRouteModule = (value) => {
187
+ if (isRecord(value) && typeof value.render === "function") {
188
+ return {
189
+ render: value.render,
190
+ };
191
+ }
192
+ if (typeof value === "function") {
193
+ return {
194
+ render: value,
195
+ };
196
+ }
197
+ if (isRecord(value) && typeof value.default === "function") {
198
+ return {
199
+ render: value.default,
200
+ };
201
+ }
202
+ return null;
203
+ };
204
+ export const createClientRouteModuleResolver = (loaders) => {
205
+ const map = { ...loaders };
206
+ return async (ctx) => {
207
+ const load = map[ctx.routeId];
208
+ if (!load) {
209
+ return null;
210
+ }
211
+ try {
212
+ const loaded = await load();
213
+ return normalizeLoadedClientRouteModule(loaded);
214
+ }
215
+ catch {
216
+ return null;
217
+ }
218
+ };
219
+ };
220
+ const resolveRouterOptions = (initial, options) => {
221
+ if (initial && typeof initial === "object") {
222
+ return { initialHref: undefined, options: initial };
223
+ }
224
+ return { initialHref: initial, options: options ?? {} };
225
+ };
226
+ export const createRouter = (initialHref, options) => {
227
+ const resolved = resolveRouterOptions(initialHref, options);
228
+ const routerOptions = resolved.options;
229
+ const routeMatcher = routerOptions.routeGraph
230
+ ? createReactRouterRouteMatcher(routerOptions.routeGraph)
231
+ : null;
232
+ const baseUrl = getBaseUrl();
233
+ const navigationMode = routerOptions.navigationMode ?? "client";
234
+ const clientRenderMode = routerOptions.clientRenderMode ?? "payload";
235
+ const fallbackToUrl = routerOptions.fallbackToUrl ?? true;
236
+ const hasCustomRoutePayloadResolver = typeof routerOptions.resolveRoutePayload === "function";
237
+ const resolveRoutePayload = routerOptions.resolveRoutePayload ??
238
+ ((href) => resolveRoutePayloadFromServer(href, baseUrl));
239
+ const applyRoutePayload = routerOptions.applyRoutePayload ?? applyRoutePayloadToDom;
240
+ const listeners = new Set();
241
+ const router = {
242
+ pathname: "/",
243
+ query: {},
244
+ push: async (href) => {
245
+ await resolveAndNavigate(href, false);
246
+ },
247
+ replace: async (href) => {
248
+ await resolveAndNavigate(href, true);
249
+ },
250
+ prefetch: async (href) => {
251
+ await requestPrefetch(href);
252
+ const next = parseHref(href, baseUrl);
253
+ const forceDynamic = shouldForceDynamicFallback(next.pathname, routerOptions.fallbackPolicy);
254
+ if (routerOptions.resolveDynamicManifest &&
255
+ (forceDynamic || !routeMatcher || !matchRouteGraph(next.pathname))) {
256
+ await routerOptions.resolveDynamicManifest(next.pathname);
257
+ return;
258
+ }
259
+ if (navigationMode === "client" && clientRenderMode === "module") {
260
+ await resolveClientRouteRender(href);
261
+ return;
262
+ }
263
+ if (navigationMode === "client" && hasCustomRoutePayloadResolver) {
264
+ // Avoid duplicate network fetches when the dev prefetch endpoint already warms payload cache.
265
+ await resolveRoutePayload(href);
266
+ }
267
+ },
268
+ subscribe: (listener) => {
269
+ listeners.add(listener);
270
+ return () => {
271
+ listeners.delete(listener);
272
+ };
273
+ },
274
+ };
275
+ const updateState = (href) => {
276
+ const next = parseHref(href, baseUrl);
277
+ router.pathname = next.pathname;
278
+ router.query = next.query;
279
+ for (const listener of listeners) {
280
+ listener(router);
281
+ }
282
+ };
283
+ const matchRouteGraph = (pathname) => routeMatcher?.match(pathname) ?? null;
284
+ const navigate = async (href, replace) => {
285
+ // Important: only touch browser history when it exists (SSR-safe).
286
+ if (hasBrowserHistory()) {
287
+ const history = globalThis.history;
288
+ const method = replace ? "replaceState" : "pushState";
289
+ history[method](null, "", href);
290
+ }
291
+ updateState(href);
292
+ };
293
+ const navigateWithUrl = async (href, replace) => {
294
+ const location = globalThis.location;
295
+ if (location) {
296
+ if (replace && typeof location.replace === "function") {
297
+ location.replace(href);
298
+ return;
299
+ }
300
+ if (!replace && typeof location.assign === "function") {
301
+ location.assign(href);
302
+ return;
303
+ }
304
+ location.href = href;
305
+ return;
306
+ }
307
+ await navigate(href, replace);
308
+ };
309
+ const navigateWithPayload = async (href, replace) => {
310
+ const payload = await resolveRoutePayload(href);
311
+ if (payload) {
312
+ await applyRoutePayload(payload);
313
+ await navigate(href, replace);
314
+ return true;
315
+ }
316
+ if (fallbackToUrl) {
317
+ await navigateWithUrl(href, replace);
318
+ return false;
319
+ }
320
+ await navigate(href, replace);
321
+ return false;
322
+ };
323
+ const resolveClientRouteRender = async (href) => {
324
+ if (!routerOptions.resolveClientRouteModule || !routeMatcher) {
325
+ return null;
326
+ }
327
+ const next = parseHref(href, baseUrl);
328
+ const match = matchRouteGraph(next.pathname);
329
+ if (!match) {
330
+ return null;
331
+ }
332
+ const module = await routerOptions.resolveClientRouteModule({
333
+ href,
334
+ pathname: next.pathname,
335
+ routeId: match.route.id,
336
+ params: match.params,
337
+ query: next.query,
338
+ });
339
+ if (!module) {
340
+ return null;
341
+ }
342
+ try {
343
+ const rendered = await module.render({
344
+ href,
345
+ pathname: next.pathname,
346
+ routeId: match.route.id,
347
+ params: match.params,
348
+ query: next.query,
349
+ });
350
+ if (!isClientRouteRenderResult(rendered)) {
351
+ return null;
352
+ }
353
+ return {
354
+ ...rendered,
355
+ routeId: rendered.routeId ?? match.route.id,
356
+ };
357
+ }
358
+ catch {
359
+ return null;
360
+ }
361
+ };
362
+ const navigateWithClientModule = async (href, replace) => {
363
+ const rendered = await resolveClientRouteRender(href);
364
+ if (!rendered) {
365
+ return false;
366
+ }
367
+ await applyRoutePayload(toRoutePayload(rendered));
368
+ await navigate(href, replace);
369
+ return true;
370
+ };
371
+ const navigateWithClientRender = async (href, replace) => {
372
+ if (clientRenderMode === "module") {
373
+ const moduleApplied = await navigateWithClientModule(href, replace);
374
+ if (moduleApplied) {
375
+ return;
376
+ }
377
+ }
378
+ await navigateWithPayload(href, replace);
379
+ };
380
+ const navigateByMode = async (href, replace) => {
381
+ if (navigationMode === "url") {
382
+ await navigateWithUrl(href, replace);
383
+ return;
384
+ }
385
+ await navigateWithClientRender(href, replace);
386
+ };
387
+ const resolveDynamicFallback = async (href, replace) => {
388
+ const next = parseHref(href, baseUrl);
389
+ const forceDynamic = shouldForceDynamicFallback(next.pathname, routerOptions.fallbackPolicy);
390
+ if (routeMatcher && !forceDynamic && matchRouteGraph(next.pathname)) {
391
+ await navigateByMode(href, replace);
392
+ return;
393
+ }
394
+ if (routerOptions.resolveDynamicManifest) {
395
+ const envelope = await routerOptions.resolveDynamicManifest(next.pathname);
396
+ if (envelope) {
397
+ const policy = envelope.policy?.render ?? "auto";
398
+ if (policy === "redirect" && envelope.policy?.redirect) {
399
+ await navigateByMode(envelope.policy.redirect, replace);
400
+ return;
401
+ }
402
+ let policyRender;
403
+ if (policy === "redirect") {
404
+ // Missing redirect target: fallback to auto to avoid breaking navigation.
405
+ const message = "Redirect policy missing target; falling back to auto.";
406
+ if (routerOptions.onWarning) {
407
+ routerOptions.onWarning(message);
408
+ }
409
+ else if (typeof console !== "undefined" && console.warn) {
410
+ console.warn(message);
411
+ }
412
+ policyRender = "auto";
413
+ }
414
+ else {
415
+ policyRender = policy;
416
+ }
417
+ const decision = evaluateRenderPolicy(routerOptions.buildMode ?? "ssg", policyRender);
418
+ for (const warning of decision.warnings) {
419
+ if (routerOptions.onWarning) {
420
+ routerOptions.onWarning(warning);
421
+ }
422
+ else if (typeof console !== "undefined" && console.warn) {
423
+ console.warn(warning);
424
+ }
425
+ }
426
+ if (decision.mode === "ssr") {
427
+ await navigateWithUrl(href, replace);
428
+ return;
429
+ }
430
+ const meta = envelope.meta ??
431
+ (isMetaDefinition(envelope.definition) ? envelope.definition.root : undefined);
432
+ if (decision.mode === "csr" && meta !== undefined) {
433
+ await routerOptions.renderDynamic?.(meta, envelope);
434
+ await navigateByMode(href, replace);
435
+ return;
436
+ }
437
+ }
438
+ }
439
+ await navigateByMode(href, replace);
440
+ };
441
+ const resolveAndNavigate = async (href, replace) => {
442
+ await resolveDynamicFallback(href, replace);
443
+ };
444
+ const seed = resolved.initialHref ??
445
+ (typeof globalThis !== "undefined" && "location" in globalThis
446
+ ? globalThis.location.href
447
+ : "/");
448
+ updateState(seed);
449
+ if (hasBrowserHistory() && typeof globalThis.addEventListener === "function") {
450
+ // Keep router state in sync with back/forward navigation.
451
+ globalThis.addEventListener("popstate", () => {
452
+ const location = globalThis.location;
453
+ if (location?.href) {
454
+ updateState(location.href);
455
+ }
456
+ });
457
+ }
458
+ return router;
459
+ };
460
+ let activeRouter = null;
461
+ export const getRouter = () => {
462
+ if (!activeRouter) {
463
+ activeRouter = createRouter();
464
+ }
465
+ return activeRouter;
466
+ };
467
+ export const setRouter = (router) => {
468
+ activeRouter = router;
469
+ };
470
+ export const useRouter = () => getRouter();
@@ -0,0 +1,17 @@
1
+ import type { GetServerProps as HyperGetServerProps, GetStaticPaths as HyperGetStaticPaths, GetStaticProps as HyperGetStaticProps, PageContextBase, PageComponent, StaticPropsContext, StaticPropsResult, StaticPathsResult, ServerPropsContext, ServerPropsResult } from "@tyndall/core";
2
+ export type NextPageContext = PageContextBase;
3
+ export type NextPage<Props = Record<string, unknown>> = PageComponent<Props>;
4
+ export type NextStaticPropsContext = StaticPropsContext;
5
+ export type NextStaticPropsResult<Props = Record<string, unknown>> = StaticPropsResult<Props>;
6
+ export type NextStaticPathsResult = StaticPathsResult;
7
+ export type NextServerPropsContext = ServerPropsContext;
8
+ export type NextServerPropsResult<Props = Record<string, unknown>> = ServerPropsResult<Props>;
9
+ export type GetStaticPropsContext = StaticPropsContext;
10
+ export type GetStaticPropsResult<Props = Record<string, unknown>> = StaticPropsResult<Props>;
11
+ export type GetStaticPathsResult = StaticPathsResult;
12
+ export type GetStaticProps<Props = Record<string, unknown>> = HyperGetStaticProps<Props>;
13
+ export type GetStaticPaths = HyperGetStaticPaths;
14
+ export type GetServerSidePropsContext = ServerPropsContext;
15
+ export type GetServerSidePropsResult<Props = Record<string, unknown>> = ServerPropsResult<Props>;
16
+ export type GetServerSideProps<Props = Record<string, unknown>> = HyperGetServerProps<Props>;
17
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,IAAI,mBAAmB,EACrC,cAAc,IAAI,mBAAmB,EACrC,cAAc,IAAI,mBAAmB,EACrC,eAAe,EACf,aAAa,EACb,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EAClB,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,eAAe,GAAG,eAAe,CAAC;AAC9C,MAAM,MAAM,QAAQ,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,aAAa,CAAC,KAAK,CAAC,CAAC;AAC7E,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AACxD,MAAM,MAAM,qBAAqB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC;AAC9F,MAAM,MAAM,qBAAqB,GAAG,iBAAiB,CAAC;AACtD,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AACxD,MAAM,MAAM,qBAAqB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC;AAE9F,MAAM,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AACvD,MAAM,MAAM,oBAAoB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC;AAC7F,MAAM,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AACrD,MAAM,MAAM,cAAc,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,mBAAmB,CAAC,KAAK,CAAC,CAAC;AACzF,MAAM,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAEjD,MAAM,MAAM,yBAAyB,GAAG,kBAAkB,CAAC;AAC3D,MAAM,MAAM,wBAAwB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAC;AACjG,MAAM,MAAM,kBAAkB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,mBAAmB,CAAC,KAAK,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@tyndall/react",
3
+ "version": "0.0.1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "bun": "./src/index.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json"
22
+ },
23
+ "dependencies": {
24
+ "@tyndall/core": "workspace:*",
25
+ "@tyndall/shared": "workspace:*",
26
+ "react-router": "^7.13.0"
27
+ },
28
+ "peerDependencies": {
29
+ "react": "^18.2.0",
30
+ "react-dom": "^18.2.0"
31
+ }
32
+ }