@reckona/mreact-compat 0.0.65 → 0.0.67

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,314 @@
1
+ import type { ReactCompatNode } from "./element.js";
2
+
3
+ export interface HydrationRecoverableErrorInfo {
4
+ kind: "tag" | "text" | "attribute" | "node" | "suspense-server-error";
5
+ path: string;
6
+ componentStack?: string;
7
+ }
8
+
9
+ export interface HydrationContext {
10
+ onRecoverableError?: (
11
+ error: Error,
12
+ info: HydrationRecoverableErrorInfo,
13
+ ) => void;
14
+ componentStack?: string;
15
+ }
16
+
17
+ export interface RenderOptions {
18
+ hydration?: HydrationContext;
19
+ eventRoot?: Element;
20
+ preserveHydrationAttributes?: boolean;
21
+ }
22
+
23
+ export interface HydrationScope {
24
+ parent: ParentNode;
25
+ previousNodes: Node[];
26
+ before: ChildNode | null;
27
+ after: ChildNode | null;
28
+ }
29
+
30
+ export function applyStreamingHydrationFragments(
31
+ root: ParentNode = document,
32
+ ): void {
33
+ const fragments = Array.from(
34
+ root.querySelectorAll<HTMLTemplateElement>(
35
+ "template[data-mreact-oob-fragment]",
36
+ ),
37
+ );
38
+
39
+ for (const fragment of fragments) {
40
+ const id = fragment.dataset.mreactOobFragment;
41
+
42
+ if (id === undefined) {
43
+ continue;
44
+ }
45
+
46
+ const placeholder = root.querySelector<Element>(
47
+ `[data-mreact-oob-placeholder="${escapeSelectorString(id)}"]`,
48
+ );
49
+
50
+ if (placeholder === null) {
51
+ continue;
52
+ }
53
+
54
+ placeholder.replaceWith(fragment.content.cloneNode(true));
55
+ fragment.remove();
56
+ }
57
+ }
58
+
59
+ export function getHydrationScope(
60
+ container: Element,
61
+ resumeId: string | undefined,
62
+ ): HydrationScope {
63
+ if (resumeId === undefined) {
64
+ return {
65
+ parent: container,
66
+ previousNodes: Array.from(container.childNodes),
67
+ before: null,
68
+ after: null,
69
+ };
70
+ }
71
+
72
+ const encodedId = encodeURIComponent(resumeId);
73
+ const start = findComment(container, `mreact-h:start:${encodedId}`);
74
+ const end =
75
+ start === null ? null : findFollowingComment(start, `mreact-h:end:${encodedId}`);
76
+
77
+ if (start === null || end === null || start.parentNode === null) {
78
+ return {
79
+ parent: container,
80
+ previousNodes: Array.from(container.childNodes),
81
+ before: null,
82
+ after: null,
83
+ };
84
+ }
85
+
86
+ return {
87
+ parent: start.parentNode,
88
+ previousNodes: collectScopedNodes(start.parentNode, start, end),
89
+ before: start,
90
+ after: end,
91
+ };
92
+ }
93
+
94
+ export function findContainingResumeBoundaryId(
95
+ root: ParentNode,
96
+ target: Node,
97
+ ): string | undefined {
98
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
99
+ const stack: string[] = [];
100
+
101
+ while (walker.nextNode()) {
102
+ const node = walker.currentNode;
103
+
104
+ if (!(node instanceof Comment)) {
105
+ continue;
106
+ }
107
+
108
+ if (node.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_PRECEDING) {
109
+ break;
110
+ }
111
+
112
+ const startId = readResumeMarkerId(node.data, "mreact-h:start:");
113
+
114
+ if (startId !== undefined) {
115
+ stack.push(startId);
116
+ continue;
117
+ }
118
+
119
+ const endId = readResumeMarkerId(node.data, "mreact-h:end:");
120
+
121
+ if (endId !== undefined) {
122
+ const lastIndex = stack.lastIndexOf(endId);
123
+
124
+ if (lastIndex >= 0) {
125
+ stack.length = lastIndex;
126
+ }
127
+ }
128
+ }
129
+
130
+ return stack.at(-1);
131
+ }
132
+
133
+ export function collectScopedNodes(
134
+ parent: ParentNode,
135
+ before: ChildNode | null,
136
+ after: ChildNode | null,
137
+ ): Node[] {
138
+ const nodes: Node[] = [];
139
+ let cursor = before === null ? parent.firstChild : before.nextSibling;
140
+
141
+ while (cursor !== null && cursor !== after) {
142
+ nodes.push(cursor);
143
+ cursor = cursor.nextSibling;
144
+ }
145
+
146
+ return nodes;
147
+ }
148
+
149
+ export function escapeSelectorString(value: string): string {
150
+ return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
151
+ }
152
+
153
+ export function reportRecoverable(
154
+ options: RenderOptions,
155
+ kind: HydrationRecoverableErrorInfo["kind"],
156
+ path: string,
157
+ error: Error,
158
+ ): void {
159
+ const componentStack = options.hydration?.componentStack;
160
+ options.hydration?.onRecoverableError?.(error, {
161
+ kind,
162
+ path,
163
+ ...(componentStack === undefined ? {} : { componentStack }),
164
+ });
165
+ }
166
+
167
+ export function reportMissingHydrationNode(
168
+ options: RenderOptions,
169
+ path: string,
170
+ ): void {
171
+ reportRecoverable(
172
+ options,
173
+ "node",
174
+ path,
175
+ new Error("Hydration missing node mismatch."),
176
+ );
177
+ }
178
+
179
+ export function reportReactSuspenseServerError(
180
+ options: RenderOptions,
181
+ path: string,
182
+ message: string,
183
+ componentStack: string | undefined,
184
+ ): void {
185
+ options.hydration?.onRecoverableError?.(new Error(message), {
186
+ kind: "suspense-server-error",
187
+ path,
188
+ ...(componentStack === undefined ? {} : { componentStack }),
189
+ });
190
+ }
191
+
192
+ export function reportHydrationNodeTypeMismatch(
193
+ options: RenderOptions,
194
+ path: string,
195
+ expected: string,
196
+ existing: Node,
197
+ ): void {
198
+ reportRecoverable(
199
+ options,
200
+ "node",
201
+ path,
202
+ new Error(
203
+ `Hydration node type mismatch: expected ${expected} but found ${describeHydrationNode(existing)}.`,
204
+ ),
205
+ );
206
+ }
207
+
208
+ export function withHydrationComponentStack(
209
+ options: RenderOptions,
210
+ componentName: string,
211
+ ): RenderOptions {
212
+ if (options.hydration === undefined) {
213
+ return options;
214
+ }
215
+
216
+ const frame = `\n at ${componentName}`;
217
+ return {
218
+ ...options,
219
+ hydration: {
220
+ ...options.hydration,
221
+ componentStack: `${options.hydration.componentStack ?? ""}${frame}`,
222
+ },
223
+ };
224
+ }
225
+
226
+ export function reportElementTextMismatch(
227
+ options: RenderOptions,
228
+ path: string,
229
+ existing: HTMLElement,
230
+ children: ReactCompatNode,
231
+ ): void {
232
+ if (
233
+ (typeof children === "string" || typeof children === "number") &&
234
+ existing.textContent !== String(children)
235
+ ) {
236
+ reportRecoverable(
237
+ options,
238
+ "text",
239
+ path,
240
+ new Error("Hydration text mismatch."),
241
+ );
242
+ }
243
+ }
244
+
245
+ export function reportExtraHydrationNodes(
246
+ options: RenderOptions,
247
+ path: string,
248
+ previousNodes: readonly Node[],
249
+ consumed: number,
250
+ ): void {
251
+ if (consumed >= previousNodes.length) {
252
+ return;
253
+ }
254
+
255
+ reportRecoverable(
256
+ options,
257
+ "node",
258
+ path,
259
+ new Error("Hydration extra node mismatch."),
260
+ );
261
+ }
262
+
263
+ function describeHydrationNode(node: Node): string {
264
+ if (node instanceof HTMLElement) {
265
+ return `<${node.tagName.toLowerCase()}>`;
266
+ }
267
+
268
+ if (node instanceof Text) {
269
+ return "text";
270
+ }
271
+
272
+ if (node instanceof Comment) {
273
+ return "comment";
274
+ }
275
+
276
+ return "node";
277
+ }
278
+
279
+ function readResumeMarkerId(
280
+ value: string,
281
+ prefix: "mreact-h:start:" | "mreact-h:end:",
282
+ ): string | undefined {
283
+ return value.startsWith(prefix)
284
+ ? decodeURIComponent(value.slice(prefix.length))
285
+ : undefined;
286
+ }
287
+
288
+ function findComment(root: ParentNode, value: string): Comment | null {
289
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
290
+
291
+ while (walker.nextNode()) {
292
+ const node = walker.currentNode;
293
+
294
+ if (node instanceof Comment && node.data === value) {
295
+ return node;
296
+ }
297
+ }
298
+
299
+ return null;
300
+ }
301
+
302
+ function findFollowingComment(start: Comment, value: string): Comment | null {
303
+ let cursor: Node | null = start.nextSibling;
304
+
305
+ while (cursor !== null) {
306
+ if (cursor instanceof Comment && cursor.data === value) {
307
+ return cursor;
308
+ }
309
+
310
+ cursor = cursor.nextSibling;
311
+ }
312
+
313
+ return null;
314
+ }
package/src/index.ts ADDED
@@ -0,0 +1,95 @@
1
+ export {
2
+ Component,
3
+ PureComponent,
4
+ } from "./class-component.js";
5
+ export {
6
+ Fragment,
7
+ Activity,
8
+ Profiler,
9
+ Suspense,
10
+ SuspenseList,
11
+ StrictMode,
12
+ Children,
13
+ cloneElement,
14
+ createElement,
15
+ createErrorBoundary,
16
+ createPortal,
17
+ createRef,
18
+ forwardRef,
19
+ isValidElement,
20
+ lazy,
21
+ memo,
22
+ } from "./element.js";
23
+ export type {
24
+ ErrorBoundaryOptions,
25
+ ElementType,
26
+ ReactCompatElement,
27
+ ReactCompatNode,
28
+ } from "./element.js";
29
+ export type {
30
+ FormEvent,
31
+ FormEventHandler,
32
+ JSXEvent,
33
+ JSXEventHandler,
34
+ } from "./jsx-runtime.js";
35
+
36
+ export {
37
+ createContext,
38
+ renderContextConsumerToString,
39
+ renderContextProviderToString,
40
+ useContext,
41
+ } from "./context.js";
42
+ export {
43
+ applyStreamingHydrationFragments,
44
+ createRoot,
45
+ createStreamingHydrationRoot,
46
+ enableEventHydrationManifestReplay,
47
+ enableHydrationEventReplay,
48
+ flushSync,
49
+ hydrateRoot,
50
+ queueHydrationEvent,
51
+ readEventHydrationManifest,
52
+ render,
53
+ unmountComponentAtNode,
54
+ } from "./render.js";
55
+ export type {
56
+ EventHydrationManifest,
57
+ EventHydrationManifestEntry,
58
+ HydrateRootOptions,
59
+ HydrationRecoverableErrorInfo,
60
+ Root,
61
+ RootOptions,
62
+ SelectiveHydrationBoundary,
63
+ SelectiveHydrationOptions,
64
+ StreamingHydrationRoot,
65
+ StreamingHydrationRootOptions,
66
+ } from "./render.js";
67
+ export {
68
+ useCallback,
69
+ useDebugValue,
70
+ useDeferredValue,
71
+ useEffectEvent,
72
+ useEffect,
73
+ useId,
74
+ useImperativeHandle,
75
+ useInsertionEffect,
76
+ useLayoutEffect,
77
+ useMemo,
78
+ useOptimistic,
79
+ useReducer,
80
+ useRef,
81
+ useState,
82
+ useSyncExternalStore,
83
+ use,
84
+ useActionState,
85
+ act,
86
+ cache,
87
+ cacheSignal,
88
+ captureOwnerStack,
89
+ renderToString,
90
+ startTransition,
91
+ unstable_useCacheRefresh,
92
+ useTransition,
93
+ version,
94
+ } from "./hooks.js";
95
+ export type { StartTransition, TransitionScope } from "./hooks.js";
@@ -0,0 +1,7 @@
1
+ export {
2
+ createCacheScope,
3
+ refreshCacheScope,
4
+ runWithEventPriority,
5
+ runWithCacheScope,
6
+ type CacheScope,
7
+ } from "./hooks.js";
@@ -0,0 +1,40 @@
1
+ import { Fragment, jsx } from "./jsx-runtime.js";
2
+ import type {
3
+ ElementType,
4
+ ReactCompatElement,
5
+ ReactCompatNode,
6
+ } from "./element.js";
7
+ import type {
8
+ JSXIntrinsicAttributes,
9
+ JSXIntrinsicElements,
10
+ } from "./jsx-runtime.js";
11
+
12
+ export { Fragment };
13
+ export type {
14
+ FormEvent,
15
+ FormEventHandler,
16
+ JSXEvent,
17
+ JSXEventHandler,
18
+ JSXHTMLAttributes,
19
+ JSXIntrinsicAttributes,
20
+ JSXIntrinsicElements,
21
+ } from "./jsx-runtime.js";
22
+
23
+ export namespace JSX {
24
+ export interface Element extends ReactCompatElement {}
25
+
26
+ export interface IntrinsicAttributes extends JSXIntrinsicAttributes {}
27
+
28
+ export interface IntrinsicElements extends JSXIntrinsicElements {}
29
+ }
30
+
31
+ export function jsxDEV<P extends Record<string, unknown>>(
32
+ type: ElementType<P>,
33
+ props: (P & { children?: ReactCompatNode; key?: unknown; ref?: unknown }) | null,
34
+ key: unknown,
35
+ _isStaticChildren: boolean,
36
+ _source: unknown,
37
+ _self: unknown,
38
+ ): ReactCompatElement<P> {
39
+ return jsx(type, props, key);
40
+ }
@@ -0,0 +1,119 @@
1
+ import { createElement, Fragment } from "./element.js";
2
+ import type {
3
+ ElementType,
4
+ ReactCompatElement,
5
+ ReactCompatNode,
6
+ } from "./element.js";
7
+
8
+ export { Fragment };
9
+
10
+ export type JSXEvent<
11
+ TCurrentTarget extends EventTarget,
12
+ TEvent extends Event = Event,
13
+ > = TEvent & {
14
+ readonly currentTarget: TCurrentTarget;
15
+ };
16
+
17
+ export type JSXEventHandler<
18
+ TCurrentTarget extends EventTarget,
19
+ TEvent extends Event = Event,
20
+ > = (event: JSXEvent<TCurrentTarget, TEvent>) => unknown;
21
+
22
+ export type FormEvent<TCurrentTarget extends EventTarget = Element> = JSXEvent<
23
+ TCurrentTarget,
24
+ SubmitEvent
25
+ >;
26
+
27
+ export type FormEventHandler<TCurrentTarget extends EventTarget = Element> =
28
+ JSXEventHandler<TCurrentTarget, SubmitEvent>;
29
+
30
+ export interface JSXDOMAttributes<TElement extends EventTarget> {
31
+ children?: ReactCompatNode;
32
+ onClick?: JSXEventHandler<TElement, MouseEvent>;
33
+ onChange?: JSXEventHandler<TElement, Event>;
34
+ onInput?: JSXEventHandler<TElement, InputEvent>;
35
+ onSubmit?: JSXEventHandler<TElement, SubmitEvent>;
36
+ }
37
+
38
+ export interface JSXHTMLAttributes<TElement extends HTMLElement>
39
+ extends JSXDOMAttributes<TElement> {
40
+ [attributeName: string]: unknown;
41
+ }
42
+
43
+ export interface JSXIntrinsicAttributes {
44
+ key?: unknown;
45
+ ref?: unknown;
46
+ }
47
+
48
+ export interface JSXIntrinsicElements {
49
+ form: JSXHTMLAttributes<HTMLFormElement> & {
50
+ onSubmit?: JSXEventHandler<HTMLFormElement, SubmitEvent>;
51
+ };
52
+ input: JSXHTMLAttributes<HTMLInputElement>;
53
+ button: JSXHTMLAttributes<HTMLButtonElement>;
54
+ textarea: JSXHTMLAttributes<HTMLTextAreaElement>;
55
+ select: JSXHTMLAttributes<HTMLSelectElement>;
56
+ option: JSXHTMLAttributes<HTMLOptionElement>;
57
+ a: JSXHTMLAttributes<HTMLAnchorElement>;
58
+ img: JSXHTMLAttributes<HTMLImageElement>;
59
+ main: JSXHTMLAttributes<HTMLElement>;
60
+ div: JSXHTMLAttributes<HTMLDivElement>;
61
+ span: JSXHTMLAttributes<HTMLSpanElement>;
62
+ [elementName: string]: Record<string, unknown>;
63
+ }
64
+
65
+ export namespace JSX {
66
+ export interface Element extends ReactCompatElement {}
67
+
68
+ export interface IntrinsicAttributes extends JSXIntrinsicAttributes {}
69
+
70
+ export interface IntrinsicElements extends JSXIntrinsicElements {}
71
+ }
72
+
73
+ declare global {
74
+ namespace JSX {
75
+ interface Element extends ReactCompatElement {}
76
+ interface IntrinsicAttributes extends JSXIntrinsicAttributes {}
77
+ interface IntrinsicElements extends JSXIntrinsicElements {}
78
+ }
79
+ }
80
+
81
+ export function jsx<P extends Record<string, unknown>>(
82
+ type: ElementType<P>,
83
+ props: (P & { children?: ReactCompatNode; key?: unknown; ref?: unknown }) | null,
84
+ key?: unknown,
85
+ ): ReactCompatElement<P> {
86
+ return createElementFromJsx(type, props, key);
87
+ }
88
+
89
+ export function jsxs<P extends Record<string, unknown>>(
90
+ type: ElementType<P>,
91
+ props: (P & { children?: ReactCompatNode; key?: unknown; ref?: unknown }) | null,
92
+ key?: unknown,
93
+ ): ReactCompatElement<P> {
94
+ return createElementFromJsx(type, props, key);
95
+ }
96
+
97
+ function createElementFromJsx<P extends Record<string, unknown>>(
98
+ type: ElementType<P>,
99
+ props: (P & { children?: ReactCompatNode; key?: unknown; ref?: unknown }) | null,
100
+ key: unknown,
101
+ ): ReactCompatElement<P> {
102
+ const config = { ...props } as P & {
103
+ children?: ReactCompatNode;
104
+ key?: unknown;
105
+ ref?: unknown;
106
+ };
107
+ const hasChildren = Object.hasOwn(config, "children");
108
+ const children = config.children;
109
+
110
+ if (key !== undefined) {
111
+ config.key = key;
112
+ }
113
+
114
+ delete config.children;
115
+
116
+ return hasChildren
117
+ ? createElement(type, config, children)
118
+ : createElement(type, config);
119
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ isReactCompatElement,
3
+ type MemoType,
4
+ type ReactCompatElement,
5
+ type ReactCompatNode,
6
+ } from "./element.js";
7
+
8
+ export function getPendingProps(node: ReactCompatNode): unknown {
9
+ if (!isReactCompatElement(node)) {
10
+ return node;
11
+ }
12
+
13
+ return node.ref === null ? node.props : { ...node.props, ref: node.ref };
14
+ }
15
+
16
+ export function areMemoPropsEqual(
17
+ memoType: Pick<MemoType<Record<string, unknown>>, "compare">,
18
+ previous: Record<string, unknown>,
19
+ next: Record<string, unknown>,
20
+ ): boolean {
21
+ return memoType.compare === undefined
22
+ ? shallowEqual(previous, next)
23
+ : memoType.compare(previous, next);
24
+ }
25
+
26
+ export function shallowEqual(
27
+ previous: Record<string, unknown>,
28
+ next: Record<string, unknown>,
29
+ ): boolean {
30
+ if (Object.is(previous, next)) {
31
+ return true;
32
+ }
33
+
34
+ const previousKeys = Object.keys(previous);
35
+ const nextKeys = Object.keys(next);
36
+
37
+ if (previousKeys.length !== nextKeys.length) {
38
+ return false;
39
+ }
40
+
41
+ return previousKeys.every(
42
+ (key) =>
43
+ Object.prototype.hasOwnProperty.call(next, key) &&
44
+ Object.is(previous[key], next[key]),
45
+ );
46
+ }
47
+
48
+ export function getElementPendingProps(element: ReactCompatElement): unknown {
49
+ return getPendingProps(element);
50
+ }
@@ -0,0 +1,26 @@
1
+ import type { ReactCompatNode } from "./element.js";
2
+ import type { RootRuntime } from "./hooks.js";
3
+ import type { RenderOptions } from "./hydration.js";
4
+
5
+ export interface ReconcileResult {
6
+ nodes: Node[];
7
+ consumed: number;
8
+ }
9
+
10
+ export type ReconcileNode = (
11
+ parent: ParentNode,
12
+ previousNodes: readonly Node[],
13
+ node: ReactCompatNode,
14
+ runtime: RootRuntime,
15
+ path: string,
16
+ options?: RenderOptions,
17
+ ) => ReconcileResult;
18
+
19
+ export type ReconcileSequence = (
20
+ parent: ParentNode,
21
+ previousNodes: readonly Node[],
22
+ children: ReactCompatNode[],
23
+ runtime: RootRuntime,
24
+ path: string,
25
+ options?: RenderOptions,
26
+ ) => ReconcileResult;