@reckona/mreact-compat 0.0.91 → 0.0.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +1 -0
  2. package/dist/class-component.d.ts +22 -6
  3. package/dist/class-component.d.ts.map +1 -1
  4. package/dist/class-component.js +157 -51
  5. package/dist/class-component.js.map +1 -1
  6. package/dist/context.d.ts +19 -3
  7. package/dist/context.d.ts.map +1 -1
  8. package/dist/context.js +55 -6
  9. package/dist/context.js.map +1 -1
  10. package/dist/dom-children.d.ts +2 -0
  11. package/dist/dom-children.d.ts.map +1 -1
  12. package/dist/dom-children.js +103 -1
  13. package/dist/dom-children.js.map +1 -1
  14. package/dist/dom-host-rules.d.ts +10 -0
  15. package/dist/dom-host-rules.d.ts.map +1 -0
  16. package/dist/dom-host-rules.js +86 -0
  17. package/dist/dom-host-rules.js.map +1 -0
  18. package/dist/dom-props.d.ts +3 -2
  19. package/dist/dom-props.d.ts.map +1 -1
  20. package/dist/dom-props.js +229 -33
  21. package/dist/dom-props.js.map +1 -1
  22. package/dist/element.d.ts +9 -4
  23. package/dist/element.d.ts.map +1 -1
  24. package/dist/element.js +101 -26
  25. package/dist/element.js.map +1 -1
  26. package/dist/event-listeners.d.ts +4 -4
  27. package/dist/event-listeners.d.ts.map +1 -1
  28. package/dist/event-listeners.js +1 -1
  29. package/dist/event-listeners.js.map +1 -1
  30. package/dist/event-types.d.ts +10 -0
  31. package/dist/event-types.d.ts.map +1 -1
  32. package/dist/event-types.js.map +1 -1
  33. package/dist/events.js +22 -1
  34. package/dist/events.js.map +1 -1
  35. package/dist/fiber-commit.d.ts +2 -1
  36. package/dist/fiber-commit.d.ts.map +1 -1
  37. package/dist/fiber-commit.js +13 -1
  38. package/dist/fiber-commit.js.map +1 -1
  39. package/dist/fiber-reconciler.d.ts.map +1 -1
  40. package/dist/fiber-reconciler.js +28 -7
  41. package/dist/fiber-reconciler.js.map +1 -1
  42. package/dist/fiber-work-loop.d.ts.map +1 -1
  43. package/dist/fiber-work-loop.js +4 -3
  44. package/dist/fiber-work-loop.js.map +1 -1
  45. package/dist/fiber.d.ts +5 -0
  46. package/dist/fiber.d.ts.map +1 -1
  47. package/dist/fiber.js +9 -0
  48. package/dist/fiber.js.map +1 -1
  49. package/dist/hooks-entry.d.ts +3 -0
  50. package/dist/hooks-entry.d.ts.map +1 -0
  51. package/dist/hooks-entry.js +2 -0
  52. package/dist/hooks-entry.js.map +1 -0
  53. package/dist/hooks.d.ts +39 -5
  54. package/dist/hooks.d.ts.map +1 -1
  55. package/dist/hooks.js +373 -326
  56. package/dist/hooks.js.map +1 -1
  57. package/dist/host-reconciler.d.ts +3 -0
  58. package/dist/host-reconciler.d.ts.map +1 -1
  59. package/dist/host-reconciler.js +1183 -68
  60. package/dist/host-reconciler.js.map +1 -1
  61. package/dist/hydration.d.ts +1 -1
  62. package/dist/hydration.d.ts.map +1 -1
  63. package/dist/hydration.js.map +1 -1
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/react-default.d.ts +4 -4
  69. package/dist/react-default.d.ts.map +1 -1
  70. package/dist/react-default.js +2 -1
  71. package/dist/react-default.js.map +1 -1
  72. package/dist/reconciler.d.ts.map +1 -1
  73. package/dist/reconciler.js +38 -22
  74. package/dist/reconciler.js.map +1 -1
  75. package/dist/root.d.ts.map +1 -1
  76. package/dist/root.js +48 -13
  77. package/dist/root.js.map +1 -1
  78. package/dist/server-render.d.ts +6 -0
  79. package/dist/server-render.d.ts.map +1 -0
  80. package/dist/server-render.js +307 -0
  81. package/dist/server-render.js.map +1 -0
  82. package/package.json +6 -2
  83. package/src/class-component.ts +313 -51
  84. package/src/context.ts +108 -9
  85. package/src/dom-children.ts +155 -1
  86. package/src/dom-host-rules.ts +115 -0
  87. package/src/dom-props.ts +297 -46
  88. package/src/element.ts +141 -31
  89. package/src/event-listeners.ts +6 -6
  90. package/src/event-types.ts +10 -0
  91. package/src/events.ts +32 -10
  92. package/src/fiber-commit.ts +16 -1
  93. package/src/fiber-reconciler.ts +39 -6
  94. package/src/fiber-work-loop.ts +4 -3
  95. package/src/fiber.ts +14 -0
  96. package/src/hooks-entry.ts +24 -0
  97. package/src/hooks.ts +482 -479
  98. package/src/host-reconciler.ts +1662 -83
  99. package/src/hydration.ts +1 -1
  100. package/src/index.ts +1 -1
  101. package/src/react-default.ts +1 -1
  102. package/src/reconciler.ts +61 -22
  103. package/src/root.ts +55 -12
  104. package/src/server-render.ts +478 -0
