hono-preact 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/iso/action-result-context.d.ts +22 -0
  2. package/dist/iso/action-result-context.js +2 -0
  3. package/dist/iso/action.d.ts +52 -13
  4. package/dist/iso/action.js +204 -88
  5. package/dist/iso/cache.d.ts +9 -0
  6. package/dist/iso/cache.js +26 -0
  7. package/dist/iso/define-app.d.ts +7 -0
  8. package/dist/iso/define-loader.d.ts +12 -0
  9. package/dist/iso/define-loader.js +26 -16
  10. package/dist/iso/form.d.ts +13 -4
  11. package/dist/iso/form.js +115 -33
  12. package/dist/iso/index.d.ts +13 -4
  13. package/dist/iso/index.js +14 -2
  14. package/dist/iso/internal/action-envelope.d.ts +37 -0
  15. package/dist/iso/internal/action-envelope.js +47 -0
  16. package/dist/iso/internal/action-result-store.d.ts +28 -0
  17. package/dist/iso/internal/action-result-store.js +35 -0
  18. package/dist/iso/internal/envelope.js +1 -2
  19. package/dist/iso/internal/form-submit-store.d.ts +9 -0
  20. package/dist/iso/internal/form-submit-store.js +32 -0
  21. package/dist/iso/internal/history-shim.d.ts +7 -0
  22. package/dist/iso/internal/history-shim.js +79 -0
  23. package/dist/iso/internal/loader-fetch.js +65 -34
  24. package/dist/iso/internal/loader.d.ts +3 -3
  25. package/dist/iso/internal/merge-refs.d.ts +4 -0
  26. package/dist/iso/internal/merge-refs.js +14 -0
  27. package/dist/iso/internal/persist-registry.d.ts +10 -0
  28. package/dist/iso/internal/persist-registry.js +24 -0
  29. package/dist/iso/internal/route-boundary.d.ts +4 -4
  30. package/dist/iso/internal/route-change.d.ts +8 -2
  31. package/dist/iso/internal/route-change.js +107 -12
  32. package/dist/iso/internal/safe-redirect.d.ts +7 -0
  33. package/dist/iso/internal/safe-redirect.js +27 -0
  34. package/dist/iso/internal/sse-decoder.d.ts +1 -1
  35. package/dist/iso/internal/sse-decoder.js +40 -26
  36. package/dist/iso/internal/use-render.d.ts +11 -0
  37. package/dist/iso/internal/use-render.js +47 -0
  38. package/dist/iso/internal/view-transition-event.d.ts +23 -0
  39. package/dist/iso/internal/view-transition-event.js +25 -0
  40. package/dist/iso/internal.d.ts +12 -1
  41. package/dist/iso/internal.js +13 -1
  42. package/dist/iso/optimistic-action.d.ts +10 -1
  43. package/dist/iso/optimistic-action.js +11 -3
  44. package/dist/iso/optimistic.d.ts +10 -1
  45. package/dist/iso/optimistic.js +45 -5
  46. package/dist/iso/outcomes.d.ts +14 -2
  47. package/dist/iso/outcomes.js +14 -3
  48. package/dist/iso/persist.d.ts +14 -0
  49. package/dist/iso/persist.js +56 -0
  50. package/dist/iso/use-action-result.d.ts +25 -0
  51. package/dist/iso/use-action-result.js +39 -0
  52. package/dist/iso/use-form-status.d.ts +5 -0
  53. package/dist/iso/use-form-status.js +13 -0
  54. package/dist/iso/view-transition-lifecycle.d.ts +9 -0
  55. package/dist/iso/view-transition-lifecycle.js +18 -0
  56. package/dist/iso/view-transition-name.d.ts +17 -0
  57. package/dist/iso/view-transition-name.js +79 -0
  58. package/dist/iso/view-transition-types.d.ts +8 -0
  59. package/dist/iso/view-transition-types.js +21 -0
  60. package/dist/server/actions-handler.d.ts +7 -0
  61. package/dist/server/actions-handler.js +42 -9
  62. package/dist/server/index.d.ts +2 -1
  63. package/dist/server/index.js +2 -1
  64. package/dist/server/loaders-handler.d.ts +8 -0
  65. package/dist/server/loaders-handler.js +37 -4
  66. package/dist/server/page-action-handler.d.ts +63 -0
  67. package/dist/server/page-action-handler.js +274 -0
  68. package/dist/server/page-action-resolvers.d.ts +28 -0
  69. package/dist/server/page-action-resolvers.js +147 -0
  70. package/dist/server/render.js +136 -55
  71. package/dist/server/route-server-modules.d.ts +7 -8
  72. package/dist/server/route-server-modules.js +7 -8
  73. package/dist/server/speculation-rules.d.ts +3 -0
  74. package/dist/server/speculation-rules.js +8 -0
  75. package/dist/server/sse.d.ts +43 -28
  76. package/dist/server/sse.js +113 -88
  77. package/dist/vite/client-entry.js +12 -3
  78. package/dist/vite/server-entry.js +10 -2
  79. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { readSSE } from './sse-decoder.js';
