what-react 0.10.0 → 0.11.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.
- package/README.md +12 -11
- package/package.json +7 -3
- package/src/dom.js +52 -29
- package/src/hooks.js +351 -0
- package/src/index.js +264 -386
- package/src/runtime.js +1147 -0
- package/src/vite-plugin.js +64 -3
package/src/index.js
CHANGED
|
@@ -1,192 +1,172 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* what-react — React compatibility layer for What Framework
|
|
3
3
|
*
|
|
4
|
-
* Implements React's public API
|
|
5
|
-
*
|
|
4
|
+
* Implements React's public API on top of a dedicated compat runtime
|
|
5
|
+
* (src/runtime.js) that provides REAL React semantics:
|
|
6
|
+
* - hooks return VALUES (not signal accessors),
|
|
7
|
+
* - components re-render on state change,
|
|
8
|
+
* - re-render output is reconciled (keyed diff) so DOM and child component
|
|
9
|
+
* state are preserved.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
11
|
+
* Alias "react" → "what-react" in your bundler to use React libraries
|
|
12
|
+
* (see the reactCompat() vite plugin: what-react/vite).
|
|
13
|
+
*
|
|
14
|
+
* What's own components are unaffected: vnodes not created by this module are
|
|
15
|
+
* delegated to what-core's run-once renderer, and compat components embedded
|
|
16
|
+
* in native What trees render through a run-once bridge component.
|
|
10
17
|
*/
|
|
11
18
|
|
|
19
|
+
import { Fragment as WhatFragment } from 'what-core';
|
|
20
|
+
import { getBridge, _getCurrentInstance, flushUpdates, _drainAll, runInCommit } from './runtime.js';
|
|
12
21
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
22
|
+
useState,
|
|
23
|
+
useReducer,
|
|
24
|
+
useMemo,
|
|
25
|
+
useCallback,
|
|
26
|
+
useRef,
|
|
27
|
+
useEffect,
|
|
28
|
+
useLayoutEffect,
|
|
29
|
+
useInsertionEffect,
|
|
30
|
+
useImperativeHandle,
|
|
31
|
+
useContext,
|
|
32
|
+
createContext,
|
|
33
|
+
useSyncExternalStore,
|
|
34
|
+
useTransition,
|
|
35
|
+
useDeferredValue,
|
|
36
|
+
startTransition,
|
|
37
|
+
useId,
|
|
38
|
+
useDebugValue,
|
|
39
|
+
use,
|
|
40
|
+
} from './hooks.js';
|
|
41
|
+
|
|
42
|
+
// ---- Re-export hooks ----
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
useState,
|
|
46
|
+
useReducer,
|
|
47
|
+
useMemo,
|
|
48
|
+
useCallback,
|
|
49
|
+
useRef,
|
|
50
|
+
useEffect,
|
|
51
|
+
useLayoutEffect,
|
|
52
|
+
useInsertionEffect,
|
|
53
|
+
useImperativeHandle,
|
|
54
|
+
useContext,
|
|
55
|
+
createContext,
|
|
56
|
+
useSyncExternalStore,
|
|
57
|
+
useTransition,
|
|
58
|
+
useDeferredValue,
|
|
59
|
+
startTransition,
|
|
60
|
+
useId,
|
|
61
|
+
useDebugValue,
|
|
62
|
+
use,
|
|
63
|
+
};
|
|
51
64
|
|
|
52
|
-
|
|
65
|
+
export const Fragment = WhatFragment;
|
|
53
66
|
|
|
54
|
-
|
|
67
|
+
// ---- Class components ----
|
|
55
68
|
|
|
56
69
|
function isClassComponent(type) {
|
|
57
70
|
return (
|
|
58
71
|
typeof type === 'function' &&
|
|
59
|
-
|
|
72
|
+
type.prototype != null &&
|
|
73
|
+
(type.prototype.isReactComponent || typeof type.prototype.render === 'function')
|
|
60
74
|
);
|
|
61
75
|
}
|
|
62
76
|
|
|
63
|
-
|
|
64
|
-
const MAX_RENDERS_PER_FRAME = 50;
|
|
77
|
+
const classWrapperCache = new WeakMap();
|
|
65
78
|
|
|
66
79
|
function getClassWrapper(ClassComp) {
|
|
67
80
|
let wrapper = classWrapperCache.get(ClassComp);
|
|
68
81
|
if (wrapper) return wrapper;
|
|
69
82
|
|
|
83
|
+
const isErrorBoundary =
|
|
84
|
+
typeof ClassComp.getDerivedStateFromError === 'function' ||
|
|
85
|
+
typeof ClassComp.prototype.componentDidCatch === 'function';
|
|
86
|
+
|
|
70
87
|
wrapper = function ClassComponentWrapper(props) {
|
|
71
|
-
const instanceRef =
|
|
72
|
-
const [
|
|
73
|
-
const renderGuardRef = whatUseRef({ count: 0, frame: 0 });
|
|
74
|
-
|
|
75
|
-
// Render cycle guard — prevent infinite re-render loops
|
|
76
|
-
const currentFrame = renderGuardRef.current.frame;
|
|
77
|
-
if (typeof requestAnimationFrame !== 'undefined') {
|
|
78
|
-
renderGuardRef.current.count++;
|
|
79
|
-
if (renderGuardRef.current.count > MAX_RENDERS_PER_FRAME) {
|
|
80
|
-
console.error(`[what-react] Max re-renders exceeded for ${ClassComp.displayName || ClassComp.name || 'ClassComponent'}. Possible infinite loop.`);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
// Reset count on next frame
|
|
84
|
-
if (renderGuardRef.current._raf === undefined) {
|
|
85
|
-
renderGuardRef.current._raf = requestAnimationFrame(() => {
|
|
86
|
-
renderGuardRef.current.count = 0;
|
|
87
|
-
renderGuardRef.current.frame++;
|
|
88
|
-
renderGuardRef.current._raf = undefined;
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
88
|
+
const instanceRef = useRef(null);
|
|
89
|
+
const [, forceRender] = useReducer((c) => c + 1, 0);
|
|
92
90
|
|
|
93
|
-
// Apply defaultProps
|
|
94
91
|
let mergedProps = props;
|
|
95
92
|
if (ClassComp.defaultProps) {
|
|
96
93
|
mergedProps = { ...ClassComp.defaultProps, ...props };
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
if (instanceRef.current === null) {
|
|
100
|
-
// Initialize state from constructor
|
|
101
97
|
const instance = new ClassComp(mergedProps);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (ClassComp.getDerivedStateFromProps) {
|
|
105
|
-
const derived = ClassComp.getDerivedStateFromProps(mergedProps, instance.state);
|
|
106
|
-
if (derived !== null && derived !== undefined) {
|
|
107
|
-
instance.state = { ...instance.state, ...derived };
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Throttle forceUpdate — coalesce rapid setState calls
|
|
112
|
-
let updateScheduled = false;
|
|
113
|
-
instance._forceUpdate = () => {
|
|
114
|
-
if (!updateScheduled) {
|
|
115
|
-
updateScheduled = true;
|
|
116
|
-
queueMicrotask(() => {
|
|
117
|
-
updateScheduled = false;
|
|
118
|
-
forceRender(c => c + 1);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
};
|
|
98
|
+
if (instance.state === undefined) instance.state = {};
|
|
99
|
+
instance._forceUpdate = forceRender;
|
|
122
100
|
instanceRef.current = instance;
|
|
123
101
|
}
|
|
124
102
|
|
|
125
103
|
const instance = instanceRef.current;
|
|
126
104
|
instance.props = mergedProps;
|
|
127
105
|
|
|
128
|
-
//
|
|
106
|
+
// getDerivedStateFromProps runs before every render (React semantics)
|
|
129
107
|
if (ClassComp.getDerivedStateFromProps) {
|
|
130
108
|
const derived = ClassComp.getDerivedStateFromProps(mergedProps, instance.state);
|
|
131
|
-
if (derived
|
|
109
|
+
if (derived != null) {
|
|
132
110
|
instance.state = { ...instance.state, ...derived };
|
|
133
111
|
}
|
|
134
112
|
}
|
|
135
113
|
|
|
136
|
-
//
|
|
137
|
-
if (ClassComp.contextType
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
114
|
+
// static contextType — inject this.context from the nearest provider
|
|
115
|
+
if (ClassComp.contextType) {
|
|
116
|
+
instance.context = useContext(ClassComp.contextType);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Error boundary registration (componentDidCatch / getDerivedStateFromError)
|
|
120
|
+
if (isErrorBoundary) {
|
|
121
|
+
const inst = _getCurrentInstance();
|
|
122
|
+
if (inst && !inst._errorHandler) {
|
|
123
|
+
inst._errorHandler = (error) => {
|
|
124
|
+
if (ClassComp.getDerivedStateFromError) {
|
|
125
|
+
const derived = ClassComp.getDerivedStateFromError(error);
|
|
126
|
+
if (derived != null) instance.state = { ...instance.state, ...derived };
|
|
127
|
+
}
|
|
128
|
+
if (instance.componentDidCatch) {
|
|
129
|
+
try { instance.componentDidCatch(error, { componentStack: '' }); } catch (e) { /* boundary error */ }
|
|
130
|
+
}
|
|
131
|
+
forceRender();
|
|
132
|
+
};
|
|
142
133
|
}
|
|
143
134
|
}
|
|
144
135
|
|
|
145
|
-
// componentDidMount / componentWillUnmount
|
|
146
|
-
|
|
136
|
+
// componentDidMount / componentWillUnmount
|
|
137
|
+
useEffect(() => {
|
|
147
138
|
instance._mounted = true;
|
|
148
|
-
if (instance.componentDidMount)
|
|
149
|
-
instance.componentDidMount();
|
|
150
|
-
}
|
|
139
|
+
if (instance.componentDidMount) instance.componentDidMount();
|
|
151
140
|
return () => {
|
|
152
141
|
instance._mounted = false;
|
|
153
|
-
if (instance.componentWillUnmount)
|
|
154
|
-
instance.componentWillUnmount();
|
|
155
|
-
}
|
|
142
|
+
if (instance.componentWillUnmount) instance.componentWillUnmount();
|
|
156
143
|
};
|
|
157
144
|
}, []);
|
|
158
145
|
|
|
159
|
-
// componentDidUpdate + getSnapshotBeforeUpdate
|
|
160
|
-
const prevRef =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
146
|
+
// componentDidUpdate (+ getSnapshotBeforeUpdate approximation)
|
|
147
|
+
const prevRef = useRef(null);
|
|
148
|
+
const snapshot = (prevRef.current && instance.getSnapshotBeforeUpdate)
|
|
149
|
+
? instance.getSnapshotBeforeUpdate(prevRef.current.props, prevRef.current.state)
|
|
150
|
+
: undefined;
|
|
151
|
+
useLayoutEffect(() => {
|
|
166
152
|
const prev = prevRef.current;
|
|
167
|
-
prevRef.current = { props: mergedProps, state: instance.state
|
|
168
|
-
if (instance.componentDidUpdate) {
|
|
169
|
-
instance.componentDidUpdate(prev.props, prev.state,
|
|
153
|
+
prevRef.current = { props: mergedProps, state: instance.state };
|
|
154
|
+
if (prev && instance.componentDidUpdate) {
|
|
155
|
+
instance.componentDidUpdate(prev.props, prev.state, snapshot);
|
|
170
156
|
}
|
|
171
|
-
}
|
|
157
|
+
});
|
|
172
158
|
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
if (instance.getSnapshotBeforeUpdate && prevRef.current.rendered) {
|
|
176
|
-
prevRef.current.snapshot = instance.getSnapshotBeforeUpdate(
|
|
177
|
-
prevRef.current.props, prevRef.current.state
|
|
178
|
-
);
|
|
179
|
-
}
|
|
159
|
+
// shouldComponentUpdate is intentionally not consulted — the compat
|
|
160
|
+
// runtime always re-renders on parent cascade; use React.memo to skip.
|
|
180
161
|
|
|
181
162
|
return instance.render();
|
|
182
163
|
};
|
|
183
164
|
|
|
184
|
-
// Preserve static properties and displayName
|
|
185
165
|
wrapper.displayName = ClassComp.displayName || ClassComp.name || 'ClassComponent';
|
|
186
|
-
// Copy static properties (
|
|
166
|
+
// Copy static properties (defaultProps, contextType, custom statics)
|
|
187
167
|
for (const key of Object.getOwnPropertyNames(ClassComp)) {
|
|
188
168
|
if (key !== 'prototype' && key !== 'length' && key !== 'name' && key !== 'caller' && key !== 'arguments') {
|
|
189
|
-
try { wrapper[key] = ClassComp[key]; } catch (e) {}
|
|
169
|
+
try { wrapper[key] = ClassComp[key]; } catch (e) { /* read-only static */ }
|
|
190
170
|
}
|
|
191
171
|
}
|
|
192
172
|
|
|
@@ -196,27 +176,43 @@ function getClassWrapper(ClassComp) {
|
|
|
196
176
|
|
|
197
177
|
// ---- createElement ----
|
|
198
178
|
|
|
179
|
+
const EMPTY_CHILDREN = [];
|
|
180
|
+
|
|
181
|
+
// Flatten nested arrays but PRESERVE holes (null/false/true) so child slot
|
|
182
|
+
// positions stay stable across conditional renders (React semantics).
|
|
183
|
+
function flattenChildren(children, out) {
|
|
184
|
+
for (let i = 0; i < children.length; i++) {
|
|
185
|
+
const child = children[i];
|
|
186
|
+
if (Array.isArray(child)) flattenChildren(child, out);
|
|
187
|
+
else out.push(child);
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
|
|
199
192
|
export function createElement(type, props, ...children) {
|
|
200
193
|
if (props == null) props = {};
|
|
201
194
|
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
195
|
+
// Resolve the render target and vnode tag:
|
|
196
|
+
// - vnode.type stays the ORIGINAL component (libraries compare element.type)
|
|
197
|
+
// - vnode.tag is the What-native bridge so core's renderer can also render
|
|
198
|
+
// compat vnodes; the compat runtime unwraps tag._compatType.
|
|
199
|
+
let tag = type;
|
|
200
|
+
if (typeof type === 'function') {
|
|
201
|
+
const renderType = isClassComponent(type) ? getClassWrapper(type) : type;
|
|
202
|
+
tag = getBridge(renderType);
|
|
203
|
+
} else if (typeof type !== 'string') {
|
|
204
|
+
console.error('[what-react] createElement: invalid element type:', type);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (children.length === 0 && props.children !== undefined) {
|
|
212
|
-
const pc = props.children;
|
|
213
|
-
children = Array.isArray(pc) ? pc : [pc];
|
|
214
|
-
props = { ...props };
|
|
215
|
-
delete props.children;
|
|
216
|
-
}
|
|
207
|
+
const rawKids = children.length > 0
|
|
208
|
+
? children
|
|
209
|
+
: (props.children !== undefined ? [props.children] : EMPTY_CHILDREN);
|
|
210
|
+
const kids = rawKids.length > 0 ? flattenChildren(rawKids, []) : EMPTY_CHILDREN;
|
|
217
211
|
|
|
218
|
-
// Normalize className → class, htmlFor → for
|
|
219
|
-
|
|
212
|
+
// Normalize className → class, htmlFor → for on host elements so vnodes
|
|
213
|
+
// also render correctly through what-core's renderer (interop path).
|
|
214
|
+
if (typeof type === 'string' && ('className' in props || 'htmlFor' in props)) {
|
|
215
|
+
props = { ...props };
|
|
220
216
|
if ('className' in props) {
|
|
221
217
|
props.class = props.className;
|
|
222
218
|
delete props.className;
|
|
@@ -227,29 +223,33 @@ export function createElement(type, props, ...children) {
|
|
|
227
223
|
}
|
|
228
224
|
}
|
|
229
225
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// Alias tag → type so React libraries can access element.type
|
|
238
|
-
vnode.type = vnode.tag;
|
|
226
|
+
const key = props.key !== undefined ? props.key : null;
|
|
227
|
+
let finalProps = props;
|
|
228
|
+
if (props.key !== undefined) {
|
|
229
|
+
finalProps = { ...props };
|
|
230
|
+
delete finalProps.key;
|
|
231
|
+
}
|
|
239
232
|
|
|
240
|
-
// Mirror children into props
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
vnode.props.children = vnode.children.length === 1
|
|
245
|
-
? vnode.children[0]
|
|
246
|
-
: vnode.children;
|
|
233
|
+
// Mirror children into props.children — React libraries read element.props.children
|
|
234
|
+
if (kids.length > 0) {
|
|
235
|
+
if (finalProps === props) finalProps = { ...props };
|
|
236
|
+
finalProps.children = kids.length === 1 ? kids[0] : kids;
|
|
247
237
|
}
|
|
248
238
|
|
|
249
|
-
return
|
|
239
|
+
return {
|
|
240
|
+
tag,
|
|
241
|
+
type,
|
|
242
|
+
props: finalProps,
|
|
243
|
+
children: kids,
|
|
244
|
+
key,
|
|
245
|
+
_vnode: true,
|
|
246
|
+
_compat: true,
|
|
247
|
+
};
|
|
250
248
|
}
|
|
251
249
|
|
|
252
250
|
// ---- forwardRef ----
|
|
251
|
+
// ref stays in props (React 19-style); forwardRef components receive it as
|
|
252
|
+
// the second argument.
|
|
253
253
|
|
|
254
254
|
export function forwardRef(render) {
|
|
255
255
|
function ForwardRefComponent(props) {
|
|
@@ -268,6 +268,54 @@ export function createRef() {
|
|
|
268
268
|
return { current: null };
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
// ---- memo ----
|
|
272
|
+
// Real memo semantics: the compat runtime skips re-render when props compare
|
|
273
|
+
// equal (default: shallow equality) and no self-update is pending.
|
|
274
|
+
|
|
275
|
+
export function memo(Component, areEqual) {
|
|
276
|
+
const render = isClassComponent(Component) ? getClassWrapper(Component) : Component;
|
|
277
|
+
function Memoized(props) {
|
|
278
|
+
return render(props);
|
|
279
|
+
}
|
|
280
|
+
Memoized.displayName = `Memo(${Component.displayName || Component.name || 'Anonymous'})`;
|
|
281
|
+
Memoized._memoCompare = areEqual || shallowEqual;
|
|
282
|
+
Memoized._memoType = Component;
|
|
283
|
+
return Memoized;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ---- lazy / Suspense ----
|
|
287
|
+
|
|
288
|
+
export function lazy(loader) {
|
|
289
|
+
let Component = null;
|
|
290
|
+
let promise = null;
|
|
291
|
+
let error = null;
|
|
292
|
+
|
|
293
|
+
function LazyComponent(props) {
|
|
294
|
+
if (error) throw error;
|
|
295
|
+
if (Component) return createElement(Component, props);
|
|
296
|
+
if (!promise) {
|
|
297
|
+
promise = loader().then(
|
|
298
|
+
(mod) => { Component = (mod && mod.default) || mod; },
|
|
299
|
+
(err) => { error = err; },
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
throw promise; // caught by the nearest Suspense boundary
|
|
303
|
+
}
|
|
304
|
+
LazyComponent.displayName = 'Lazy';
|
|
305
|
+
LazyComponent._lazy = true;
|
|
306
|
+
return LazyComponent;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function Suspense(props) {
|
|
310
|
+
const inst = _getCurrentInstance();
|
|
311
|
+
if (inst) inst._isSuspense = true;
|
|
312
|
+
if (inst && inst._suspendCount > 0) {
|
|
313
|
+
return props.fallback !== undefined ? props.fallback : null;
|
|
314
|
+
}
|
|
315
|
+
return props.children;
|
|
316
|
+
}
|
|
317
|
+
Suspense.displayName = 'Suspense';
|
|
318
|
+
|
|
271
319
|
// ---- Children utilities ----
|
|
272
320
|
|
|
273
321
|
export const Children = {
|
|
@@ -290,13 +338,13 @@ export const Children = {
|
|
|
290
338
|
count(children) {
|
|
291
339
|
if (children == null) return 0;
|
|
292
340
|
const arr = Array.isArray(children) ? children : [children];
|
|
293
|
-
return arr.flat(Infinity).filter(c => c != null && c !== false && c !== true).length;
|
|
341
|
+
return arr.flat(Infinity).filter((c) => c != null && c !== false && c !== true).length;
|
|
294
342
|
},
|
|
295
343
|
|
|
296
344
|
toArray(children) {
|
|
297
345
|
if (children == null) return [];
|
|
298
346
|
const arr = Array.isArray(children) ? children : [children];
|
|
299
|
-
return arr.flat(Infinity).filter(c => c != null && c !== false && c !== true);
|
|
347
|
+
return arr.flat(Infinity).filter((c) => c != null && c !== false && c !== true);
|
|
300
348
|
},
|
|
301
349
|
|
|
302
350
|
only(children) {
|
|
@@ -313,27 +361,29 @@ export const Children = {
|
|
|
313
361
|
export function cloneElement(element, props, ...children) {
|
|
314
362
|
if (!element) return element;
|
|
315
363
|
|
|
316
|
-
|
|
317
|
-
const tag = element.tag || element.type;
|
|
364
|
+
const type = element.type !== undefined ? element.type : element.tag;
|
|
318
365
|
const oldProps = element.props || {};
|
|
319
366
|
const oldChildren = element.children || [];
|
|
320
367
|
const oldKey = element.key;
|
|
321
368
|
const oldRef = oldProps.ref;
|
|
322
369
|
|
|
323
|
-
if (!
|
|
370
|
+
if (!type) return element;
|
|
324
371
|
|
|
325
372
|
const newProps = { ...oldProps, ...props };
|
|
326
|
-
// Preserve ref from old element if not overridden
|
|
327
373
|
if (props && props.ref !== undefined) {
|
|
328
374
|
newProps.ref = props.ref;
|
|
329
375
|
} else if (oldRef !== undefined) {
|
|
330
376
|
newProps.ref = oldRef;
|
|
331
377
|
}
|
|
332
378
|
const newChildren = children.length > 0 ? children : oldChildren;
|
|
333
|
-
const newKey = props
|
|
334
|
-
if (newKey
|
|
379
|
+
const newKey = props && props.key !== undefined ? props.key : oldKey;
|
|
380
|
+
if (newKey != null) newProps.key = newKey;
|
|
381
|
+
else delete newProps.key;
|
|
335
382
|
|
|
336
|
-
|
|
383
|
+
// Don't double-pass children via props
|
|
384
|
+
if (children.length > 0) delete newProps.children;
|
|
385
|
+
|
|
386
|
+
return createElement(type, newProps, ...[].concat(newChildren || []));
|
|
337
387
|
}
|
|
338
388
|
|
|
339
389
|
// ---- createFactory (deprecated but used by some libraries) ----
|
|
@@ -354,221 +404,54 @@ export function isValidElement(object) {
|
|
|
354
404
|
);
|
|
355
405
|
}
|
|
356
406
|
|
|
357
|
-
// ----
|
|
358
|
-
// Must run synchronously after DOM mutations but before paint.
|
|
359
|
-
// We use queueMicrotask for layout-level timing (runs before next rAF).
|
|
360
|
-
|
|
361
|
-
export function useLayoutEffect(fn, deps) {
|
|
362
|
-
const hookRef = whatUseRef({ deps: undefined, cleanup: null });
|
|
363
|
-
|
|
364
|
-
const hook = hookRef.current;
|
|
365
|
-
|
|
366
|
-
if (_depsChanged(hook.deps, deps)) {
|
|
367
|
-
// Run synchronously via microtask — before next paint but after DOM mutations
|
|
368
|
-
queueMicrotask(() => {
|
|
369
|
-
if (hook.cleanup) {
|
|
370
|
-
try { hook.cleanup(); } catch (e) { /* cleanup error */ }
|
|
371
|
-
hook.cleanup = null;
|
|
372
|
-
}
|
|
373
|
-
const result = fn();
|
|
374
|
-
if (typeof result === 'function') {
|
|
375
|
-
hook.cleanup = result;
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
hook.deps = deps;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Register cleanup on unmount
|
|
382
|
-
onCleanup(() => {
|
|
383
|
-
if (hook.cleanup) {
|
|
384
|
-
try { hook.cleanup(); } catch (e) { /* cleanup error */ }
|
|
385
|
-
hook.cleanup = null;
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// ---- useInsertionEffect ----
|
|
391
|
-
// React 18 hook for CSS-in-JS libraries. Runs synchronously before layout effects.
|
|
392
|
-
// We run it immediately (synchronously) during render to ensure it runs before
|
|
393
|
-
// useLayoutEffect's microtask and useEffect's async scheduling.
|
|
394
|
-
|
|
395
|
-
export function useInsertionEffect(fn, deps) {
|
|
396
|
-
const hookRef = whatUseRef({ deps: undefined, cleanup: null });
|
|
397
|
-
|
|
398
|
-
const hook = hookRef.current;
|
|
399
|
-
|
|
400
|
-
if (_depsChanged(hook.deps, deps)) {
|
|
401
|
-
// Run synchronously — before layout effects
|
|
402
|
-
if (hook.cleanup) {
|
|
403
|
-
try { hook.cleanup(); } catch (e) { /* cleanup error */ }
|
|
404
|
-
hook.cleanup = null;
|
|
405
|
-
}
|
|
406
|
-
const result = fn();
|
|
407
|
-
if (typeof result === 'function') {
|
|
408
|
-
hook.cleanup = result;
|
|
409
|
-
}
|
|
410
|
-
hook.deps = deps;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Register cleanup on unmount
|
|
414
|
-
onCleanup(() => {
|
|
415
|
-
if (hook.cleanup) {
|
|
416
|
-
try { hook.cleanup(); } catch (e) { /* cleanup error */ }
|
|
417
|
-
hook.cleanup = null;
|
|
418
|
-
}
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// ---- useImperativeHandle ----
|
|
423
|
-
|
|
424
|
-
export function useImperativeHandle(ref, createHandle, deps) {
|
|
425
|
-
useLayoutEffect(() => {
|
|
426
|
-
if (typeof ref === 'function') {
|
|
427
|
-
const handle = createHandle();
|
|
428
|
-
ref(handle);
|
|
429
|
-
return () => ref(null);
|
|
430
|
-
} else if (ref && typeof ref === 'object') {
|
|
431
|
-
const handle = createHandle();
|
|
432
|
-
ref.current = handle;
|
|
433
|
-
return () => { ref.current = null; };
|
|
434
|
-
}
|
|
435
|
-
}, deps);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// ---- useId ----
|
|
439
|
-
let idCounter = 0;
|
|
440
|
-
export function useId() {
|
|
441
|
-
const ref = whatUseRef(null);
|
|
442
|
-
if (ref.current === null) {
|
|
443
|
-
ref.current = ':w' + (++idCounter).toString(36) + ':';
|
|
444
|
-
}
|
|
445
|
-
return ref.current;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// ---- useDebugValue ----
|
|
449
|
-
export function useDebugValue() {}
|
|
450
|
-
|
|
451
|
-
// ---- useSyncExternalStore ----
|
|
452
|
-
// Uses a signal internally so that consumers get reactive updates.
|
|
453
|
-
// The signal is initialized with getSnapshot(), and updated via the store's
|
|
454
|
-
// subscribe callback. The returned signal function integrates with What's
|
|
455
|
-
// fine-grained reactivity — reading it inside an effect auto-tracks the dependency.
|
|
456
|
-
|
|
457
|
-
export function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
|
|
458
|
-
// Create a signal initialized with the current snapshot
|
|
459
|
-
const storeSignal = whatUseRef(null);
|
|
460
|
-
if (storeSignal.current === null) {
|
|
461
|
-
storeSignal.current = signal(getSnapshot());
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
const sig = storeSignal.current;
|
|
407
|
+
// ---- StrictMode ----
|
|
465
408
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
whatUseEffect(() => {
|
|
469
|
-
const handleChange = () => {
|
|
470
|
-
const next = getSnapshot();
|
|
471
|
-
const prev = sig.peek();
|
|
472
|
-
if (!Object.is(prev, next)) {
|
|
473
|
-
sig.set(next);
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
// Sync in case store changed between render and effect
|
|
477
|
-
handleChange();
|
|
478
|
-
const unsubscribe = subscribe(handleChange);
|
|
479
|
-
return unsubscribe;
|
|
480
|
-
}, [subscribe, getSnapshot]);
|
|
481
|
-
|
|
482
|
-
// Return the signal function itself. In the run-once model, returning sig()
|
|
483
|
-
// would capture a snapshot that never updates. Returning the signal function
|
|
484
|
-
// lets the fine-grained runtime track it reactively when used in JSX.
|
|
485
|
-
return sig;
|
|
409
|
+
export function StrictMode(props) {
|
|
410
|
+
return props.children;
|
|
486
411
|
}
|
|
412
|
+
StrictMode.displayName = 'StrictMode';
|
|
487
413
|
|
|
488
|
-
// ----
|
|
414
|
+
// ---- act (testing helper) ----
|
|
415
|
+
// Runs the callback, then synchronously drains renders + effects.
|
|
489
416
|
|
|
490
|
-
export function
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
setIsPending(true);
|
|
495
|
-
queueMicrotask(() => {
|
|
496
|
-
batch(() => {
|
|
497
|
-
fn();
|
|
498
|
-
setIsPending(false);
|
|
499
|
-
});
|
|
500
|
-
});
|
|
417
|
+
export function act(callback) {
|
|
418
|
+
const result = callback && callback();
|
|
419
|
+
if (result && typeof result.then === 'function') {
|
|
420
|
+
return result.then(() => { _drainAll(); });
|
|
501
421
|
}
|
|
502
|
-
|
|
503
|
-
return
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// ---- useDeferredValue ----
|
|
507
|
-
|
|
508
|
-
export function useDeferredValue(value) {
|
|
509
|
-
const [deferred, setDeferred] = whatUseState(value);
|
|
510
|
-
|
|
511
|
-
whatUseEffect(() => {
|
|
512
|
-
setDeferred(value);
|
|
513
|
-
}, [value]);
|
|
514
|
-
|
|
515
|
-
return deferred;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// ---- startTransition (module-level) ----
|
|
519
|
-
|
|
520
|
-
export function startTransition(fn) {
|
|
521
|
-
queueMicrotask(() => {
|
|
522
|
-
batch(fn);
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// ---- StrictMode ----
|
|
527
|
-
|
|
528
|
-
export function StrictMode({ children }) {
|
|
529
|
-
return children;
|
|
422
|
+
_drainAll();
|
|
423
|
+
return { then(resolve) { _drainAll(); if (resolve) resolve(); } };
|
|
530
424
|
}
|
|
531
425
|
|
|
532
426
|
// ---- Component / PureComponent ----
|
|
533
|
-
//
|
|
534
|
-
//
|
|
427
|
+
// Function constructors (not native classes) so transpiled code using
|
|
428
|
+
// Component.call(this, props) works alongside native class extends.
|
|
535
429
|
|
|
536
430
|
export function Component(props) {
|
|
537
431
|
this.props = props;
|
|
538
432
|
this.state = {};
|
|
539
|
-
this._stateSignal = null;
|
|
540
433
|
this._mounted = false;
|
|
541
434
|
this._forceUpdate = null;
|
|
542
435
|
}
|
|
543
436
|
|
|
544
437
|
Component.prototype.isReactComponent = {};
|
|
545
438
|
|
|
546
|
-
Component.prototype.setState = function(update, callback) {
|
|
439
|
+
Component.prototype.setState = function (update, callback) {
|
|
547
440
|
const nextState = typeof update === 'function'
|
|
548
441
|
? { ...this.state, ...update(this.state, this.props) }
|
|
549
442
|
: { ...this.state, ...update };
|
|
550
443
|
|
|
551
444
|
this.state = nextState;
|
|
552
|
-
|
|
553
|
-
if (
|
|
554
|
-
this._forceUpdate();
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
if (callback) {
|
|
558
|
-
queueMicrotask(callback);
|
|
559
|
-
}
|
|
445
|
+
if (this._forceUpdate) this._forceUpdate();
|
|
446
|
+
if (callback) queueMicrotask(callback);
|
|
560
447
|
};
|
|
561
448
|
|
|
562
|
-
Component.prototype.forceUpdate = function(callback) {
|
|
563
|
-
if (this._forceUpdate)
|
|
564
|
-
|
|
565
|
-
}
|
|
566
|
-
if (callback) {
|
|
567
|
-
queueMicrotask(callback);
|
|
568
|
-
}
|
|
449
|
+
Component.prototype.forceUpdate = function (callback) {
|
|
450
|
+
if (this._forceUpdate) this._forceUpdate();
|
|
451
|
+
if (callback) queueMicrotask(callback);
|
|
569
452
|
};
|
|
570
453
|
|
|
571
|
-
Component.prototype.render = function() {
|
|
454
|
+
Component.prototype.render = function () {
|
|
572
455
|
return null;
|
|
573
456
|
};
|
|
574
457
|
|
|
@@ -580,22 +463,8 @@ PureComponent.prototype = Object.create(Component.prototype);
|
|
|
580
463
|
PureComponent.prototype.constructor = PureComponent;
|
|
581
464
|
PureComponent.prototype.isPureReactComponent = true;
|
|
582
465
|
|
|
583
|
-
PureComponent.prototype.shouldComponentUpdate = function(nextProps, nextState) {
|
|
584
|
-
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
|
|
585
|
-
};
|
|
586
|
-
|
|
587
466
|
// ---- Internal helpers ----
|
|
588
467
|
|
|
589
|
-
function _depsChanged(oldDeps, newDeps) {
|
|
590
|
-
if (oldDeps === undefined) return true;
|
|
591
|
-
if (!oldDeps || !newDeps) return true;
|
|
592
|
-
if (oldDeps.length !== newDeps.length) return true;
|
|
593
|
-
for (let i = 0; i < oldDeps.length; i++) {
|
|
594
|
-
if (!Object.is(oldDeps[i], newDeps[i])) return true;
|
|
595
|
-
}
|
|
596
|
-
return false;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
468
|
function shallowEqual(a, b) {
|
|
600
469
|
if (Object.is(a, b)) return true;
|
|
601
470
|
if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
|
|
@@ -608,34 +477,43 @@ function shallowEqual(a, b) {
|
|
|
608
477
|
return true;
|
|
609
478
|
}
|
|
610
479
|
|
|
480
|
+
// ---- flushSync re-export (some libraries import it from 'react') ----
|
|
481
|
+
|
|
482
|
+
export { flushUpdates as unstable_flushUpdates };
|
|
483
|
+
|
|
611
484
|
// ---- React internals that some libraries check ----
|
|
485
|
+
|
|
612
486
|
export const __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
|
|
613
487
|
ReactCurrentOwner: { current: null },
|
|
614
488
|
ReactCurrentDispatcher: { current: null },
|
|
615
489
|
};
|
|
616
490
|
|
|
617
491
|
// ---- Version ----
|
|
492
|
+
|
|
618
493
|
export const version = '18.3.1';
|
|
619
494
|
|
|
620
495
|
// ---- Default export (import * as React from 'react') ----
|
|
496
|
+
|
|
621
497
|
const React = {
|
|
622
|
-
useState
|
|
623
|
-
|
|
498
|
+
useState,
|
|
499
|
+
useReducer,
|
|
500
|
+
useMemo,
|
|
501
|
+
useCallback,
|
|
502
|
+
useRef,
|
|
503
|
+
useEffect,
|
|
624
504
|
useLayoutEffect,
|
|
625
505
|
useInsertionEffect,
|
|
626
|
-
useMemo: whatUseMemo,
|
|
627
|
-
useCallback: whatUseCallback,
|
|
628
|
-
useRef: whatUseRef,
|
|
629
|
-
useContext: whatUseContext,
|
|
630
|
-
useReducer: whatUseReducer,
|
|
631
506
|
useImperativeHandle,
|
|
632
|
-
|
|
633
|
-
|
|
507
|
+
useContext,
|
|
508
|
+
createContext,
|
|
634
509
|
useSyncExternalStore,
|
|
635
510
|
useTransition,
|
|
636
511
|
useDeferredValue,
|
|
512
|
+
startTransition,
|
|
513
|
+
useId,
|
|
514
|
+
useDebugValue,
|
|
515
|
+
use,
|
|
637
516
|
createElement,
|
|
638
|
-
createContext: whatCreateContext,
|
|
639
517
|
createRef,
|
|
640
518
|
createFactory,
|
|
641
519
|
forwardRef,
|
|
@@ -643,13 +521,13 @@ const React = {
|
|
|
643
521
|
isValidElement,
|
|
644
522
|
Component,
|
|
645
523
|
PureComponent,
|
|
646
|
-
Fragment
|
|
647
|
-
Suspense
|
|
524
|
+
Fragment,
|
|
525
|
+
Suspense,
|
|
648
526
|
StrictMode,
|
|
649
|
-
memo
|
|
650
|
-
lazy
|
|
527
|
+
memo,
|
|
528
|
+
lazy,
|
|
529
|
+
act,
|
|
651
530
|
Children,
|
|
652
|
-
startTransition,
|
|
653
531
|
version,
|
|
654
532
|
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
|
655
533
|
};
|