@@ -5,6 +5,8 @@ import type { ReconcileNode, ReconcileResult } from "./reconcile-types.js";
5
5
  import { isThenable } from "./thenable.js";
6
6
  import { shallowEqual } from "./prop-comparison.js";
7
7
 
8
+ const CLASS_COMPONENT_RUNTIME_OWNER = Symbol.for("modular.react.class_component_runtime");
9
+
8
10
  export interface ClassComponentInstance {
9
11
  props: Record<string, unknown>;
10
12
  state?: Record<string, unknown>;
@@ -46,43 +48,150 @@ export interface ClassComponentType {
46
48
  getDerivedStateFromError?: (error: Error) => Record<string, unknown> | null;
47
49
  }
48
50
 
49
- export class Component<
51
+ export interface Component<
50
52
  P extends Record<string, unknown> = Record<string, unknown>,
51
53
  S extends Record<string, unknown> = Record<string, unknown>,
52
54
  > {
53
55
  props: P;
54
56
  state?: S;
55
- setState!: ClassComponentInstance["setState"];
56
- forceUpdate!: ClassComponentInstance["forceUpdate"];
57
-
58
- constructor(props: P) {
59
- this.props = props;
60
- }
57
+ setState(
58
+ partial:
59
+ | Partial<S>
60
+ | ((
61
+ previousState: Readonly<S>,
62
+ props: Readonly<P>,
63
+ ) => Partial<S> | S | null),
64
+ callback?: () => void,
65
+ ): void;
66
+ forceUpdate(callback?: () => void): void;
67
+ render(): ReactCompatNode;
68
+ }
61
69
 
62
- render(): ReactCompatNode {
63
- return null;
64
- }
70
+ export interface ComponentConstructor {
71
+ new <
72
+ P extends Record<string, unknown> = Record<string, unknown>,
73
+ S extends Record<string, unknown> = Record<string, unknown>,
74
+ >(props: P): Component<P, S>;
75
+ <
76
+ P extends Record<string, unknown> = Record<string, unknown>,
77
+ S extends Record<string, unknown> = Record<string, unknown>,
78
+ >(this: Component<P, S>, props: P): void;
79
+ prototype: Component<any, any>;
65
80
  }
66
81
 
67
- export class PureComponent<
82
+ export const Component: ComponentConstructor = function Component<
83
+ P extends Record<string, unknown> = Record<string, unknown>,
84
+ S extends Record<string, unknown> = Record<string, unknown>,
85
+ >(this: Component<P, S>, props: P): void {
86
+ this.props = props;
87
+ } as ComponentConstructor;
88
+
89
+ Component.prototype.setState = function setState<
90
+ P extends Record<string, unknown>,
91
+ S extends Record<string, unknown>,
92
+ >(
93
+ this: Component<P, S>,
94
+ partial:
95
+ | Partial<S>
96
+ | ((
97
+ previousState: Readonly<S>,
98
+ props: Readonly<P>,
99
+ ) => Partial<S> | S | null),
100
+ callback?: () => void,
101
+ ): void {
102
+ enqueueClassSetState(
103
+ this as unknown as ClassComponentInstance,
104
+ partial as Parameters<NonNullable<ClassComponentInstance["setState"]>>[0],
105
+ callback,
106
+ );
107
+ };
108
+
109
+ Component.prototype.forceUpdate = function forceUpdate(
110
+ this: Component,
111
+ callback?: () => void,
112
+ ): void {
113
+ enqueueClassForceUpdate(this as unknown as ClassComponentInstance, callback);
114
+ };
115
+
116
+ Component.prototype.render = function render(): ReactCompatNode {
117
+ return null;
118
+ };
119
+
120
+ export interface PureComponent<
68
121
  P extends Record<string, unknown> = Record<string, unknown>,
69
122
  S extends Record<string, unknown> = Record<string, unknown>,
70
123
  > extends Component<P, S> {
71
- shouldComponentUpdate(nextProps: P, nextState: S): boolean {
72
- return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state ?? {}, nextState ?? {});
73
- }
124
+ shouldComponentUpdate(nextProps: P, nextState: S): boolean;
125
+ }
126
+
127
+ export interface PureComponentConstructor {
128
+ new <
129
+ P extends Record<string, unknown> = Record<string, unknown>,
130
+ S extends Record<string, unknown> = Record<string, unknown>,
131
+ >(props: P): PureComponent<P, S>;
132
+ <
133
+ P extends Record<string, unknown> = Record<string, unknown>,
134
+ S extends Record<string, unknown> = Record<string, unknown>,
135
+ >(this: PureComponent<P, S>, props: P): void;
136
+ prototype: PureComponent<any, any>;
74
137
  }
