@reckona/mreact-compat 0.0.66 → 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.
package/src/element.ts ADDED
@@ -0,0 +1,317 @@
1
+ export const REACT_COMPAT_ELEMENT_TYPE = Symbol.for("modular.react.element");
2
+ export const ERROR_BOUNDARY_TYPE = Symbol.for("modular.react.error_boundary");
3
+ export const FORWARD_REF_TYPE = Symbol.for("modular.react.forward_ref");
4
+ export const MEMO_TYPE = Symbol.for("modular.react.memo");
5
+ export const LAZY_TYPE = Symbol.for("modular.react.lazy");
6
+ export const STRICT_MODE_TYPE = Symbol.for("modular.react.strict_mode");
7
+ export const PORTAL_TYPE = Symbol.for("modular.react.portal");
8
+ const REACT_COMPAT_PROVIDER_TYPE = Symbol.for("modular.react.provider");
9
+ export const Fragment = Symbol.for("modular.react.fragment");
10
+ export const Suspense = Symbol.for("modular.react.suspense");
11
+ export const SuspenseList = Symbol.for("modular.react.suspense_list");
12
+ export const Activity = Symbol.for("modular.react.activity");
13
+ export const Profiler = Symbol.for("modular.react.profiler");
14
+
15
+ export interface ReactCompatProviderType {
16
+ $$typeof: symbol;
17
+ context: unknown;
18
+ }
19
+
20
+ export interface ReactCompatContextProviderShorthand {
21
+ Provider: ReactCompatProviderType;
22
+ Consumer: unknown;
23
+ }
24
+
25
+ export type ElementType<P = Record<string, unknown>> =
26
+ | string
27
+ | typeof Fragment
28
+ | typeof Suspense
29
+ | typeof SuspenseList
30
+ | typeof Activity
31
+ | typeof Profiler
32
+ | typeof ERROR_BOUNDARY_TYPE
33
+ | typeof STRICT_MODE_TYPE
34
+ | ReactCompatContextProviderShorthand
35
+ | ReactCompatProviderType
36
+ | ForwardRefType<P>
37
+ | MemoType<P>
38
+ | LazyType<P>
39
+ | ((props: P) => ReactCompatNode | PromiseLike<ReactCompatNode>)
40
+ | (new (props: P) => { render(): ReactCompatNode });
41
+
42
+ export type ReactCompatNode =
43
+ | ReactCompatElement
44
+ | ReactCompatPortal
45
+ | string
46
+ | number
47
+ | boolean
48
+ | null
49
+ | undefined
50
+ | ReactCompatNode[];
51
+
52
+ export interface ReactCompatElement<P = Record<string, unknown>> {
53
+ $$typeof: typeof REACT_COMPAT_ELEMENT_TYPE;
54
+ type: ElementType<P>;
55
+ key: string | null;
56
+ ref: unknown;
57
+ props: P & { children?: ReactCompatNode };
58
+ }
59
+
60
+ export interface ReactCompatPortal {
61
+ $$typeof: typeof PORTAL_TYPE;
62
+ container: Element;
63
+ children: ReactCompatNode;
64
+ key: string | null;
65
+ }
66
+
67
+ export function createElement<P extends Record<string, unknown>>(
68
+ type: ElementType<P>,
69
+ config: (P & { key?: unknown; ref?: unknown }) | null,
70
+ ...children: ReactCompatNode[]
71
+ ): ReactCompatElement<P> {
72
+ const normalizedType = normalizeElementType(type);
73
+ const props = applyDefaultProps(normalizedType, { ...config }) as P & {
74
+ children?: ReactCompatNode;
75
+ key?: unknown;
76
+ ref?: unknown;
77
+ };
78
+ const key = props.key === undefined ? null : String(props.key);
79
+ const ref = props.ref ?? null;
80
+
81
+ delete props.key;
82
+ delete props.ref;
83
+
84
+ if (children.length === 1) {
85
+ props.children = children[0];
86
+ } else if (children.length > 1) {
87
+ props.children = children;
88
+ }
89
+
90
+ return {
91
+ $$typeof: REACT_COMPAT_ELEMENT_TYPE,
92
+ type: normalizedType as ElementType<P>,
93
+ key,
94
+ ref,
95
+ props: props as P & { children?: ReactCompatNode },
96
+ };
97
+ }
98
+
99
+ export function isReactCompatElement(
100
+ value: unknown,
101
+ ): value is ReactCompatElement {
102
+ return (
103
+ typeof value === "object" &&
104
+ value !== null &&
105
+ (value as { $$typeof?: unknown }).$$typeof === REACT_COMPAT_ELEMENT_TYPE
106
+ );
107
+ }
108
+
109
+ export function createPortal(
110
+ children: ReactCompatNode,
111
+ container: Element,
112
+ key?: unknown,
113
+ ): ReactCompatPortal {
114
+ return {
115
+ $$typeof: PORTAL_TYPE,
116
+ container,
117
+ children,
118
+ key: key === undefined ? null : String(key),
119
+ };
120
+ }
121
+
122
+ export function isReactCompatPortal(value: unknown): value is ReactCompatPortal {
123
+ return (
124
+ typeof value === "object" &&
125
+ value !== null &&
126
+ (value as { $$typeof?: unknown }).$$typeof === PORTAL_TYPE
127
+ );
128
+ }
129
+
130
+ export function createRef<T>(): { current: T | null } {
131
+ return { current: null };
132
+ }
133
+
134
+ export interface ForwardRefType<P = Record<string, unknown>> {
135
+ $$typeof: typeof FORWARD_REF_TYPE;
136
+ render: (props: P, ref: unknown) => ReactCompatNode;
137
+ }
138
+
139
+ export interface MemoType<P = Record<string, unknown>> {
140
+ $$typeof: typeof MEMO_TYPE;
141
+ type: ElementType<P>;
142
+ compare?: (previous: P, next: P) => boolean;
143
+ }
144
+
145
+ export interface LazyType<P = Record<string, unknown>> {
146
+ $$typeof: typeof LAZY_TYPE;
147
+ load: () => Promise<{ default: ElementType<P> }>;
148
+ status: "uninitialized" | "pending" | "resolved" | "rejected";
149
+ promise?: Promise<void>;
150
+ resolved?: ElementType<P>;
151
+ error?: unknown;
152
+ }
153
+
154
+ export function forwardRef<P, T>(
155
+ render: (props: P, ref: { current: T | null } | ((value: T | null) => void) | null) => ReactCompatNode,
156
+ ): ForwardRefType<P & { ref?: unknown }> {
157
+ return { $$typeof: FORWARD_REF_TYPE, render: render as ForwardRefType<P>["render"] };
158
+ }
159
+
160
+ export function memo<P>(
161
+ type: ElementType<P>,
162
+ compare?: (previous: P, next: P) => boolean,
163
+ ): MemoType<P> {
164
+ return compare === undefined
165
+ ? { $$typeof: MEMO_TYPE, type }
166
+ : { $$typeof: MEMO_TYPE, type, compare };
167
+ }
168
+
169
+ export function lazy<P>(
170
+ load: () => Promise<{ default: ElementType<P> }>,
171
+ ): LazyType<P> {
172
+ return {
173
+ $$typeof: LAZY_TYPE,
174
+ load,
175
+ status: "uninitialized",
176
+ };
177
+ }
178
+
179
+ export const StrictMode = STRICT_MODE_TYPE;
180
+
181
+ export function cloneElement<P extends Record<string, unknown>>(
182
+ element: ReactCompatElement<P>,
183
+ props: Partial<P> | null,
184
+ ...children: ReactCompatNode[]
185
+ ): ReactCompatElement<P> {
186
+ const nextProps = applyDefaultProps(element.type, {
187
+ ...element.props,
188
+ ...props,
189
+ }) as P & { key?: unknown; ref?: unknown };
190
+ const key = nextProps.key === undefined ? element.key : String(nextProps.key);
191
+ const ref = nextProps.ref === undefined ? element.ref : nextProps.ref;
192
+
193
+ delete nextProps.key;
194
+ delete nextProps.ref;
195
+
196
+ if (children.length === 1) {
197
+ (nextProps as P & { children?: ReactCompatNode }).children = children[0];
198
+ } else if (children.length > 1) {
199
+ (nextProps as P & { children?: ReactCompatNode }).children = children;
200
+ }
201
+
202
+ return {
203
+ $$typeof: REACT_COMPAT_ELEMENT_TYPE,
204
+ type: element.type,
205
+ key,
206
+ ref,
207
+ props: nextProps as P & { children?: ReactCompatNode },
208
+ };
209
+ }
210
+
211
+ function normalizeElementType<P>(type: ElementType<P>): ElementType<P> {
212
+ return isReactCompatContextProviderShorthand(type) ? (type.Provider as ElementType<P>) : type;
213
+ }
214
+
215
+ function applyDefaultProps(
216
+ type: unknown,
217
+ props: Record<string, unknown>,
218
+ ): Record<string, unknown> {
219
+ const defaultProps = (type as { defaultProps?: Record<string, unknown> } | undefined)
220
+ ?.defaultProps;
221
+
222
+ if (defaultProps === undefined) {
223
+ return props;
224
+ }
225
+
226
+ for (const [name, value] of Object.entries(defaultProps)) {
227
+ if (props[name] === undefined) {
228
+ props[name] = value;
229
+ }
230
+ }
231
+
232
+ return props;
233
+ }
234
+
235
+ function isReactCompatContextProviderShorthand(
236
+ value: unknown,
237
+ ): value is ReactCompatContextProviderShorthand {
238
+ if (typeof value !== "object" || value === null) {
239
+ return false;
240
+ }
241
+
242
+ const provider = (value as { Provider?: unknown }).Provider;
243
+
244
+ return (
245
+ typeof provider === "object" &&
246
+ provider !== null &&
247
+ (provider as { $$typeof?: unknown }).$$typeof === REACT_COMPAT_PROVIDER_TYPE
248
+ );
249
+ }
250
+
251
+ export const isValidElement = isReactCompatElement;
252
+
253
+ export const Children = {
254
+ map<T>(
255
+ children: ReactCompatNode,
256
+ fn: (child: Exclude<ReactCompatNode, null | undefined | boolean>, index: number) => T,
257
+ ): T[] | null {
258
+ if (children === null || children === undefined) {
259
+ return null;
260
+ }
261
+
262
+ return flattenChildren(children).map((child, index) =>
263
+ fn(child as Exclude<ReactCompatNode, null | undefined | boolean>, index),
264
+ );
265
+ },
266
+ count(children: ReactCompatNode): number {
267
+ if (children === null || children === undefined) {
268
+ return 0;
269
+ }
270
+
271
+ return flattenChildren(children).length;
272
+ },
273
+ toArray(children: ReactCompatNode): Exclude<ReactCompatNode, null | undefined | boolean>[] {
274
+ return toChildArray(children);
275
+ },
276
+ only(children: ReactCompatNode): Exclude<ReactCompatNode, null | undefined | boolean> {
277
+ const array = toChildArray(children);
278
+
279
+ if (array.length !== 1) {
280
+ throw new Error("Expected exactly one child.");
281
+ }
282
+
283
+ return array[0] as Exclude<ReactCompatNode, null | undefined | boolean>;
284
+ },
285
+ };
286
+
287
+ function toChildArray(
288
+ children: ReactCompatNode,
289
+ ): Exclude<ReactCompatNode, null | undefined | boolean>[] {
290
+ return flattenChildren(children).filter(
291
+ (child): child is Exclude<ReactCompatNode, null | undefined | boolean> =>
292
+ child !== null && child !== undefined && typeof child !== "boolean",
293
+ );
294
+ }
295
+
296
+ function flattenChildren(children: ReactCompatNode): ReactCompatNode[] {
297
+ if (Array.isArray(children)) {
298
+ return children.flatMap((child) => flattenChildren(child));
299
+ }
300
+
301
+ return [children];
302
+ }
303
+
304
+ export interface ErrorBoundaryOptions {
305
+ fallback: (error: Error) => ReactCompatNode;
306
+ onError?: (error: Error) => void;
307
+ }
308
+
309
+ export function createErrorBoundary(
310
+ options: ErrorBoundaryOptions,
311
+ children: ReactCompatNode,
312
+ ): ReactCompatElement<ErrorBoundaryOptions & { children: ReactCompatNode }> {
313
+ return createElement(ERROR_BOUNDARY_TYPE, {
314
+ ...options,
315
+ children,
316
+ });
317
+ }
@@ -0,0 +1,27 @@
1
+ import type { SyntheticEvent } from "./event-types.js";
2
+
3
+ export interface AppliedProps {
4
+ props: Record<string, unknown>;
5
+ listeners: Map<string, AppliedEventListener>;
6
+ }
7
+
8
+ export interface AppliedEventListener {
9
+ handler: (event: SyntheticEvent) => void;
10
+ }
11
+
12
+ const appliedProps = new WeakMap<HTMLElement, AppliedProps>();
13
+
14
+ export function getAppliedProps(element: HTMLElement): AppliedProps | undefined {
15
+ return appliedProps.get(element);
16
+ }
17
+
18
+ export function setAppliedProps(element: HTMLElement, props: AppliedProps): void {
19
+ appliedProps.set(element, props);
20
+ }
21
+
22
+ export function getAppliedEventHandler(
23
+ element: HTMLElement,
24
+ name: string,
25
+ ): ((event: SyntheticEvent) => void) | undefined {
26
+ return appliedProps.get(element)?.listeners.get(name)?.handler;
27
+ }
@@ -0,0 +1 @@
1
+ export { runWithEventPriority, type EventPriority } from "./hooks.js";
@@ -0,0 +1,154 @@
1
+ const queuedHydrationEvents = new WeakMap<Element, QueuedHydrationEvent[]>();
2
+ const replayedEvents = new WeakSet<Event>();
3
+ const allowedReplayEventTypes = new Set(["click", "input", "change", "submit"]);
4
+
5
+ export interface EventHydrationManifest {
6
+ version: 1;
7
+ events: EventHydrationManifestEntry[];
8
+ }
9
+
10
+ export interface EventHydrationManifestEntry {
11
+ id: string;
12
+ event: string;
13
+ handler: string;
14
+ }
15
+
16
+ interface QueuedHydrationEvent {
17
+ target: EventTarget;
18
+ event: Event;
19
+ }
20
+
21
+ export interface HydrationEventReplayOptions {
22
+ onCapturedEvent?: (event: Event, target: EventTarget) => void;
23
+ }
24
+
25
+ export function queueHydrationEvent(
26
+ container: Element,
27
+ event: Event,
28
+ target: EventTarget,
29
+ ): void {
30
+ if (
31
+ !allowedReplayEventTypes.has(event.type) ||
32
+ !(target instanceof Node) ||
33
+ !container.contains(target)
34
+ ) {
35
+ return;
36
+ }
37
+
38
+ const events = queuedHydrationEvents.get(container) ?? [];
39
+ events.push({ event, target });
40
+ queuedHydrationEvents.set(container, events);
41
+ }
42
+
43
+ export function enableHydrationEventReplay(container: Element): () => void {
44
+ return enableHydrationEventReplayForTypes(container, allowedReplayEventTypes);
45
+ }
46
+
47
+ export function readEventHydrationManifest(
48
+ root: ParentNode = document,
49
+ ): EventHydrationManifest | undefined {
50
+ const script = root.querySelector<HTMLScriptElement>(
51
+ "script[data-mreact-event-manifest]",
52
+ );
53
+
54
+ if (script === null) {
55
+ return undefined;
56
+ }
57
+
58
+ const value = JSON.parse(script.textContent ?? "") as EventHydrationManifest;
59
+
60
+ if (value.version !== 1 || !Array.isArray(value.events)) {
61
+ return undefined;
62
+ }
63
+
64
+ return value;
65
+ }
66
+
67
+ export function enableEventHydrationManifestReplay(
68
+ container: Element,
69
+ manifest: EventHydrationManifest | undefined,
70
+ options: HydrationEventReplayOptions = {},
71
+ ): () => void {
72
+ if (manifest === undefined) {
73
+ return () => undefined;
74
+ }
75
+
76
+ const eventTypes = new Set(
77
+ manifest.events
78
+ .map((event) => event.event)
79
+ .filter((event) => allowedReplayEventTypes.has(event)),
80
+ );
81
+
82
+ return enableHydrationEventReplayForTypes(container, eventTypes, options);
83
+ }
84
+
85
+ export function replayQueuedHydrationEvents(container: Element): void {
86
+ const events = queuedHydrationEvents.get(container) ?? [];
87
+ queuedHydrationEvents.delete(container);
88
+
89
+ for (const { event, target } of events) {
90
+ replayedEvents.add(event);
91
+ target.dispatchEvent(event);
92
+ }
93
+ }
94
+
95
+ function enableHydrationEventReplayForTypes(
96
+ container: Element,
97
+ eventTypes: Iterable<string>,
98
+ options: HydrationEventReplayOptions = {},
99
+ ): () => void {
100
+ const listeners = Array.from(eventTypes, (type) => {
101
+ const listener = (event: Event): void => {
102
+ if (replayedEvents.has(event) || !(event.target instanceof Node)) {
103
+ return;
104
+ }
105
+
106
+ const replayEvent = cloneReplayableEvent(event);
107
+ queueHydrationEvent(container, replayEvent, event.target);
108
+ options.onCapturedEvent?.(replayEvent, event.target);
109
+ event.stopImmediatePropagation();
110
+ event.preventDefault();
111
+ };
112
+
113
+ container.addEventListener(type, listener, true);
114
+ return { type, listener };
115
+ });
116
+
117
+ return () => {
118
+ for (const { type, listener } of listeners) {
119
+ container.removeEventListener(type, listener, true);
120
+ }
121
+ };
122
+ }
123
+
124
+ function cloneReplayableEvent(event: Event): Event {
125
+ const init = {
126
+ bubbles: event.bubbles,
127
+ cancelable: event.cancelable,
128
+ composed: event.composed,
129
+ };
130
+
131
+ if (typeof MouseEvent !== "undefined" && event instanceof MouseEvent) {
132
+ return new MouseEvent(event.type, {
133
+ ...init,
134
+ button: event.button,
135
+ buttons: event.buttons,
136
+ clientX: event.clientX,
137
+ clientY: event.clientY,
138
+ ctrlKey: event.ctrlKey,
139
+ metaKey: event.metaKey,
140
+ shiftKey: event.shiftKey,
141
+ altKey: event.altKey,
142
+ });
143
+ }
144
+
145
+ if (typeof InputEvent !== "undefined" && event instanceof InputEvent) {
146
+ return new InputEvent(event.type, {
147
+ ...init,
148
+ data: event.data,
149
+ inputType: event.inputType,
150
+ });
151
+ }
152
+
153
+ return new Event(event.type, init);
154
+ }
@@ -0,0 +1,18 @@
1
+ export interface SyntheticEvent {
2
+ bubbles: boolean;
3
+ cancelable: boolean;
4
+ defaultPrevented: boolean;
5
+ eventPhase: number;
6
+ isTrusted: boolean;
7
+ nativeEvent: Event;
8
+ timeStamp: number;
9
+ type: string;
10
+ target: EventTarget | null;
11
+ currentTarget: EventTarget | null;
12
+ persist(): void;
13
+ preventDefault(): void;
14
+ stopPropagation(): void;
15
+ isDefaultPrevented(): boolean;
16
+ isPersistent(): boolean;
17
+ isPropagationStopped(): boolean;
18
+ }