2
+ import { TimeoutError } from '../action.js';
2
3
  /**
3
4
  * POST to /__loaders and consume the response.
4
5
  *
@@ -18,6 +19,9 @@ export async function fetchLoaderData(moduleKey, loaderName, location, signal, c
18
19
  // Try to parse a deny outcome envelope first; it carries `message`
19
20
  // rather than `error`. Fall back to the legacy `{ error }` shape.
20
21
  const body = (await res.json().catch(() => ({})));
22
+ if (body.__outcome === 'timeout' && typeof body.timeoutMs === 'number') {
23
+ throw new TimeoutError(body.timeoutMs);
24
+ }
21
25
  if (body.__outcome === 'deny') {
22
26
  // The `deny()` constructor defaults `message` for first-party
23
27
  // callers, but a hand-rolled envelope from custom server middleware
@@ -74,40 +78,7 @@ export async function fetchLoaderData(moduleKey, loaderName, location, signal, c
74
78
  // SSE: read the first message event synchronously (await first chunk),
75
79
  // then kick off an async loop that pushes subsequent chunks to callbacks.
76
80
  const iter = readSSE(res.body);
77
- let firstChunk;
78
- while (true) {
79
- const step = await iter.next();
80
- if (step.done) {
81
- // Stream closed before any data event: error
82
- throw new Error('Streaming loader closed before emitting any data');
83
- }
84
- const ev = step.value;
85
- if (ev.event === 'message') {
86
- try {
87
- firstChunk = JSON.parse(ev.data);
88
- }
89
- catch {
90
- throw new Error('Malformed first chunk in streaming loader');
91
- }
92
- break;
93
- }
94
- if (ev.event === 'error') {
95
- try {
96
- const parsed = JSON.parse(ev.data);
97
- const err = new Error(parsed.message ?? 'Streamed error');
98
- if (parsed.name)
99
- err.name = parsed.name;
100
- throw err;
101
- }
102
- catch (e) {
103
- if (e instanceof Error && e.message.startsWith('Malformed')) {
104
- throw new Error('Malformed error event in streaming loader');
105
- }
106
- throw e;
107
- }
108
- }
109
- // Other events (result, etc.): ignore for loaders
110
- }
81
+ const firstChunk = await readFirstChunk(iter);
111
82
  // Continue consuming chunks in the background. Each subsequent message
112
83
  // pushes a value via onChunk. Errors fire onError. End fires onEnd.
113
84
  (async () => {
@@ -127,6 +98,16 @@ export async function fetchLoaderData(moduleKey, loaderName, location, signal, c
127
98
  // malformed mid-stream chunk: skip
128
99
  }
129
100
  }
101
+ else if (ev.event === 'timeout') {
102
+ try {
103
+ const parsed = JSON.parse(ev.data);
104
+ callbacks.onError(new TimeoutError(parsed.timeoutMs ?? 0));
105
+ }
106
+ catch {
107
+ callbacks.onError(new Error('Malformed timeout event in streaming loader'));
108
+ }
109
+ return;
110
+ }
130
111
  else if (ev.event === 'error') {
131
112
  try {
132
113
  const parsed = JSON.parse(ev.data);
@@ -151,3 +132,53 @@ export async function fetchLoaderData(moduleKey, loaderName, location, signal, c
151
132
  })();
152
133
  return firstChunk;
153
134
  }
135
+ /**
136
+ * Drain the SSE stream until the first `message` event and parse it as `T`.
137
+ * `timeout` / `error` events before the first message reject with the
138
+ * appropriate error. Other event types are ignored.
139
+ */
140
+ async function readFirstChunk(iter) {
141
+ while (true) {
142
+ const step = await iter.next();
143
+ if (step.done) {
144
+ throw new Error('Streaming loader closed before emitting any data');
145
+ }
146
+ const ev = step.value;
147
+ if (ev.event === 'message') {
148
+ try {
149
+ return JSON.parse(ev.data);
150
+ }
151
+ catch {
152
+ throw new Error('Malformed first chunk in streaming loader');
153
+ }
154
+ }
155
+ if (ev.event === 'timeout') {
156
+ let timeoutMs = 0;
157
+ try {
158
+ const parsed = JSON.parse(ev.data);
159
+ timeoutMs = parsed.timeoutMs ?? 0;
160
+ }
161
+ catch (e) {
162
+ throw new Error(`Malformed timeout event in streaming loader: ${e instanceof Error ? e.message : String(e)}`);
163
+ }
164
+ throw new TimeoutError(timeoutMs);
165
+ }
166
+ if (ev.event === 'error') {
167
+ let message = 'Streamed error';
168
+ let name;
169
+ try {
170
+ const parsed = JSON.parse(ev.data);
171
+ message = parsed.message ?? message;
172
+ name = parsed.name;
173
+ }
174
+ catch {
175
+ throw new Error('Malformed error event in streaming loader');
176
+ }
177
+ const err = new Error(message);
178
+ if (name)
179
+ err.name = name;
180
+ throw err;
181
+ }
182
+ // Other events (result, etc.): ignore for loaders
183
+ }
184
+ }
@@ -1,13 +1,13 @@
1
- import type { ComponentChildren, JSX } from 'preact';
1
+ import type { ComponentChildren } from 'preact';
2
2
  import type { RouteHook } from 'preact-iso';