75
138
 
139
+ export const PureComponent: PureComponentConstructor = function PureComponent<
140
+ P extends Record<string, unknown> = Record<string, unknown>,
141
+ S extends Record<string, unknown> = Record<string, unknown>,
142
+ >(this: PureComponent<P, S>, props: P): void {
143
+ (Component as unknown as (this: unknown, props: unknown) => void).call(
144
+ this,
145
+ props,
146
+ );
147
+ } as PureComponentConstructor;
148
+
149
+ PureComponent.prototype = Object.create(Component.prototype) as PureComponent<any, any>;
150
+ PureComponent.prototype.constructor = PureComponent;
151
+ PureComponent.prototype.shouldComponentUpdate = function shouldComponentUpdate<
152
+ P extends Record<string, unknown>,
153
+ S extends Record<string, unknown>,
154
+ >(
155
+ this: PureComponent<P, S>,
156
+ nextProps: P,
157
+ nextState: S,
158
+ ): boolean {
159
+ return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state ?? {}, nextState ?? {});
160
+ };
161
+
76
162
  interface ClassLifecycleSnapshot {
77
163
  previousState?: Record<string, unknown>;
164
+ nextState?: Record<string, unknown>;
78
165
  force?: boolean;
79
166
  snapshot?: unknown;
80
167
  }
81
168
 
