@ccheever/exact-renderer 0.1.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.
Files changed (80) hide show
  1. package/package.json +118 -0
  2. package/src/__tests__/adapter-window-state.test.tsx +190 -0
  3. package/src/__tests__/attrs.test.ts +157 -0
  4. package/src/__tests__/classname.test.ts +332 -0
  5. package/src/__tests__/color.test.ts +169 -0
  6. package/src/__tests__/dom-mirror.test.ts +682 -0
  7. package/src/__tests__/dom-shim.test.ts +274 -0
  8. package/src/__tests__/fixtures/SvelteCounter.svelte +7 -0
  9. package/src/__tests__/fixtures/SvelteInput.svelte +8 -0
  10. package/src/__tests__/host-config.test.ts +51 -0
  11. package/src/__tests__/host-ops.test.ts +2234 -0
  12. package/src/__tests__/image-source.test.ts +135 -0
  13. package/src/__tests__/liquid-glass.test.ts +72 -0
  14. package/src/__tests__/multi-root.test.ts +118 -0
  15. package/src/__tests__/native-view-events.test.ts +102 -0
  16. package/src/__tests__/nodes.test.ts +399 -0
  17. package/src/__tests__/normalize.test.ts +576 -0
  18. package/src/__tests__/paragraph-lowering.test.tsx +144 -0
  19. package/src/__tests__/props.test.ts +518 -0
  20. package/src/__tests__/protocol-encoder.test.ts +732 -0
  21. package/src/__tests__/protocol-fixture-bytes.test.ts +41 -0
  22. package/src/__tests__/reconciler.test.tsx +241 -0
  23. package/src/__tests__/svelte-adapter.test.ts +166 -0
  24. package/src/__tests__/svg-source.test.ts +71 -0
  25. package/src/__tests__/tags.test.ts +354 -0
  26. package/src/__tests__/toggle.test.ts +441 -0
  27. package/src/__tests__/transitions.test.ts +106 -0
  28. package/src/__tests__/web-primitives.test.tsx +454 -0
  29. package/src/__tests__/window-hooks.test.tsx +447 -0
  30. package/src/adapter-contract.ts +68 -0
  31. package/src/attrs.ts +596 -0
  32. package/src/classname-contract.ts +87 -0
  33. package/src/classname-resolve.ts +553 -0
  34. package/src/classname-runtime.ts +29 -0
  35. package/src/components.ts +214 -0
  36. package/src/css-variable-context.ts +83 -0
  37. package/src/dom-hydration.ts +160 -0
  38. package/src/dom-mirror.ts +1459 -0
  39. package/src/dom-shim.ts +1736 -0
  40. package/src/group-context.ts +69 -0
  41. package/src/host-config.ts +431 -0
  42. package/src/host-ops.ts +3167 -0
  43. package/src/image-source.native.ts +703 -0
  44. package/src/image-source.ts +554 -0
  45. package/src/index.ts +278 -0
  46. package/src/inspector-runtime.ts +244 -0
  47. package/src/inspector.ts +3570 -0
  48. package/src/jsx-augmentations.ts +54 -0
  49. package/src/keyboard-avoidance.ts +217 -0
  50. package/src/native-primitives.ts +43 -0
  51. package/src/native-view-events.ts +322 -0
  52. package/src/native-view.ts +60 -0
  53. package/src/nodes/index.ts +41 -0
  54. package/src/nodes/node.ts +531 -0
  55. package/src/peer-context.ts +100 -0
  56. package/src/primitives.native.ts +8 -0
  57. package/src/primitives.ts +8 -0
  58. package/src/props/index.ts +14 -0
  59. package/src/props/normalize.ts +816 -0
  60. package/src/protocol/encoder.ts +940 -0
  61. package/src/protocol/index.ts +33 -0
  62. package/src/reconciler.ts +581 -0
  63. package/src/runtime.ts +11 -0
  64. package/src/safe-area.ts +543 -0
  65. package/src/solid.ts +490 -0
  66. package/src/style/color.js +1 -0
  67. package/src/style/color.ts +15 -0
  68. package/src/style/index.js +1 -0
  69. package/src/style/index.ts +22 -0
  70. package/src/style/normalize.js +1 -0
  71. package/src/style/normalize.ts +1426 -0
  72. package/src/svelte.ts +349 -0
  73. package/src/svg-source.ts +222 -0
  74. package/src/tags/index.ts +21 -0
  75. package/src/tags/tag-map.ts +289 -0
  76. package/src/text/paragraph-lowering.ts +310 -0
  77. package/src/types.ts +1175 -0
  78. package/src/vue.ts +535 -0
  79. package/src/web-host.ts +19 -0
  80. package/src/web-primitives.ts +1654 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Protocol Encoding - Public Exports