3
3
  import type { LoaderRef } from '../define-loader.js';
4
4
  export { serializeLocationForCache } from './cache-key.js';
5
5
  type LoaderHostProps<T> = {
6
6
  loader: LoaderRef<T>;
7
7
  location?: RouteHook;
8
- fallback?: JSX.Element;
8
+ fallback?: ComponentChildren;
9
9
  errorFallback?: ComponentChildren | ((err: Error, reset: () => void) => ComponentChildren);
10
10
  children: ComponentChildren;
11
11
  };
12
- export declare function LoaderHost<T>({ loader: loaderRef, location: locationProp, fallback, errorFallback, children, }: LoaderHostProps<T>): JSX.Element;
12
+ export declare function LoaderHost<T>({ loader: loaderRef, location: locationProp, fallback, errorFallback, children, }: LoaderHostProps<T>): import("preact").JSX.Element;
13
13
  export { LoaderHost as Loader };
@@ -0,0 +1,4 @@
1
+ import type { Ref } from 'preact';
2
+ type AnyRef<T> = Ref<T> | null | undefined;
3
+ export declare function mergeRefs<T>(...refs: AnyRef<T>[]): (node: T | null) => void;
4
+ export {};
@@ -0,0 +1,14 @@
1
+ export function mergeRefs(...refs) {
2
+ return (node) => {
3
+ for (const ref of refs) {
4
+ if (ref == null)
5
+ continue;
6
+ if (typeof ref === 'function') {
7
+ ref(node);
8
+ }
9
+ else {
10
+ ref.current = node;
11
+ }
12
+ }
13
+ };
14
+ }
@@ -0,0 +1,10 @@
1
+ import type { ComponentChildren } from 'preact';
2
+ export interface PersistEntry {
3
+ children: ComponentChildren;
4
+ viewTransitionName: string | undefined;
5
+ }
6
+ export declare function __persistRegistryWrite(id: string, entry: PersistEntry): void;
7
+ export declare function __persistRegistryRead(): ReadonlyMap<string, PersistEntry>;
8
+ export declare function __persistRegistrySubscribe(sub: () => void): () => void;
9
+ /** Test-only reset. Do not call from production code. */
10
+ export declare function __persistRegistryResetForTesting(): void;
@@ -0,0 +1,24 @@
1
+ let map = new Map();
2
+ const subs = new Set();
3
+ export function __persistRegistryWrite(id, entry) {
4
+ // Replace the map reference so consumers using identity-checks can detect.
5
+ const next = new Map(map);
6
+ next.set(id, entry);
7
+ map = next;
8
+ for (const sub of subs)
9
+ sub();
10
+ }
11
+ export function __persistRegistryRead() {
12
+ return map;
13
+ }
14
+ export function __persistRegistrySubscribe(sub) {
15
+ subs.add(sub);
16
+ return () => {
17
+ subs.delete(sub);
18
+ };
19
+ }
20
+ /** Test-only reset. Do not call from production code. */
21
+ export function __persistRegistryResetForTesting() {
22
+ map = new Map();
23
+ subs.clear();
24
+ }
@@ -1,6 +1,6 @@
1
1
  import { Component } from 'preact';