82
- const classLifecycleSnapshots = new WeakMap<
83
- ClassComponentInstance,
84
- ClassLifecycleSnapshot
85
- >();
169
+ interface ClassUpdateContext {
170
+ runtime: RootRuntime;
171
+ path: string;
172
+ }
173
+
174
+ interface ClassComponentGlobalState {
175
+ lifecycleSnapshots: WeakMap<ClassComponentInstance, ClassLifecycleSnapshot>;
176
+ updateContexts: WeakMap<ClassComponentInstance, ClassUpdateContext>;
177
+ pendingInstancesByRuntime: WeakMap<RootRuntime, Map<string, ClassComponentInstance>>;
178
+ dirtyPathsByRuntime: WeakMap<RootRuntime, Set<string>>;
179
+ }
180
+
181
+ const CLASS_COMPONENT_STATE_KEY = Symbol.for("modular.react.class_component_state");
182
+ const classComponentGlobalState =
183
+ ((globalThis as typeof globalThis & Record<symbol, ClassComponentGlobalState | undefined>)[
184
+ CLASS_COMPONENT_STATE_KEY
185
+ ] ??= {
186
+ lifecycleSnapshots: new WeakMap(),
187
+ updateContexts: new WeakMap(),
188
+ pendingInstancesByRuntime: new WeakMap(),
189
+ dirtyPathsByRuntime: new WeakMap(),
190
+ });
191
+ const classLifecycleSnapshots = classComponentGlobalState.lifecycleSnapshots;
192
+ const classUpdateContexts = classComponentGlobalState.updateContexts;
193
+ const classPendingInstancesByRuntime = classComponentGlobalState.pendingInstancesByRuntime;
194
+ const classDirtyPathsByRuntime = classComponentGlobalState.dirtyPathsByRuntime;
86
195
 
87
196
  export type ClassComponentRenderResult =