3
+ *
4
+ * This module re-exports the protocol encoding utilities.
5
+ */
6
+
7
+ export type { ProtocolEncoder } from './encoder.js';
8
+ export {
9
+ createProtocolEncoder,
10
+ setStyle,
11
+ setTransform,
12
+ setOpacity,
13
+ setBackgroundColor,
14
+ setTransition,
15
+ setTextContent,
16
+ setLang,
17
+ setImageSource,
18
+ setPlaceholder,
19
+ setAccessibilityLabel,
20
+ setToggleValue,
21
+ setGlassEffect,
22
+ setTintColor,
23
+ setDisabled,
24
+ setShowsScrollIndicator,
25
+ dispatchProtocol,
26
+ getScreenDimensions,
27
+ encodeCreateElement,
28
+ encodeCreateText,
29
+ encodeProps,
30
+ encodeEvents,
31
+ encodeChildren,
32
+ encodeDestroy,
33
+ } from './encoder.js';
@@ -0,0 +1,581 @@
1
+ // @system @ref LLP 0007 — React reconciler surface for Exact
2
+ /**
3
+ * Exact React Reconciler
4
+ *
5
+ * This module provides the public API for rendering React elements
6
+ * to the Exact runtime. It wraps the react-reconciler with our
7
+ * custom host config.
8
+ *
9
+ * Public API:
10
+ * - render(element): Render a React element tree
11
+ * - unmount(): Unmount the current tree
12
+ *
13
+ * Event Dispatch:
14
+ * - Events from native are dispatched through __exactDispatchEvent (in host-ops.ts)
15
+ * - Each framework's reactivity handles updates
16
+ */
17
+
18
+ // @ts-ignore - react-reconciler types don't exactly match runtime
19
+ import Reconciler from 'react-reconciler';
20
+ import type { ReactElement } from 'react';
21
+
22
+ import {
23
+ __setActiveWindowRenderer,
24
+ subscribeToRootWindowState,
25
+ type Unsubscribe,
26
+ } from '@exact/core/window-state';
27
+ import { hostConfig, _clearHandlers } from './host-config.js';
28
+ import {
29
+ createRoot,
30
+ destroyRoot,
31
+ renderErrorFallback,
32
+ setNativeEventBatcher,
33
+ syncRootInheritedWindowState,
34
+ syncRootWindowState,
35
+ unregisterInspectorRoot,
36
+ type RootNode,
37
+ } from './host-ops.js';
38
+ import { _resetInspectorState } from './inspector-runtime.js';
39
+ import { _resetNativeViewEventBridgeForTests } from './native-view-events.js';
40
+ import { _resetNodeIdCounter } from './nodes/index.js';
41
+
42
+ /**
43
+ * Development mode flag - gate verbose logging.
44
+ */
45
+ const __DEV__ = process.env.NODE_ENV !== 'production';
46
+
47
+ // =============================================================================
48
+ // Create Reconciler
49
+ // =============================================================================
50
+
51
+ /**
52
+ * Create the React reconciler with our host config.
53
+ */
54
+ const reconciler = Reconciler(hostConfig);
55
+ type ReconcilerWithSyncInternals = typeof reconciler & {
56
+ updateContainerSync?: (
57
+ element: ReactElement | null,
58
+ fiber: unknown,
59
+ parentComponent: unknown,
60
+ callback: () => void,
61
+ ) => void;
62
+ flushSyncWork?: () => void;
63
+ batchedUpdates?: (callback: () => void) => void;
64
+ };
65
+ const reconcilerWithSyncInternals = reconciler as ReconcilerWithSyncInternals;
66
+ if (typeof reconcilerWithSyncInternals.batchedUpdates === 'function') {
67
+ setNativeEventBatcher((callback) => {
68
+ reconcilerWithSyncInternals.batchedUpdates!(callback);
69
+ });
70
+ }
71
+
72
+ // =============================================================================
73
+ // Root State
74
+ // =============================================================================
75
+
76
+ /**
77
+ * The root container node.
78
+ */
79
+ let rootContainer: RootNode | null = null;
80
+
81
+ /**
82
+ * The fiber root for React.
83
+ */
84
+ let rootFiber: unknown = null;
85
+
86
+ /**
87
+ * The currently rendered element (for explicit forceUpdate() replays).
88
+ */
89
+ let currentElement: ReactElement | null = null;
90
+ let rootWindowStateUnsubscribe: Unsubscribe | null = null;
91
+
92
+ type RendererDebugState = Partial<{
93
+ enteredRender: boolean;
94
+ createdRoot: boolean;
95
+ updateScheduled: boolean;
96
+ renderError: string | null;
97
+ }>;
98
+
99
+ const rendererDebugScope = globalThis as typeof globalThis & {
100
+ __exactRendererDebugState?: RendererDebugState;
101
+ };
102
+
103
+ function recordRendererDebugState(patch: RendererDebugState): void {
104
+ Object.assign(rendererDebugScope.__exactRendererDebugState ??= {}, patch);
105
+ }
106
+
107
+ // =============================================================================
108
+ // Render State Management
109
+ // =============================================================================
110
+
111
+ /**
112
+ * Whether we're currently in the middle of a render.
113
+ */
114
+ let isRendering = false;
115
+
116
+ function updateContainerSyncIfAvailable(
117
+ element: ReactElement | null,
118
+ fiber: unknown,
119
+ onComplete: () => void,
120
+ ): boolean {
121
+ if (typeof reconcilerWithSyncInternals.updateContainerSync !== 'function') {
122
+ return false;
123
+ }
124
+
125
+ reconcilerWithSyncInternals.updateContainerSync(element, fiber, null, onComplete);
126
+ if (typeof reconcilerWithSyncInternals.flushSyncWork === 'function') {
127
+ reconcilerWithSyncInternals.flushSyncWork();
128
+ }
129
+ return true;
130
+ }
131
+
132
+ function performContainerUpdate(
133
+ element: ReactElement | null,
134
+ fiber: unknown,
135
+ onComplete: () => void,
136
+ ): void {
137
+ if (updateContainerSyncIfAvailable(element, fiber, onComplete)) {
138
+ return;
139
+ }
140
+
141
+ if (typeof reconciler.flushSync === 'function') {
142
+ reconciler.flushSync(() => {
143
+ reconciler.updateContainer(element, fiber, null, onComplete);
144
+ });
145
+ return;
146
+ }
147
+
148
+ reconciler.updateContainer(element, fiber, null, onComplete);
149
+ }
150
+
151
+ function formatRenderErrorMessage(error: unknown): string {
152
+ if (__DEV__ && error instanceof Error && error.message) {
153
+ return `Render error: ${error.message}`;
154
+ }
155
+
156
+ return 'Something went wrong.';
157
+ }
158
+
159
+ function handleRenderFailure(
160
+ label: string,
161
+ container: RootNode | null,
162
+ clearCurrentElement: () => void,
163
+ error: unknown
164
+ ): void {
165
+ clearCurrentElement();
166
+ console.error(`[${label}] Render failed:`, error);
167
+
168
+ if (!container) {
169
+ return;
170
+ }
171
+
172
+ try {
173
+ renderErrorFallback(container, formatRenderErrorMessage(error));
174
+ } catch (fallbackError) {
175
+ console.error(`[${label}] Fallback render failed:`, fallbackError);
176
+ }
177
+ }
178
+
179
+ // =============================================================================
180
+ // Event Handling
181
+ // =============================================================================
182
+
183
+ // Note: Event dispatch is handled in host-ops.ts via __exactDispatchEvent.
184
+ // React state updates should flush through hostConfig.resetAfterCommit().
185
+
186
+ /**
187
+ * Trigger an explicit re-render of the current tree.
188
+ * This is only used for manual forceUpdate() calls and explicit remounts.
189
+ */
190
+ function triggerRerender(): void {
191
+ if (!rootFiber || !currentElement) {
192
+ return;
193
+ }
194
+
195
+ if (isRendering) {
196
+ return;
197
+ }
198
+
199
+ isRendering = true;
200
+
201
+ try {
202
+ performContainerUpdate(currentElement, rootFiber, () => {
203
+ isRendering = false;
204
+ });
205
+ } catch (error) {
206
+ isRendering = false;
207
+ handleRenderFailure('Reconciler', rootContainer, () => {
208
+ currentElement = null;
209
+ }, error);
210
+ }
211
+ }
212
+
213
+ function subscribeMainRootWindowState(): void {
214
+ rootWindowStateUnsubscribe?.();
215
+ if (!rootContainer) {
216
+ rootWindowStateUnsubscribe = null;
217
+ return;
218
+ }
219
+
220
+ rootWindowStateUnsubscribe = subscribeToRootWindowState(rootContainer.rootId, () => {
221
+ if (!rootFiber || !currentElement || isRendering) {
222
+ if (rootContainer) {
223
+ syncRootWindowState(rootContainer);
224
+ }
225
+ return;
226
+ }
227
+
228
+ syncRootInheritedWindowState(rootContainer);
229
+ });
230
+ }
231
+
232
+ // =============================================================================
233
+ // Public API
234
+ // =============================================================================
235
+
236
+ /**
237
+ * Render a React element tree to the Exact runtime.
238
+ *
239
+ * @param element - The React element to render
240
+ *
241
+ * @example
242
+ * ```tsx
243
+ * import { render } from '@exact/runtime';
244
+ *
245
+ * function App() {
246
+ * return <div style={{ flex: 1, backgroundColor: '#fff' }}>Hello Exact!</div>;
247
+ * }
248
+ *
249
+ * render(<App />);
250
+ * ```
251
+ */
252
+ export function render(element: ReactElement): void {
253
+ if ((globalThis as any).__exactLoadTimings) (globalThis as any).__exactLoadTimings.renderNativeStart = Date.now();
254
+ recordRendererDebugState({
255
+ enteredRender: true,
256
+ renderError: null,
257
+ });
258
+ __setActiveWindowRenderer('react');
259
+ // Create root container if needed
260
+ if (!rootContainer) {
261
+ // Use shared createRoot from host-ops (creates node and registers with native)
262
+ rootContainer = createRoot(0, 'react');
263
+ recordRendererDebugState({
264
+ createdRoot: true,
265
+ });
266
+ subscribeMainRootWindowState();
267
+
268
+ // @ts-expect-error - createContainer args vary by react-reconciler version
269
+ rootFiber = reconciler.createContainer(
270
+ rootContainer,
271
+ 1, // LegacyRoot (sync rendering) - ConcurrentRoot (0) doesn't work in JSC
272
+ null, // hydrationCallbacks
273
+ false, // isStrictMode
274
+ false, // concurrentUpdatesByDefaultOverride
275
+ '', // identifierPrefix
276
+ (error: Error) => console.error('[Reconciler] Recoverable error:', error),
277
+ null // transitionCallbacks
278
+ );
279
+ }
280
+
281
+ // Store the current element so forceUpdate() can replay it if needed.
282
+ currentElement = element;
283
+
284
+ isRendering = true;
285
+
286
+ try {
287
+ performContainerUpdate(element, rootFiber, () => {
288
+ isRendering = false;
289
+ });
290
+ recordRendererDebugState({
291
+ updateScheduled: true,
292
+ });
293
+ } catch (error) {
294
+ isRendering = false;
295
+ recordRendererDebugState({
296
+ renderError: error instanceof Error ? error.message : String(error),
297
+ });
298
+ handleRenderFailure('Reconciler', rootContainer, () => {
299
+ currentElement = null;
300
+ }, error);
301
+ }
302
+ if ((globalThis as any).__exactLoadTimings) (globalThis as any).__exactLoadTimings.renderNativeEnd = Date.now();
303
+ }
304
+
305
+ /**
306
+ * Unmount the current React tree.
307
+ *
308
+ * @example
309
+ * ```tsx
310
+ * import { render, unmount } from '@exact/runtime';
311
+ *
312
+ * render(<App />);
313
+ *
314
+ * // Later...
315
+ * unmount();
316
+ * ```
317
+ */
318
+ export function unmount(): void {
319
+ if (rootFiber) {
320
+ performContainerUpdate(null, rootFiber, () => {});
321
+ currentElement = null;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Fully reset the renderer state.
327
+ * Clears the root container, fiber, event handlers, and event queue.
328
+ * Use this for tests or multi-root scenarios that need a clean slate.
329
+ *
330
+ * @example
331
+ * ```tsx
332
+ * import { render, reset } from '@exact/runtime';
333
+ *
334
+ * // Render an app
335
+ * render(<App />);
336
+ *
337
+ * // Reset everything for a fresh start
338
+ * reset();
339
+ *
340
+ * // Render a different app
341
+ * render(<OtherApp />);
342
+ * ```
343
+ */
344
+ export function reset(): void {
345
+ // Unmount current tree if any
346
+ if (rootFiber) {
347
+ performContainerUpdate(null, rootFiber, () => {});
348
+ }
349
+ for (const [rootId, instance] of rootInstances) {
350
+ performContainerUpdate(null, instance.fiber, () => {});
351
+ instance.unsubscribeWindowState?.();
352
+ destroyRoot(rootId);
353
+ }
354
+ rootInstances.clear();
355
+
356
+ // Clear all state
357
+ if (rootContainer) {
358
+ destroyRoot(rootContainer.rootId);
359
+ }
360
+ rootContainer = null;
361
+ rootFiber = null;
362
+ currentElement = null;
363
+ isRendering = false;
364
+ rootWindowStateUnsubscribe?.();
365
+ rootWindowStateUnsubscribe = null;
366
+
367
+ // Clear handler registry (in host-ops)
368
+ _clearHandlers();
369
+ }
370
+
371
+ /**
372
+ * Reset the renderer for native bundle replacement without dispatching a
373
+ * teardown batch to the host.
374
+ *
375
+ * The native shell already resets the kernel as part of bundle replacement, so
376
+ * sending destroy operations from the old JS app is both redundant and risky:
377
+ * those stale teardown batches can race with the new bundle's initial mount and
378
+ * leave the fresh kernel tree empty. This path intentionally clears only the
379
+ * JS-side renderer state.
380
+ */
381
+ export function prepareForBundleReplacement(): void {
382
+ for (const instance of rootInstances.values()) {
383
+ instance.unsubscribeWindowState?.();
384
+ unregisterInspectorRoot(instance.container.rootId);
385
+ }
386
+ rootInstances.clear();
387
+
388
+ if (rootContainer) {
389
+ unregisterInspectorRoot(rootContainer.rootId);
390
+ }
391
+
392
+ rootContainer = null;
393
+ rootFiber = null;
394
+ currentElement = null;
395
+ isRendering = false;
396
+ rootWindowStateUnsubscribe?.();
397
+ rootWindowStateUnsubscribe = null;
398
+
399
+ _clearHandlers();
400
+ _resetNativeViewEventBridgeForTests();
401
+ _resetInspectorState();
402
+ _resetNodeIdCounter();
403
+ }
404
+
405
+ /**
406
+ * Force a re-render of the current element.
407
+ * Useful for debugging or when external state changes.
408
+ */
409
+ export function forceUpdate(): void {
410
+ if (currentElement) {
411
+ triggerRerender();
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Re-sync viewport/safe-area dependent host state without relying on a React
417
+ * replay. This is useful when window metrics changed outside the framework's
418
+ * reactive graph but the mounted tree still needs its inherited layout state
419
+ * recomputed immediately.
420
+ */
421
+ export function syncWindowState(rootId?: number): void {
422
+ if ((rootId === undefined || rootId === 0) && rootContainer) {
423
+ syncRootInheritedWindowState(rootContainer);
424
+ }
425
+
426
+ for (const [instanceRootId, instance] of rootInstances) {
427
+ if (instanceRootId === 0) {
428
+ continue;
429
+ }
430
+ if (rootId !== undefined && instanceRootId !== rootId) {
431
+ continue;
432
+ }
433
+ syncRootInheritedWindowState(instance.container);
434
+ }
435
+ }
436
+
437
+ /**
438
+ * Get the current root container (for debugging).
439
+ * @internal
440
+ */
441
+ export function _getRootContainer(): RootNode | null {
442
+ return rootContainer;
443
+ }
444
+
445
+ /**
446
+ * Check if a render is in progress.
447
+ * @internal
448
+ */
449
+ export function _isRendering(): boolean {
450
+ return isRendering;
451
+ }
452
+
453
+ /**
454
+ * Get the event queue length (for debugging).
455
+ * @internal
456
+ * @deprecated Event queue removed - events handled immediately via host-ops
457
+ */
458
+ export function _getEventQueueLength(): number {
459
+ return 0;
460
+ }
461
+
462
+ // =============================================================================
463
+ // Multi-Root API
464
+ // =============================================================================
465
+
466
+ /**
467
+ * Per-root state for multi-root support.
468
+ */
469
+ interface RootInstance {
470
+ container: RootNode;
471
+ fiber: unknown;
472
+ element: ReactElement | null;
473
+ isRendering: boolean;
474
+ unsubscribeWindowState: Unsubscribe | null;
475
+ }
476
+
477
+ const rootInstances = new Map<number, RootInstance>();
478
+
479
+ /**
480
+ * An independent rendering surface.
481
+ * Each ExactRoot has its own React tree, container, and render cycle.
482
+ */
483
+ export interface ExactRoot {
484
+ /** The root ID for this rendering surface. */
485
+ readonly rootId: number;
486
+
487
+ /** Render a React element tree into this root. */
488
+ render(element: ReactElement): void;
489
+
490
+ /** Unmount the React tree from this root. */
491
+ unmount(): void;
492
+ }
493
+
494
+ /**
495
+ * Create an independent rendering surface with its own React tree.
496
+ *
497
+ * @param rootId - A unique identifier for this root (must be > 0)
498
+ * @returns An ExactRoot object with render() and unmount() methods
499
+ *
500
+ * @example
501
+ * ```tsx
502
+ * import { createExactRoot } from '@exact/runtime';
503
+ *
504
+ * const root1 = createExactRoot(1);
505
+ * const root2 = createExactRoot(2);
506
+ *
507
+ * root1.render(<Sidebar />);
508
+ * root2.render(<MainContent />);
509
+ * ```
510
+ */
511
+ export function createExactRoot(rootId: number): ExactRoot {
512
+ __setActiveWindowRenderer('react');
513
+ if (rootId === 0) {
514
+ throw new Error('createExactRoot: rootId must be > 0. Use render() for the default root.');
515
+ }
516
+ if (rootInstances.has(rootId)) {
517
+ throw new Error(`createExactRoot: rootId ${rootId} is already in use.`);
518
+ }
519
+
520
+ const container = createRoot(rootId, 'react');
521
+
522
+ // @ts-expect-error - createContainer args vary by react-reconciler version
523
+ const fiber = reconciler.createContainer(
524
+ container,
525
+ 1, // LegacyRoot (sync rendering)
526
+ null, // hydrationCallbacks
527
+ false, // isStrictMode
528
+ false, // concurrentUpdatesByDefaultOverride
529
+ '', // identifierPrefix
530
+ (error: Error) => console.error(`[Reconciler:${rootId}] Recoverable error:`, error),
531
+ null // transitionCallbacks
532
+ );
533
+
534
+ const instance: RootInstance = {
535
+ container,
536
+ fiber,
537
+ element: null,
538
+ isRendering: false,
539
+ unsubscribeWindowState: null,
540
+ };
541
+
542
+ instance.unsubscribeWindowState = subscribeToRootWindowState(rootId, () => {
543
+ if (!instance.element || instance.isRendering) {
544
+ syncRootWindowState(instance.container);
545
+ return;
546
+ }
547
+
548
+ syncRootInheritedWindowState(instance.container);
549
+ });
550
+
551
+ rootInstances.set(rootId, instance);
552
+
553
+ return {
554
+ rootId,
555
+
556
+ render(element: ReactElement): void {
557
+ instance.element = element;
558
+ instance.isRendering = true;
559
+
560
+ try {
561
+ performContainerUpdate(element, instance.fiber, () => {
562
+ instance.isRendering = false;
563
+ });
564
+ } catch (error) {
565
+ instance.isRendering = false;
566
+ handleRenderFailure(`Reconciler:${rootId}`, instance.container, () => {
567
+ instance.element = null;
568
+ }, error);
569
+ }
570
+ },
571
+
572
+ unmount(): void {
573
+ performContainerUpdate(null, instance.fiber, () => {});
574
+ instance.element = null;
575
+ instance.unsubscribeWindowState?.();
576
+ instance.unsubscribeWindowState = null;
577
+ rootInstances.delete(rootId);
578
+ destroyRoot(rootId);
579
+ },
580
+ };
581
+ }
package/src/runtime.ts ADDED
@@ -0,0 +1,11 @@
1
+ export {
2
+ createExactRoot,
3
+ forceUpdate,
4
+ prepareForBundleReplacement,
5
+ render,
6
+ reset,
7
+ syncWindowState,
8
+ unmount,
9
+ } from './reconciler.js';
10
+
11
+ export type { ExactRoot } from './reconciler.js';