2
- import type { ComponentChildren, FunctionComponent, JSX } from 'preact';
3
- type ErrorFallback = JSX.Element | ((error: Error, reset: () => void) => JSX.Element);
2
+ import type { ComponentChildren, FunctionComponent } from 'preact';
3
+ type ErrorFallback = ComponentChildren | ((error: Error, reset: () => void) => ComponentChildren);
4
4
  type ErrorBoundaryProps = {
5
5
  fallback?: ErrorFallback;
6
6
  children: ComponentChildren;
@@ -16,10 +16,10 @@ export declare class ErrorBoundary extends Component<ErrorBoundaryProps, {
16
16
  };
17
17
  componentDidCatch(error: unknown): void;
18
18
  reset: () => void;
19
- render(): ComponentChildren;
19
+ render(): any;
20
20
  }
21
21
  export declare const RouteBoundary: FunctionComponent<{
22
- fallback?: JSX.Element;
22
+ fallback?: ComponentChildren;
23
23
  errorFallback?: ErrorFallback;
24
24
  children: ComponentChildren;
25
25
  }>;
@@ -1,4 +1,10 @@
1
- type Sub = (to: string, from: string | undefined) => void;
1
+ import { ViewTransitionEvent } from './view-transition-event.js';
2
+ export type PhaseName = 'beforeTransition' | 'beforeSwap' | 'afterSwap' | 'afterTransition';
3
+ type PhaseSub = (event: ViewTransitionEvent) => void | Promise<void>;
4
+ type LegacySub = (to: string, from: string | undefined) => void;
5
+ export declare function __subscribePhase(phase: PhaseName, sub: PhaseSub): () => void;
6
+ export declare function __subscribeRouteChange(sub: LegacySub): () => void;
2
7
  export declare function __dispatchRouteChange(to: string, from: string | undefined): void;
3
- export declare function __subscribeRouteChange(sub: Sub): () => void;
8
+ /** @internal Test-only reset for default-types installer. */
9
+ export declare function resetDefaultTypesForTesting(): void;
4
10
  export {};
@@ -1,18 +1,113 @@
1
1
  import { flushSync } from 'preact/compat';