88
197
  | {
@@ -152,10 +261,27 @@ export function renderClassComponentWithRuntime(
152
261
  props: Record<string, unknown>,
153
262
  runtime: RootRuntime,
154
263
  path: string,
264
+ options: {
265
+ currentInstance?: ClassComponentInstance;
266
+ hasDirtyDescendant?: boolean;
267
+ } = {},
155
268
  ): ClassComponentRenderResult {
156
269
  return renderWithRootRuntime(runtime, path, () => {
157
270
  const instanceRef = useRef<ClassComponentInstance | undefined>(undefined);
158
271
  const didCommitRef = useRef(false);
272
+ const currentInstance =
273
+ options.currentInstance instanceof type
274
+ ? options.currentInstance
275
+ : getPendingClassInstance(runtime, path, type);
276
+
277
+ if (
278
+ currentInstance !== undefined &&
279
+ instanceRef.current !== currentInstance
280
+ ) {
281
+ instanceRef.current = currentInstance;
282
+ didCommitRef.current = true;
283
+ }
284
+
159
285
  const previousInstance = instanceRef.current;
160
286
  const hasDifferentType =
161
287
  previousInstance !== undefined && !(previousInstance instanceof type);
@@ -163,6 +289,7 @@ export function renderClassComponentWithRuntime(
163
289
 
164
290
  if (hasDifferentType) {
165
291
  classLifecycleSnapshots.delete(previousInstance);
292
+ classUpdateContexts.delete(previousInstance);
166
293
  didCommitRef.current = false;
167
294
  instanceRef.current = undefined;
168
295
  }
@@ -176,15 +303,21 @@ export function renderClassComponentWithRuntime(
176
303
  const previousState = snapshot?.previousState ?? instance.state ?? {};
177
304
 
178
305
  instanceRef.current = instance;
179
- installClassUpdateMethods(instance, runtime);
180
- applyDerivedStateFromProps(type, instance, props, previousState);
181
- const nextState = instance.state ?? {};
306
+ installClassUpdateMethods(instance, runtime, path);
307
+ clearPendingClassUpdate(runtime, path);
308
+ const nextState = resolveDerivedStateFromProps(
309
+ type,
310
+ props,
311
+ snapshot?.nextState ?? instance.state ?? {},
312
+ );
182
313
  const shouldSkipUpdate =
183
314
  didCommitRef.current &&
184
315
  snapshot?.force !== true &&
316
+ options.hasDirtyDescendant !== true &&
185
317
  instance.shouldComponentUpdate?.(props, nextState) === false;
186
318
 
187
319
  instance.props = props;
320
+ instance.state = nextState;
188
321
  installClassLifecycleEffects(
189
322
  instance,
190
323
  didCommitRef,
@@ -221,7 +354,7 @@ export function renderClassComponentWithRuntime(
221
354
 
222
355
  return { kind: "render", node: fallbackNode, instance, type };
223
356
  }
224
- });
357
+ }, CLASS_COMPONENT_RUNTIME_OWNER);
225
358
  }
226
359
 
227
360
  export function applyDerivedStateFromProps(
@@ -230,14 +363,22 @@ export function applyDerivedStateFromProps(
230
363
  nextProps: Record<string, unknown>,
231
364
  previousState: Record<string, unknown>,
232
365
  ): void {
366
+ instance.state = resolveDerivedStateFromProps(type, nextProps, previousState);
367
+ }
368
+
369
+ export function resolveDerivedStateFromProps(
370
+ type: ClassComponentType,
371
+ nextProps: Record<string, unknown>,
372
+ previousState: Record<string, unknown>,
373
+ ): Record<string, unknown> {
233
374
  const derivedState = type.getDerivedStateFromProps?.(nextProps, previousState);
234
375
 
235
376
  if (derivedState === undefined || derivedState === null) {
236
- return;
377
+ return previousState;
237
378
  }
238
379
 
239
- instance.state = {
240
- ...instance.state,
380
+ return {
381
+ ...previousState,
241
382
  ...derivedState,
242
383
  };
243
384
  }
@@ -329,35 +470,152 @@ export function reconcileErrorBoundary(
329
470
  function installClassUpdateMethods(
330
471
  instance: ClassComponentInstance,
331
472
  runtime: RootRuntime,
473
+ path: string,
332
474
  ): void {
333
- instance.setState = (partial, callback): void => {
334
- const previousState = instance.state ?? {};
335
- if (!classLifecycleSnapshots.has(instance)) {
336
- classLifecycleSnapshots.set(instance, { previousState });
337
- }
338
- const nextPartial =
339
- typeof partial === "function"
340
- ? partial(previousState, instance.props)
341
- : partial;
342
-
343
- if (nextPartial !== null) {
344
- instance.state = {
345
- ...previousState,
346
- ...nextPartial,
347
- };
348
- }
475
+ classUpdateContexts.set(instance, { runtime, path });
476
+ instance.setState = Component.prototype.setState;
477
+ instance.forceUpdate = Component.prototype.forceUpdate;
478
+ }
349
479
 
350
- runtime.rerender();
480
+ function enqueueClassSetState(
481
+ instance: ClassComponentInstance,
482
+ partial: Parameters<NonNullable<ClassComponentInstance["setState"]>>[0],
483
+ callback?: () => void,
484
+ ): void {
485
+ const updateContext = classUpdateContexts.get(instance);
486
+
487
+ if (updateContext === undefined) {
351
488
  callback?.call(instance);
352
- };
353
- instance.forceUpdate = (callback): void => {
354
- classLifecycleSnapshots.set(instance, {
355
- previousState: instance.state ?? {},
356
- force: true,
357
- });
358
- runtime.rerender();
489
+ return;
490
+ }
491
+
492
+ const snapshot = classLifecycleSnapshots.get(instance);
493
+ const previousState = snapshot?.previousState ?? instance.state ?? {};
494
+ const baseState = snapshot?.nextState ?? instance.state ?? {};
495
+ const nextPartial =
496
+ typeof partial === "function"
497
+ ? partial(baseState, instance.props)
498
+ : partial;
499
+
500
+ const nextState =
501
+ nextPartial === null
502
+ ? baseState
503
+ : {
504
+ ...baseState,
505
+ ...nextPartial,
506
+ };
507
+
508
+ classLifecycleSnapshots.set(instance, {
509
+ ...snapshot,
510
+ previousState,
511
+ nextState,
512
+ });
513
+
514
+ markClassInstanceDirty(instance, updateContext);
515
+ callback?.call(instance);
516
+ }
517
+
518
+ function enqueueClassForceUpdate(
519
+ instance: ClassComponentInstance,
520
+ callback?: () => void,
521
+ ): void {
522
+ const updateContext = classUpdateContexts.get(instance);
523
+
524
+ if (updateContext === undefined) {
359
525
  callback?.call(instance);
360
- };
526
+ return;
527
+ }
528
+
529
+ classLifecycleSnapshots.set(instance, {
530
+ previousState: instance.state ?? {},
531
+ force: true,
532
+ });
533
+ markClassInstanceDirty(instance, updateContext);
534
+ callback?.call(instance);
535
+ }
536
+
537
+ function markClassInstanceDirty(
538
+ instance: ClassComponentInstance,
539
+ updateContext: ClassUpdateContext,
540
+ ): void {
541
+ markPendingClassUpdate(instance, updateContext);
542
+ const runtimeInstance = updateContext.runtime.instances.get(updateContext.path) as
543
+ | { dirty?: boolean }
544
+ | undefined;
545
+ if (runtimeInstance !== undefined) {
546
+ runtimeInstance.dirty = true;
547
+ }
548
+ updateContext.runtime.rerender();
549
+ }
550
+
551
+ export function hasDirtyClassUpdate(
552
+ runtime: RootRuntime | undefined,
553
+ keys: readonly string[],
554
+ prefix?: string,
555
+ ): boolean {
556
+ if (runtime === undefined) {
557
+ return false;
558
+ }
559
+
560
+ const dirtyPaths = classDirtyPathsByRuntime.get(runtime);
561
+ if (dirtyPaths === undefined) {
562
+ return false;
563
+ }
564
+
565
+ for (const key of keys) {
566
+ if (dirtyPaths.has(key)) {
567
+ return true;
568
+ }
569
+ }
570
+
571
+ if (prefix === undefined) {
572
+ return false;
573
+ }
574
+
575
+ for (const dirtyPath of dirtyPaths) {
576
+ if (dirtyPath === prefix || dirtyPath.startsWith(`${prefix}.`)) {
577
+ return true;
578
+ }
579
+ }
580
+
581
+ return false;
582
+ }
583
+
584
+ function markPendingClassUpdate(
585
+ instance: ClassComponentInstance,
586
+ updateContext: ClassUpdateContext,
587
+ ): void {
588
+ let pendingInstances = classPendingInstancesByRuntime.get(updateContext.runtime);
589
+ if (pendingInstances === undefined) {
590
+ pendingInstances = new Map();
591
+ classPendingInstancesByRuntime.set(updateContext.runtime, pendingInstances);
592
+ }
593
+ let dirtyPaths = classDirtyPathsByRuntime.get(updateContext.runtime);
594
+ if (dirtyPaths === undefined) {
595
+ dirtyPaths = new Set();
596
+ classDirtyPathsByRuntime.set(updateContext.runtime, dirtyPaths);
597
+ }
598
+ pendingInstances.set(updateContext.path, instance);
599
+ dirtyPaths.add(updateContext.path);
600
+ }
601
+
602
+ function getPendingClassInstance(
603
+ runtime: RootRuntime,
604
+ path: string,
605
+ type: ClassComponentType,
606
+ ): ClassComponentInstance | undefined {
607
+ const dirtyPaths = classDirtyPathsByRuntime.get(runtime);
608
+ if (dirtyPaths?.has(path) !== true) {
609
+ return undefined;
610
+ }
611
+
612
+ const instance = classPendingInstancesByRuntime.get(runtime)?.get(path);
613
+ return instance instanceof type ? instance : undefined;
614
+ }
615
+
616
+ function clearPendingClassUpdate(runtime: RootRuntime, path: string): void {
617
+ classDirtyPathsByRuntime.get(runtime)?.delete(path);
618
+ classPendingInstancesByRuntime.get(runtime)?.delete(path);
361
619
  }
362
620
 
363
621
  function installClassLifecycleEffects(
@@ -369,7 +627,10 @@ function installClassLifecycleEffects(
369
627
  replacedInstance?: ClassComponentInstance,
370
628
  ): void {
371
629
  useLayoutEffect(() => {
372
- replacedInstance?.componentWillUnmount?.();
630
+ if (replacedInstance !== undefined) {
631
+ replacedInstance.componentWillUnmount?.();
632
+ classUpdateContexts.delete(replacedInstance);
633
+ }
373
634
 
374
635
  if (skipUpdate) {
375
636
  classLifecycleSnapshots.delete(instance);
@@ -394,6 +655,7 @@ function installClassLifecycleEffects(
394
655
  return () => {
395
656
  instance.componentWillUnmount?.();
396
657
  classLifecycleSnapshots.delete(instance);
658
+ classUpdateContexts.delete(instance);
397
659
  };
398
660
  }, []);
399
661
  }
package/src/context.ts CHANGED
@@ -1,5 +1,5 @@
1
- const REACT_COMPAT_PROVIDER_TYPE = Symbol.for("modular.react.provider");
2
- const REACT_COMPAT_CONSUMER_TYPE = Symbol.for("modular.react.consumer");
1
+ const REACT_COMPAT_PROVIDER_TYPE = Symbol.for("react.context");
2
+ const REACT_COMPAT_CONSUMER_TYPE = Symbol.for("react.consumer");
3
3
 
4
4
  export interface ReactCompatContext<T> {
5
5
  defaultValue: T;
@@ -9,18 +9,50 @@ export interface ReactCompatContext<T> {
9
9
  displayName: string | undefined;
10
10
  }
11
11
 
12
+ export interface ReactCompatExternalContext<T> {
13
+ $$typeof: typeof REACT_COMPAT_PROVIDER_TYPE;
14
+ _currentValue?: T;
15
+ _currentValue2?: T;
16
+ _defaultValue?: T;
17
+ Provider?: unknown;
18
+ Consumer?: unknown;
19
+ displayName?: string | undefined;
20
+ }
21
+
22
+ export type ReactCompatContextLike<T> =
23
+ | ReactCompatContext<T>
24
+ | ReactCompatExternalContext<T>;
25
+
12
26
  export interface ReactCompatProvider<T> {
13
27
  $$typeof: typeof REACT_COMPAT_PROVIDER_TYPE;
14
- context: ReactCompatContext<T>;
28
+ context?: ReactCompatContextLike<T>;
15
29
  displayName: string | undefined;
16
30
  }
17
31
 
18
32
  export interface ReactCompatConsumer<T> {
19
33
  $$typeof: typeof REACT_COMPAT_CONSUMER_TYPE;
20
- context: ReactCompatContext<T>;
34
+ context?: ReactCompatContextLike<T>;
35
+ _context?: ReactCompatContextLike<T>;
21
36
  displayName: string | undefined;
22
37
  }
23
38
 
39
+ type ContextReadObserver = (context: ReactCompatContextLike<unknown>, value: unknown) => void;
40
+ interface ContextReadObserverState {
41
+ current: ContextReadObserver | undefined;
42
+ }
43
+
44
+ const externalContextValues = new WeakMap<object, unknown[]>();
45
+
46
+ const CONTEXT_READ_OBSERVER_STATE_KEY = Symbol.for(
47
+ "modular.react.context_read_observer_state",
48
+ );
49
+ const contextReadObserverState =
50
+ ((globalThis as typeof globalThis & Record<symbol, ContextReadObserverState | undefined>)[
51
+ CONTEXT_READ_OBSERVER_STATE_KEY
52
+ ] ??= {
53
+ current: undefined,
54
+ });
55
+
24
56
  export function createContext<T>(defaultValue: T): ReactCompatContext<T> {
25
57
  const context: ReactCompatContext<T> = {
26
58
  defaultValue,
@@ -62,8 +94,37 @@ function installContextDisplayName<T>(context: ReactCompatContext<T>): void {
62
94
  });
63
95
  }
64
96
 
65
- export function useContext<T>(context: ReactCompatContext<T>): T {
66
- return context.values.at(-1) ?? context.defaultValue;
97
+ export function useContext<T>(context: ReactCompatContextLike<T>): T {
98
+ const value = readContextValue(context);
99
+ contextReadObserverState.current?.(context as ReactCompatContextLike<unknown>, value);
100
+ return value;
101
+ }
102
+
103
+ export function readContextValue<T>(context: ReactCompatContextLike<T>): T {
104
+ if (isInternalContextRecord(context)) {
105
+ return context.values.at(-1) ?? context.defaultValue;
106
+ }
107
+
108
+ return (
109
+ (externalContextValues.get(context)?.at(-1) as T | undefined) ??
110
+ context._currentValue ??
111
+ context._currentValue2 ??
112
+ (context._defaultValue as T)
113
+ );
114
+ }
115
+
116
+ export function withContextReadObserver<T>(
117
+ observer: ContextReadObserver,
118
+ render: () => T,
119
+ ): T {
120
+ const previousObserver = contextReadObserverState.current;
121
+ contextReadObserverState.current = observer;
122
+
123
+ try {
124
+ return render();
125
+ } finally {
126
+ contextReadObserverState.current = previousObserver;
127
+ }
67
128
  }
68
129
 
69
130
  export function isReactCompatProvider(
@@ -117,11 +178,27 @@ export function pushContextProvider<T>(
117
178
  provider: ReactCompatProvider<T>,
118
179
  value: T,
119
180
  ): void {
120
- provider.context.values.push(value);
181
+ const context = providerContext(provider);
182
+
183
+ if (isInternalContextRecord(context)) {
184
+ context.values.push(value);
185
+ return;
186
+ }
187
+
188
+ const values = externalContextValues.get(context) ?? [];
189
+ values.push(value);
190
+ externalContextValues.set(context, values);
121
191
  }
122
192
 
123
193
  export function popContextProvider<T>(provider: ReactCompatProvider<T>): void {
124
- provider.context.values.pop();
194
+ const context = providerContext(provider);
195
+
196
+ if (isInternalContextRecord(context)) {
197
+ context.values.pop();
198
+ return;
199
+ }
200
+
201
+ externalContextValues.get(context)?.pop();
125
202
  }
126
203
 
127
204
  export function renderContextProviderToString<T>(
@@ -136,5 +213,27 @@ export function renderContextConsumerToString<T>(
136
213
  consumer: ReactCompatConsumer<T>,
137
214
  render: (value: T) => string,
138
215
  ): string {
139
- return render(useContext(consumer.context));
216
+ return render(useContext(consumerContext(consumer)));
217
+ }
218
+
219
+ export function providerContext<T>(
220
+ provider: ReactCompatProvider<T>,
221
+ ): ReactCompatContextLike<T> {
222
+ return provider.context ?? (provider as unknown as ReactCompatExternalContext<T>);
223
+ }
224
+
225
+ export function consumerContext<T>(
226
+ consumer: ReactCompatConsumer<T>,
227
+ ): ReactCompatContextLike<T> {
228
+ return (
229
+ consumer.context ??
230
+ consumer._context ??
231
+ (consumer as unknown as ReactCompatExternalContext<T>)
232
+ );
233
+ }
234
+
235
+ function isInternalContextRecord<T>(
236
+ context: ReactCompatContextLike<T>,
237
+ ): context is ReactCompatContext<T> {
238
+ return "values" in context && Array.isArray(context.values);
140
239
  }