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/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 using What's signals + reconciler.
5
- * Alias "react" "what-react" in your bundler to use React libraries.
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
- * What's existing hooks already have positional tracking (hookIndex/hooks[]),
8
- * so most hooks are thin re-exports. The main work is bridging createElement,
9
- * forwardRef, Children, class components, and React-specific APIs.
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
- h,
14
- Fragment as WhatFragment,
15
- signal,
16
- effect,
17
- computed,
18
- batch,
19
- flushSync as whatFlushSync,
20
- untrack,
21
- memo as whatMemo,
22
- lazy as whatLazy,
23
- Suspense as WhatSuspense,
24
- ErrorBoundary as WhatErrorBoundary,
25
- useState as whatUseState,
26
- useEffect as whatUseEffect,
27
- useMemo as whatUseMemo,
28
- useCallback as whatUseCallback,
29
- useRef as whatUseRef,
30
- useContext as whatUseContext,
31
- useReducer as whatUseReducer,
32
- createContext as whatCreateContext,
33
- onMount,
34
- onCleanup,
35
- } from 'what-core';
36
-
37
- // ---- Re-export What's hooks with React-compatible names ----
38
-
39
- export const useState = whatUseState;
40
- export const useEffect = whatUseEffect;
41
- export const useMemo = whatUseMemo;
42
- export const useCallback = whatUseCallback;
43
- export const useRef = whatUseRef;
44
- export const useContext = whatUseContext;
45
- export const useReducer = whatUseReducer;
46
- export const createContext = whatCreateContext;
47
- export const Fragment = WhatFragment;
48
- export const Suspense = WhatSuspense;
49
- export const memo = whatMemo;
50
- export const lazy = whatLazy;
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
- // ---- Class component wrapper ----
65
+ export const Fragment = WhatFragment;
53
66
 
54
- const classWrapperCache = new WeakMap();
67
+ // ---- Class components ----
55
68
 
56
69
  function isClassComponent(type) {
57
70
  return (
58
71
  typeof type === 'function' &&
59
- (type.prototype?.isReactComponent || type.prototype?.render)
72
+ type.prototype != null &&
73
+ (type.prototype.isReactComponent || typeof type.prototype.render === 'function')
60
74
  );
61
75
  }
62
76
 
63
- // Max re-renders per component per frame to prevent infinite loops
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 = whatUseRef(null);
72
- const [renderCount, forceRender] = whatUseState(0);
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
- // Apply getDerivedStateFromProps on initial render
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
- // Apply getDerivedStateFromProps on every render (React semantics)
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 !== null && derived !== undefined) {
109
+ if (derived != null) {
132
110
  instance.state = { ...instance.state, ...derived };
133
111
  }
134
112
  }
135
113
 
136
- // Static contextType support — inject this.context from nearest provider
137
- if (ClassComp.contextType && ClassComp.contextType._whatContext) {
138
- try {
139
- instance.context = whatUseContext(ClassComp.contextType);
140
- } catch (e) {
141
- // Context not available leave as undefined
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 lifecycle
146
- whatUseEffect(() => {
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 = whatUseRef({ props: null, state: null, rendered: false, snapshot: undefined });
161
- whatUseEffect(() => {
162
- if (!prevRef.current.rendered) {
163
- prevRef.current = { props: mergedProps, state: instance.state, rendered: true, snapshot: undefined };
164
- return;
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, rendered: true, snapshot: undefined };
168
- if (instance.componentDidUpdate) {
169
- instance.componentDidUpdate(prev.props, prev.state, prev.snapshot);
153
+ prevRef.current = { props: mergedProps, state: instance.state };
154
+ if (prev && instance.componentDidUpdate) {
155
+ instance.componentDidUpdate(prev.props, prev.state, snapshot);
170
156
  }
171
- }, [mergedProps, renderCount]);
157
+ });
172
158
 
173
- // getSnapshotBeforeUpdate capture before DOM updates
174
- // We approximate by calling it synchronously before render returns
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 (getDerivedStateFromProps, defaultProps, contextType, etc.)
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
- // Wrap class components so What's reconciler can call them as functions
203
- if (isClassComponent(type)) {
204
- type = getClassWrapper(type);
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
- // React libraries sometimes pass children via props instead of as spread args
208
- // (e.g., React Router's createElement(Router, { children, location, ... })).
209
- // Move props.children into the spread children array so h() puts them
210
- // in vnode.children otherwise the reconciler overwrites props.children.
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 for HTML elements
219
- if (typeof type === 'string') {
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
- // Keep ref in props What's reconciler handles ref for HTML elements,
231
- // and forwardRef components extract it from props. No need to strip.
232
-
233
- const vnode = children.length <= 1
234
- ? h(type, props, children[0])
235
- : h(type, props, ...children);
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 for React compat — React libraries read
241
- // element.props.children (e.g., React Router's createRoutesFromChildren)
242
- if (vnode.children.length > 0) {
243
- vnode.props = { ...vnode.props };
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 vnode;
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
- // Handle both vnode objects and plain React-style elements
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 (!tag) return element;
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?.key !== undefined ? props.key : oldKey;
334
- if (newKey !== undefined) newProps.key = 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
- return createElement(tag, newProps, ...([].concat(newChildren || [])));
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
- // ---- useLayoutEffect ----
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
- // Subscribe to the store, updating the signal when the store changes.
467
- // onCleanup handles unsubscription on unmount.
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
- // ---- useTransition ----
414
+ // ---- act (testing helper) ----
415
+ // Runs the callback, then synchronously drains renders + effects.
489
416
 
490
- export function useTransition() {
491
- const [isPending, setIsPending] = whatUseState(false);
492
-
493
- function startTransitionFn(fn) {
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 [isPending, startTransitionFn];
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
- // Use function constructors (not native classes) so that transpiled code
534
- // using Component.call(this, props) works alongside native class extends.
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 (this._forceUpdate) {
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
- this._forceUpdate();
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: whatUseState,
623
- useEffect: whatUseEffect,
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
- useId,
633
- useDebugValue,
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: WhatFragment,
647
- Suspense: WhatSuspense,
524
+ Fragment,
525
+ Suspense,
648
526
  StrictMode,
649
- memo: whatMemo,
650
- lazy: whatLazy,
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
  };