2
- const subs = new Set();
3
- export function __dispatchRouteChange(to, from) {
4
- for (const cb of subs)
5
- cb(to, from);
6
- if (typeof document === 'undefined')
7
- return;
8
- const startViewTransition = document.startViewTransition;
9
- if (typeof startViewTransition !== 'function')
10
- return;
11
- startViewTransition.call(document, () => flushSync(() => { }));
2
+ import { ViewTransitionEvent, } from './view-transition-event.js';
3
+ import { getNavDirection } from './history-shim.js';
4
+ const phaseSubs = {
5
+ beforeTransition: new Set(),
6
+ beforeSwap: new Set(),
7
+ afterSwap: new Set(),
8
+ afterTransition: new Set(),
9
+ };
10
+ const legacySubs = new Set();
11
+ export function __subscribePhase(phase, sub) {
12
+ phaseSubs[phase].add(sub);
13
+ return () => {
14
+ phaseSubs[phase].delete(sub);
15
+ };
12
16
  }
13
17
  export function __subscribeRouteChange(sub) {
14
- subs.add(sub);
18
+ legacySubs.add(sub);
15
19
  return () => {
16
- subs.delete(sub);
20
+ legacySubs.delete(sub);
21
+ };
22
+ }
23
+ function fireLegacy(to, from) {
24
+ for (const sub of legacySubs)
25
+ sub(to, from);
26
+ }
27
+ function getStartViewTransition() {
28
+ if (typeof document === 'undefined')
29
+ return undefined;
30
+ const fn = document.startViewTransition;
31
+ return typeof fn === 'function' ? fn.bind(document) : undefined;
32
+ }
33
+ export function __dispatchRouteChange(to, from) {
34
+ ensureDefaultTypes();
35
+ const direction = getNavDirection();
36
+ const event = new ViewTransitionEvent({ to, from, direction });
37
+ for (const sub of phaseSubs.beforeTransition)
38
+ sub(event);
39
+ const fireAfterSwap = () => {
40
+ for (const sub of phaseSubs.afterSwap)
41
+ sub(event);
42
+ // Legacy subscribers fire at the afterSwap slot: after the DOM swap,
43
+ // before the browser begins animating the new frame.
44
+ fireLegacy(to, from);
17
45
  };
46
+ const fireAfterTransition = (reason) => {
47
+ if (reason !== undefined)
48
+ event.reason = reason;
49
+ for (const sub of phaseSubs.afterTransition)
50
+ sub(event);
51
+ };
52
+ if (event._skipped) {
53
+ flushSync(() => { });
54
+ fireAfterSwap();
55
+ fireAfterTransition('skipped');
56
+ return;
57
+ }
58
+ const start = getStartViewTransition();
59
+ if (!start) {
60
+ flushSync(() => { });
61
+ fireAfterSwap();
62
+ fireAfterTransition('unsupported');
63
+ return;
64
+ }
65
+ const transition = start(() => {
66
+ flushSync(() => { });
67
+ });
68
+ // Set event.transition before firing beforeSwap so all subsequent phase
69
+ // subscribers see a non-null transition. In real browsers startViewTransition
70
+ // invokes the callback asynchronously, meaning transition is set here before
71
+ // the browser calls the update function. In synchronous test mocks the
72
+ // callback returns before start() does, so we set transition here (after
73
+ // start() returns) and then fire the post-swap phases manually.
74
+ event.transition = transition;
75
+ for (const sub of phaseSubs.beforeSwap)
76
+ sub(event);
77
+ fireAfterSwap();
78
+ // Apply types accumulated across all phases. beforeTransition and beforeSwap
79
+ // have both run by this point, so the full set of types is available here.
80
+ const vtTypes = transition.types;
81
+ if (vtTypes && typeof vtTypes.add === 'function') {
82
+ for (const t of event.types)
83
+ vtTypes.add(t);
84
+ }
85
+ transition.finished.then(() => fireAfterTransition(), () => fireAfterTransition('aborted'));
86
+ }
87
+ let defaultTypesInstalled = false;
88
+ let firstDispatchSeen = false;
89
+ let defaultTypeUnsubscriber = null;
90
+ function ensureDefaultTypes() {
91
+ if (defaultTypesInstalled)
92
+ return;
93
+ defaultTypesInstalled = true;
94
+ defaultTypeUnsubscriber = __subscribePhase('beforeTransition', (event) => {
95
+ if (!firstDispatchSeen) {
96
+ event.types.push('nav-initial');
97
+ firstDispatchSeen = true;
98
+ }
99
+ else {
100
+ event.types.push(`nav-${event.direction}`);
101
+ }
102
+ event.types.push('nav-same-origin');
103
+ });
104
+ }
105
+ /** @internal Test-only reset for default-types installer. */
106
+ export function resetDefaultTypesForTesting() {
107
+ if (defaultTypeUnsubscriber) {
108
+ defaultTypeUnsubscriber();
109
+ }
110
+ defaultTypesInstalled = false;
111
+ firstDispatchSeen = false;
112
+ defaultTypeUnsubscriber = null;
18
113
  }
@@ -0,0 +1,7 @@
1
+ export declare function isSameOrigin(target: string): boolean;
2
+ /**
3
+ * Navigate to `target` only when it resolves same-origin against the current
4
+ * window. Cross-origin targets log a console error and are not followed.
5
+ * Returns true when the navigation was issued.
6
+ */
7
+ export declare function assignSafeRedirect(target: string): boolean;
@@ -0,0 +1,27 @@
1
+ export function isSameOrigin(target) {
2
+ if (typeof window === 'undefined')
3
+ return true;
4
+ try {
5
+ // Relative URLs resolve against the current origin and are always same-origin.
6
+ const resolved = new URL(target, window.location.href);
7
+ return resolved.origin === window.location.origin;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ /**
14
+ * Navigate to `target` only when it resolves same-origin against the current
15
+ * window. Cross-origin targets log a console error and are not followed.
16
+ * Returns true when the navigation was issued.
17
+ */
18
+ export function assignSafeRedirect(target) {
19
+ if (typeof window === 'undefined')
20
+ return false;
21
+ if (!isSameOrigin(target)) {
22
+ console.error(`[hono-preact] refusing to navigate to cross-origin redirect target: ${target}`);
23
+ return false;
24
+ }
25
+ window.location.assign(target);
26
+ return true;
27
+ }
@@ -2,4 +2,4 @@ export type SSEEvent = {
2
2
  event: string;
3
3
  data: string;
4
4
  };
5
- export declare function readSSE(stream: ReadableStream<Uint8Array>): AsyncGenerator<SSEEvent, void, unknown>;
5
+ export declare function readSSE(stream: ReadableStream<BufferSource>): AsyncGenerator<SSEEvent, void, unknown>;
@@ -1,39 +1,53 @@
1
- export async function* readSSE(stream) {
2
- const reader = stream.getReader();
3
- const decoder = new TextDecoder();
1
+ function lineSplitTransform() {
4
2
  let buffer = '';
3
+ return new TransformStream({
4
+ transform(chunk, controller) {
5
+ buffer += chunk;
6
+ let nl;
7
+ while ((nl = buffer.indexOf('\n')) !== -1) {
8
+ controller.enqueue(buffer.slice(0, nl).replace(/\r$/, ''));
9
+ buffer = buffer.slice(nl + 1);
10
+ }
11
+ },
12
+ flush(controller) {
13
+ if (buffer.length) {
14
+ controller.enqueue(buffer.replace(/\r$/, ''));
15
+ buffer = '';
16
+ }
17
+ },
18
+ });
19
+ }
20
+ export async function* readSSE(stream) {
21
+ const lines = stream
22
+ .pipeThrough(new TextDecoderStream())
23
+ .pipeThrough(lineSplitTransform());
5
24
  let event = 'message';
6
25
  let dataLines = [];
26
+ const reader = lines.getReader();
7
27
  try {
8
28
  while (true) {
9
- const { done, value } = await reader.read();
29
+ const { done, value: line } = await reader.read();
10
30
  if (done) {
11
- buffer += decoder.decode();
12
- if (dataLines.length)
31
+ if (dataLines.length) {
13
32
  yield { event, data: dataLines.join('\n') };
33
+ }
14
34
  return;
15
35
  }
16
- buffer += decoder.decode(value, { stream: true });
17
- let nl;
18
- while ((nl = buffer.indexOf('\n')) !== -1) {
19
- const line = buffer.slice(0, nl).replace(/\r$/, '');
20
- buffer = buffer.slice(nl + 1);
21
- if (line === '') {
22
- if (dataLines.length) {
23
- yield { event, data: dataLines.join('\n') };
24
- }
25
- event = 'message';
26
- dataLines = [];
27
- }
28
- else if (line.startsWith(':')) {
29
- // SSE comment / keepalive, ignore
30
- }
31
- else if (line.startsWith('event:')) {
32
- event = line.slice(6).trim();
33
- }
34
- else if (line.startsWith('data:')) {
35
- dataLines.push(line.slice(5).replace(/^ /, ''));
36
+ if (line === '') {
37
+ if (dataLines.length) {
38
+ yield { event, data: dataLines.join('\n') };
36
39
  }
40
+ event = 'message';
41
+ dataLines = [];
42
+ }
43
+ else if (line.startsWith(':')) {
44
+ // SSE comment / keepalive, ignore
45
+ }
46
+ else if (line.startsWith('event:')) {
47
+ event = line.slice(6).trim();
48
+ }
49
+ else if (line.startsWith('data:')) {
50
+ dataLines.push(line.slice(5).replace(/^ /, ''));
37
51
  }
38
52
  }
39
53
  }
@@ -0,0 +1,11 @@
1
+ import { type ComponentChildren, type VNode } from 'preact';
2
+ type Props = Record<string, unknown>;
3
+ export type UseRenderRender = VNode | string | ((props: Props) => VNode) | undefined;
4
+ interface UseRenderOptions {
5
+ render?: UseRenderRender;
6
+ defaultTag: string;
7
+ props: Props;
8
+ children?: ComponentChildren;
9
+ }
10
+ export declare function useRender(opts: UseRenderOptions): VNode;
11
+ export {};
@@ -0,0 +1,47 @@
1
+ import { cloneElement, h, } from 'preact';
2
+ import { mergeRefs } from './merge-refs.js';
3
+ function joinClass(a, b) {
4
+ const parts = [];
5
+ if (typeof a === 'string' && a.length > 0)
6
+ parts.push(a);
7
+ if (typeof b === 'string' && b.length > 0)
8
+ parts.push(b);
9
+ if (parts.length === 0)
10
+ return undefined;
11
+ return parts.join(' ');
12
+ }
13
+ function mergeProps(user, framework) {
14
+ const out = { ...user };
15
+ for (const key of Object.keys(framework)) {
16
+ if (key === 'class' || key === 'className') {
17
+ const userClass = (user.class ?? user.className);
18
+ const merged = joinClass(userClass, framework[key]);
19
+ if (merged !== undefined)
20
+ out.class = merged;
21
+ delete out.className;
22
+ }
23
+ else if (key === 'ref') {
24
+ out.ref = mergeRefs(user.ref, framework.ref);
25
+ }
26
+ else {
27
+ out[key] = framework[key];
28
+ }
29
+ }
30
+ return out;
31
+ }
32
+ export function useRender(opts) {
33
+ const { render, defaultTag, props, children } = opts;
34
+ if (typeof render === 'function') {
35
+ return render(mergeProps({}, props));
36
+ }
37
+ if (render && typeof render === 'object' && 'type' in render) {
38
+ const merged = mergeProps((render.props ?? {}), props);
39
+ const mergedChildren = children !== undefined
40
+ ? children
41
+ : (render.props?.children ??
42
+ null);
43
+ return cloneElement(render, merged, mergedChildren);
44
+ }
45
+ const tag = typeof render === 'string' ? render : defaultTag;
46
+ return h(tag, props, children);
47
+ }
@@ -0,0 +1,23 @@
1
+ export type NavDirection = 'initial' | 'push' | 'replace' | 'back' | 'forward';
2
+ export type ViewTransitionReason = 'skipped' | 'unsupported' | 'aborted';
3
+ interface ViewTransitionEventInit {
4
+ to: string;
5
+ from: string | undefined;
6
+ direction: NavDirection;
7
+ }
8
+ export declare class ViewTransitionEvent {
9
+ readonly to: string;
10
+ readonly from: string | undefined;
11
+ readonly direction: NavDirection;
12
+ readonly types: string[];
13
+ transition: ViewTransition | null;
14
+ reason: ViewTransitionReason | undefined;
15
+ /** @internal */
16
+ _skipped: boolean;
17
+ private readonly stash;
18
+ constructor(init: ViewTransitionEventInit);
19
+ skip(): void;
20
+ set(key: unknown, value: unknown): void;
21
+ get<T = unknown>(key: unknown): T | undefined;
22
+ }
23
+ export {};
@@ -0,0 +1,25 @@
1
+ export class ViewTransitionEvent {
2
+ to;
3
+ from;
4
+ direction;
5
+ types = [];
6
+ transition = null;
7
+ reason = undefined;
8
+ /** @internal */
9
+ _skipped = false;
10
+ stash = new Map();
11
+ constructor(init) {
12
+ this.to = init.to;
13
+ this.from = init.from;
14
+ this.direction = init.direction;
15
+ }
16
+ skip() {
17
+ this._skipped = true;
18
+ }
19
+ set(key, value) {
20
+ this.stash.set(key, value);
21
+ }
22
+ get(key) {
23
+ return this.stash.get(key);
24
+ }
25
+ }
@@ -2,19 +2,30 @@ export { Loader } from './internal/loader.js';
2
2
  export { Envelope } from './internal/envelope.js';
3
3
  export { RouteBoundary } from './internal/route-boundary.js';
4
4
  export { OptimisticOverlay } from './internal/optimistic-overlay.js';
5
+ export { serializeActionOutcome, type ActionEnvelope, type ActionResolution, type SerializedEnvelope, } from './internal/action-envelope.js';
5
6
  export { LoaderIdContext, LoaderDataContext } from './internal/contexts.js';
6
7
  export { ReloadContext } from './reload-context.js';
7
8
  export { RouteLocationsContext, RouteLocationsProvider, } from './internal/route-locations.js';
8
9
  export { getPreloadedData, deletePreloadedData } from './internal/preload.js';
9
- export { runRequestScope, getRequestStore, captureRequestScope, } from './cache.js';
10
+ export { runRequestScope, getRequestStore, captureRequestScope, getActionResultSlot, setActionResultSlot, type ActionResultSlot, } from './cache.js';
10
11
  export { default as wrapPromise } from './internal/wrap-promise.js';
11
12
  export { HonoRequestContext } from './internal/contexts.js';
12
13
  export { PageMiddlewareHost } from './internal/page-middleware-host.js';
13
14
  export { __dispatchRouteChange, __subscribeRouteChange, } from './internal/route-change.js';
15
+ export { installHistoryShim, getNavDirection, } from './internal/history-shim.js';
16
+ export { __subscribePhase, type PhaseName } from './internal/route-change.js';
17
+ export { ViewTransitionEvent, type NavDirection, type ViewTransitionReason, } from './internal/view-transition-event.js';
18
+ export { useRender, type UseRenderRender } from './internal/use-render.js';
19
+ export { mergeRefs } from './internal/merge-refs.js';
14
20
  export { installStreamRegistry, subscribeToLoaderStream, } from './internal/stream-registry.js';
15
21
  export { registerServerStreamingLoader, takeServerStreamingLoaders, } from './internal/streaming-ssr.js';
16
22
  export type { ServerLoaderStream } from './internal/streaming-ssr.js';
23
+ export { beginSubmit, endSubmit, isPending, subscribe as subscribeFormSubmit, } from './internal/form-submit-store.js';
24
+ export { assignSafeRedirect, isSameOrigin } from './internal/safe-redirect.js';
25
+ export { setLastActionResult, clearLastActionResult, getLastActionResult, subscribeActionResults, type StoredActionResult, } from './internal/action-result-store.js';
17
26
  export { dispatchServer, dispatchClient, type DispatchResult, } from './internal/middleware-runner.js';
18
27
  export { partitionUse } from './internal/use-partitioner.js';
19
28
  export { fanStart, fanChunk, fanEnd, fanError, fanAbort, } from './internal/stream-observer-runner.js';
20
29
  export { __$createLoaderStub_hpiso } from './internal/loader-stub.js';
30
+ export { readSSE } from './internal/sse-decoder.js';
31
+ export type { SSEEvent } from './internal/sse-decoder.js';