@atolljs/islands 0.1.0 → 0.1.1

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.
@@ -1,446 +0,0 @@
1
- /**
2
- * React-reconciler host adapter for the worker DOM backend.
3
- *
4
- * The generic instance state (op queues, instance ids, handler registry,
5
- * doorbell, geometry) lives in `instance.ts`. This file only wires the
6
- * reconciler's create/append/update/remove/commit hooks to that backend.
7
- *
8
- * react-reconciler@0.34 reads ~150 fields off the config object. Most are
9
- * stubs for features this renderer doesn't implement (hydration,
10
- * persistence, resources, singletons, view transitions, test selectors,
11
- * scope/hydratable APIs) — they exist so nothing throws when the reconciler
12
- * destructures them; they are never invoked behind the `supports*` flags.
13
- */
14
-
15
- import { createContext } from 'react';
16
- import {
17
- bumpOpsVersion,
18
- getActiveInstance,
19
- getLastActiveInstance,
20
- instances,
21
- pushOp,
22
- serializeProps,
23
- unregisterHandler,
24
- type ElementInstance,
25
- type HostInstance,
26
- type RootContainer,
27
- type TextInstance,
28
- } from './instance';
29
- import { newElement, newText } from './instance';
30
- import { docForInstance, type ProxyNode } from './proxyDom';
31
-
32
- /* ── Event-priority plumbing ────────────────────────────────────────────── */
33
- /*
34
- * React 19 expresses event priorities as lane numbers: 2 = discrete
35
- * (click/keydown → SyncLane), 8 = continuous, 32 = default, 0 = none. The
36
- * reconciler calls setCurrentUpdatePriority around its own flushes
37
- * (flushSyncFromReconciler sets 2), so honoring the set/get pair and echoing
38
- * the value back from resolveUpdatePriority is all that's needed.
39
- */
40
- let currentUpdatePriority = 0;
41
- const DEFAULT_EVENT_PRIORITY = 32;
42
-
43
- /* ── React-19 transition context (must be real React objects) ───────────── */
44
-
45
- const NotPendingTransition = Object.freeze({ pending: false, data: null, method: null, action: null });
46
- const HostTransitionContext = createContext(NotPendingTransition);
47
-
48
- const noop = (): void => {};
49
- const NULL = (): null => null;
50
- const FALSE = (): boolean => false;
51
- const TRUE = (): boolean => true;
52
-
53
- /* ── Refs & portals ───────────────────────────────────────────────────────
54
- *
55
- * React refs must receive an object the COMPONENT treats as "the element" —
56
- * for React DOM that's the real DOM node, for us the closest truthful thing
57
- * is the proxy DOM's facade (it navigates the shadow tree, mutates via ops,
58
- * and reports `nodeType === 1`). getPublicInstance therefore adopts the
59
- * host instance into a per-instance proxy document, created lazily so React
60
- * mounts only pay for it when something actually holds a ref.
61
- *
62
- * This is what makes react-dom's `createPortal(children, ref.current)` work
63
- * here: isValidContainer() only checks nodeType, the portal fiber then feeds
64
- * the facade back as `containerInfo`, and the container-level methods below
65
- * unwrap `.instance` so portal children land inside the real target node —
66
- * that is the entire recharts <Tooltip>/<Legend> path.
67
- */
68
- const publicInstanceFor = (instance: HostInstance): ProxyNode | HostInstance =>
69
- docForInstance(instance.instance).adopt(instance);
70
-
71
- /**
72
- * Portal `containerInfo` is the public instance (a ProxyElement) the caller
73
- * passed to createPortal; the ROOT_CONTAINER sentinel and raw instances keep
74
- * their own `id`. Anything unrecognizable falls back to the island root.
75
- */
76
- const containerParentId = (container: unknown): number => {
77
- const inst = (container as { instance?: { id?: unknown } } | null)?.instance;
78
- if (inst && typeof inst.id === 'number') return inst.id;
79
- const id = (container as { id?: unknown } | null)?.id;
80
- return typeof id === 'number' ? id : 0;
81
- };
82
-
83
- /**
84
- * The instance a create should bind to — read off the root container for
85
- * ordinary renders, off the portal container's wrapped instance for portal
86
- * subtrees (a ProxyElement, whose `.instance` field doesn't exist but whose
87
- * `.instance.instance` does), finally falling back to ambient instance state.
88
- */
89
- const containerInstance = (container: unknown): string => {
90
- // `.instance` is the instance KEY on a RootContainer/raw host record and
91
- // the host RECORD on a ProxyElement (portal containers) — disambiguate
92
- // by shape: a string IS the key; a record holds `.instance.instance`.
93
- const inner = (container as { instance?: unknown } | null)?.instance;
94
- if (typeof inner === 'string') return inner;
95
- return (inner as { instance?: string } | undefined)?.instance ??
96
- (getActiveInstance() !== '' ? getActiveInstance() : getLastActiveInstance());
97
- };
98
-
99
- /* Host context = DOM namespace tracking, same role it plays in React DOM:
100
- * `getChildHostContext` flips the namespace when the element type requires
101
- * it, and `createInstance` stamps the result on the instance so the `create`
102
- * op can tell the driver to createElementNS. Recharts-style SVG trees need
103
- * this — without it every <svg>/<path> would arrive as an HTMLUnknownElement. */
104
- interface HostContext {
105
- ns?: string;
106
- }
107
- const SVG_NS = 'http://www.w3.org/2000/svg';
108
- const MATH_NS = 'http://www.w3.org/1998/Math/MathML';
109
- const HTML_CTX: HostContext = Object.freeze({});
110
- const SVG_CTX: HostContext = Object.freeze({ ns: SVG_NS });
111
- const MATH_CTX: HostContext = Object.freeze({ ns: MATH_NS });
112
-
113
- /** The namespace context a host element gives its children — and the
114
- * namespace the element itself belongs to (getChildNamespace semantics:
115
- * <svg> in HTML is SVG, foreignObject/desc/title flip back to HTML). */
116
- const childHostContextFor = (parent: HostContext, type: string): HostContext => {
117
- if (type === 'svg') return SVG_CTX;
118
- if (type === 'math') return MATH_CTX;
119
- if (type === 'foreignObject' || type === 'desc' || type === 'title') return HTML_CTX;
120
- return parent;
121
- };
122
-
123
- /* ── The host config ────────────────────────────────────────────────────── */
124
-
125
- export const hostConfig = {
126
- // Identity / capabilities
127
- rendererVersion: '0.34.0',
128
- rendererPackageName: '@atolljs/islands',
129
- extraDevToolsConfig: null,
130
- isPrimaryRenderer: true,
131
- warnsIfNotActing: false,
132
- supportsMutation: true,
133
- supportsPersistence: false,
134
- supportsHydration: false,
135
- supportsResources: false,
136
- supportsSingletons: false,
137
- supportsMicrotasks: false,
138
- supportsTestSelectors: false,
139
-
140
- getPublicInstance: (instance: HostInstance): unknown => publicInstanceFor(instance),
141
- // Host contexts must be non-null objects (the reconciler pushes them on a
142
- // context stack and warns "Expected host context to exist" on null). This
143
- // renderer carries no per-subtree context — echo a single frozen sentinel.
144
- // Host contexts must be non-null objects (the reconciler warns on null).
145
- // Ours carries only the element namespace: <svg>/<math> enter their
146
- // namespaces; foreignObject/desc/title are HTML-integration points that
147
- // flip children back to HTML; everything else inherits the parent's.
148
- // For portals, this becomes the context the portal subtree renders under —
149
- // derived from the container's own namespace so children of an SVG portal
150
- // target (recharts zIndex <g> layers) stay in the SVG namespace.
151
- getRootHostContext: (container: unknown): HostContext => {
152
- const ns = (container as { instance?: { ns?: string } } | null)?.instance?.ns;
153
- if (ns === SVG_NS) return SVG_CTX;
154
- if (ns === MATH_NS) return MATH_CTX;
155
- return HTML_CTX;
156
- },
157
- getChildHostContext: (parent: HostContext, type: string): HostContext =>
158
- childHostContextFor(parent, type),
159
- prepareForCommit: NULL,
160
- // The doorbell: every commit bumps opsVersion once — the main thread's
161
- // observe() wakes (Atomics.waitAsync) and flushes the op queue, so commits
162
- // made outside task calls (effects, timers, async setState) arrive as a
163
- // push instead of waiting on a poll. Task-returned ops bump too; the
164
- // follow-up flush just finds an empty queue.
165
- resetAfterCommit: bumpOpsVersion,
166
-
167
- // Creation — emit ops
168
- createInstance: (
169
- type: string,
170
- props: Record<string, unknown>,
171
- rootContainer: unknown,
172
- hostContext: unknown,
173
- _internalHandle: unknown,
174
- ): ElementInstance => {
175
- // The element's own namespace = the child context its TYPE produces
176
- // under the parent's context — the same computation React DOM runs
177
- // (getChildNamespace): <svg> in HTML is itself SVG, <foreignObject> in
178
- // SVG is itself HTML. Instance comes from the container (see RootContainer).
179
- const ctx = childHostContextFor(hostContext as HostContext, type);
180
- const instance = newElement(type, props, ctx.ns, containerInstance(rootContainer));
181
- pushOp(instance.instance, {
182
- t: 'create',
183
- id: instance.id,
184
- type,
185
- props: instance.props,
186
- ns: instance.ns,
187
- });
188
- return instance;
189
- },
190
- createTextInstance: (
191
- text: string,
192
- rootContainer: unknown,
193
- _hostContext: unknown,
194
- _internalHandle: unknown,
195
- ): TextInstance => {
196
- const instance = newText(text, containerInstance(rootContainer));
197
- pushOp(instance.instance, { t: 'text', id: instance.id, text });
198
- return instance;
199
- },
200
- finalizeInitialChildren: FALSE,
201
- // Always false: string/number children become real text instances, so the
202
- // tree arrives as linear ops and `utext` handles every text update. (The
203
- // `true` path would expect commitUpdate to set textContent from
204
- // props.children — which we deliberately never serialize.)
205
- shouldSetTextContent: FALSE,
206
-
207
- // Tree wiring — emit ops
208
- appendInitialChild: (parent: HostInstance, child: HostInstance): void => {
209
- pushOp(parent.instance, { t: 'append', parent: parent.id, child: child.id });
210
- },
211
- appendChild: (parent: HostInstance, child: HostInstance): void => {
212
- pushOp(parent.instance, { t: 'append', parent: parent.id, child: child.id });
213
- },
214
- appendChildToContainer: (container: unknown, child: HostInstance): void => {
215
- pushOp(child.instance, { t: 'append', parent: containerParentId(container), child: child.id });
216
- },
217
- insertBefore: (parent: HostInstance, child: HostInstance, before: HostInstance): void => {
218
- pushOp(parent.instance, { t: 'append', parent: parent.id, child: child.id, before: before.id });
219
- },
220
- insertInContainerBefore: (container: unknown, child: HostInstance, before: HostInstance): void => {
221
- pushOp(child.instance, {
222
- t: 'append',
223
- parent: containerParentId(container),
224
- child: child.id,
225
- before: before.id,
226
- });
227
- },
228
- removeChild: (_parent: HostInstance, child: HostInstance): void => {
229
- pushOp(child.instance, { t: 'remove', child: child.id });
230
- },
231
- removeChildFromContainer: (_container: unknown, child: HostInstance): void => {
232
- pushOp(child.instance, { t: 'remove', child: child.id });
233
- },
234
- // No instance argument — routed by the task's active instance (only reached
235
- // inside mount/remount's syncCommit).
236
- clearContainer: (): void => {
237
- pushOp(getActiveInstance(), { t: 'clear' });
238
- },
239
-
240
- // Updates — emit ops
241
- commitUpdate: (
242
- instance: ElementInstance,
243
- _type: string,
244
- _oldProps: Record<string, unknown>,
245
- newProps: Record<string, unknown>,
246
- _internalHandle: unknown,
247
- ): void => {
248
- // Serialize first so the op carries the latest props; re-serialization
249
- // also re-registers event handlers against the current closure.
250
- instance.props = serializeProps(instance, newProps);
251
- pushOp(instance.instance, { t: 'update', id: instance.id, props: instance.props });
252
- },
253
- commitTextUpdate: (instance: TextInstance, _oldText: string, newText: string): void => {
254
- instance.text = newText;
255
- pushOp(instance.instance, { t: 'utext', id: instance.id, text: newText });
256
- },
257
- commitMount: noop,
258
- // Unreachable while shouldSetTextContent is always false; still emit the
259
- // spec'd op for completeness.
260
- resetTextContent: (instance: ElementInstance): void => {
261
- instance.props = {};
262
- pushOp(instance.instance, { t: 'update', id: instance.id, props: {} });
263
- },
264
-
265
- // Suspense visibility — `hidden` is a global HTML attribute the main
266
- // thread applies like any other prop.
267
- hideInstance: (instance: ElementInstance): void => {
268
- pushOp(instance.instance, { t: 'update', id: instance.id, props: { ...instance.props, hidden: true } });
269
- },
270
- unhideInstance: (instance: ElementInstance): void => {
271
- pushOp(instance.instance, { t: 'update', id: instance.id, props: { ...instance.props } });
272
- },
273
- hideTextInstance: (instance: TextInstance): void => {
274
- pushOp(instance.instance, { t: 'utext', id: instance.id, text: '' });
275
- },
276
- unhideTextInstance: (instance: TextInstance, text: string): void => {
277
- pushOp(instance.instance, { t: 'utext', id: instance.id, text });
278
- },
279
-
280
- // GC hook — drop the record and its handler slots.
281
- detachDeletedInstance: (instance: HostInstance): void => {
282
- instances.delete(instance.id);
283
- if (instance.kind === 'element') {
284
- for (const hid of Object.values(instance.listenerSlots)) unregisterHandler(hid);
285
- }
286
- },
287
-
288
- // Scheduler
289
- scheduleTimeout: setTimeout,
290
- cancelTimeout: clearTimeout,
291
- noTimeout: -1,
292
- // supportsMicrotasks is false, so the reconciler schedules root work via
293
- // the `scheduler` package (MessageChannel in a worker). This export is
294
- // still destructured — provide a real one for completeness.
295
- scheduleMicrotask: (fn: () => void): void => queueMicrotask(fn),
296
-
297
- // Event priorities (see note above — values are lane numbers).
298
- setCurrentUpdatePriority: (p: number): void => {
299
- currentUpdatePriority = p;
300
- },
301
- getCurrentUpdatePriority: (): number => currentUpdatePriority,
302
- resolveUpdatePriority: (): number =>
303
- currentUpdatePriority !== 0 ? currentUpdatePriority : DEFAULT_EVENT_PRIORITY,
304
- resolveEventType: NULL,
305
- resolveEventTimeStamp: (): number => -1.1,
306
- trackSchedulerEvent: noop,
307
- shouldAttemptEagerTransition: FALSE,
308
-
309
- // Events/portals/scopes — no DOM event system exists worker-side.
310
- getInstanceFromNode: NULL,
311
- beforeActiveInstanceBlur: noop,
312
- afterActiveInstanceBlur: noop,
313
- preparePortalMount: noop,
314
- prepareScopeUpdate: noop,
315
- getInstanceFromScope: NULL,
316
-
317
- // React 19 "suspend on commit" — never suspend.
318
- requestPostPaintCallback: noop,
319
- maySuspendCommit: FALSE,
320
- maySuspendCommitOnUpdate: FALSE,
321
- maySuspendCommitInSyncRender: FALSE,
322
- preloadInstance: TRUE,
323
- startSuspendingCommit: noop,
324
- suspendInstance: noop,
325
- suspendOnActiveViewTransition: noop,
326
- waitForCommitToBeReady: NULL,
327
- getSuspendedCommitReason: () => 0,
328
-
329
- // Transitions / forms / console
330
- NotPendingTransition,
331
- HostTransitionContext,
332
- resetFormInstance: noop,
333
- bindToConsole: (methodName: string, args: unknown[]) =>
334
- (console as unknown as Record<string, (...a: unknown[]) => void>)[methodName]?.bind(console, ...args) ??
335
- noop,
336
-
337
- // Persistence (supportsPersistence: false — destructured, never called)
338
- createContainerChildSet: () => [],
339
- appendChildToContainerChildSet: noop,
340
- finalizeContainerChildren: noop,
341
- replaceContainerChildren: noop,
342
- cloneInstance: NULL,
343
- cloneMutableInstance: (instance: HostInstance) => instance,
344
- cloneMutableTextInstance: (instance: HostInstance) => instance,
345
- cloneHiddenInstance: (instance: HostInstance) => instance,
346
- cloneHiddenTextInstance: (instance: HostInstance) => instance,
347
-
348
- // Fragment instances (only used for `<Fragment ref={…}>` — unimplemented)
349
- createFragmentInstance: () => ({}),
350
- updateFragmentInstanceFiber: noop,
351
- commitNewChildToFragmentInstance: noop,
352
- deleteChildFromFragmentInstance: noop,
353
-
354
- // View transitions (React 19) — all stubs
355
- createViewTransitionInstance: (name: string) => ({ name, autoName: null }),
356
- applyViewTransitionName: noop,
357
- restoreViewTransitionName: noop,
358
- cancelViewTransitionName: noop,
359
- cancelRootViewTransitionName: noop,
360
- restoreRootViewTransitionName: noop,
361
- cloneRootViewTransitionContainer: noop,
362
- removeRootViewTransitionClone: noop,
363
- measureInstance: NULL,
364
- measureClonedInstance: NULL,
365
- wasInstanceInViewport: FALSE,
366
- hasInstanceChanged: FALSE,
367
- hasInstanceAffectedParent: FALSE,
368
- startViewTransition: NULL,
369
- startGestureTransition: NULL,
370
- stopViewTransition: noop,
371
- addViewTransitionFinishedListener: noop,
372
- getCurrentGestureOffset: () => 0,
373
-
374
- // Hydration (supportsHydration: false — destructured, never called)
375
- isSuspenseInstancePending: FALSE,
376
- isSuspenseInstanceFallback: FALSE,
377
- getSuspenseInstanceFallbackErrorDetails: () => ({}),
378
- registerSuspenseInstanceRetry: noop,
379
- getNextHydratableSibling: NULL,
380
- getNextHydratableSiblingAfterSingleton: NULL,
381
- getFirstHydratableChild: NULL,
382
- getFirstHydratableChildWithinContainer: NULL,
383
- getFirstHydratableChildWithinActivityInstance: NULL,
384
- getFirstHydratableChildWithinSingleton: NULL,
385
- getFirstHydratableChildWithinSuspenseInstance: NULL,
386
- canHydrateInstance: NULL,
387
- canHydrateTextInstance: NULL,
388
- canHydrateActivityInstance: NULL,
389
- canHydrateSuspenseInstance: NULL,
390
- canHydrateFormStateMarker: NULL,
391
- isFormStateMarkerMatching: FALSE,
392
- hydrateInstance: noop,
393
- hydrateTextInstance: noop,
394
- hydrateActivityInstance: noop,
395
- hydrateSuspenseInstance: noop,
396
- getNextHydratableInstanceAfterActivityInstance: NULL,
397
- getNextHydratableInstanceAfterSuspenseInstance: NULL,
398
- commitHydratedInstance: noop,
399
- commitHydratedContainer: noop,
400
- commitHydratedSuspenseInstance: noop,
401
- finalizeHydratedChildren: FALSE,
402
- flushHydrationEvents: noop,
403
- clearActivityBoundary: FALSE,
404
- clearSuspenseBoundary: FALSE,
405
- clearActivityBoundaryFromContainer: FALSE,
406
- clearSuspenseBoundaryFromContainer: FALSE,
407
- hideDehydratedBoundary: noop,
408
- unhideDehydratedBoundary: noop,
409
- shouldDeleteUnhydratedTailInstances: FALSE,
410
- diffHydratedPropsForDevWarnings: NULL,
411
- diffHydratedTextForDevWarnings: NULL,
412
- describeHydratableInstanceForDevWarnings: NULL,
413
- validateHydratableInstance: TRUE,
414
- validateHydratableTextInstance: TRUE,
415
-
416
- // Host resources (stylesheets/scripts — supportsResources: false)
417
- isHostHoistableType: FALSE,
418
- getHoistableRoot: NULL,
419
- getResource: NULL,
420
- acquireResource: noop,
421
- releaseResource: noop,
422
- hydrateHoistable: noop,
423
- mountHoistable: noop,
424
- unmountHoistable: noop,
425
- createHoistableInstance: NULL,
426
- prepareToCommitHoistables: noop,
427
- mayResourceSuspendCommit: FALSE,
428
- preloadResource: TRUE,
429
- suspendResource: noop,
430
-
431
- // Singletons (supportsSingletons: false)
432
- resolveSingletonInstance: NULL,
433
- acquireSingletonInstance: noop,
434
- releaseSingletonInstance: noop,
435
- isHostSingletonType: FALSE,
436
- isSingletonScope: FALSE,
437
-
438
- // Test selectors (supportsTestSelectors: false)
439
- findFiberRoot: NULL,
440
- getBoundingRect: NULL,
441
- getTextContent: NULL,
442
- isHiddenSubtree: FALSE,
443
- matchAccessibilityRole: FALSE,
444
- setFocusIfFocusable: FALSE,
445
- setupIntersectionObserver: noop,
446
- };
@@ -1,74 +0,0 @@
1
- /**
2
- * The React-runtime half of the island worker — `react`, `react-reconciler`
3
- * and the host config live HERE so a worker bundle whose registry holds only
4
- * non-React apps (Vue/Svelte/Solid/Angular/imperative) never pulls the
5
- * reconciler in. `definePolyWorker` reaches this module only through
6
- * `await import('./reactInstance')` when a mounted app resolves to a component
7
- * function — first mount of a React instance pays one microtask's worth of
8
- * module init, everything after is the same sync path.
9
- */
10
- import { createElement, type ReactElement } from 'react';
11
- import Reconciler from 'react-reconciler';
12
- import { hostConfig } from './hostConfig';
13
- import { ROOT_CONTAINER } from './instance';
14
-
15
- /**
16
- * A mounted React instance — carries its own reconciler + container and the
17
- * `render`/`unmountTree` verbs, so the runtime module never needs the
18
- * `react` import itself (element creation is the only place it was used).
19
- */
20
- export interface ReactInstance {
21
- /** The wire key — 'app' or 'app@instance'; op queues route by this. */
22
- key: string;
23
- /** The registry name — which component this instance renders. */
24
- app: string;
25
- /** Per-instance random id — the island's "worker pid" badge. */
26
- pid: string;
27
- imperative?: undefined;
28
- rendered?: undefined;
29
- reconciler: ReturnType<typeof Reconciler>;
30
- container: unknown;
31
- /** Mount/re-render the component into this instance's container. */
32
- render(App: (props: Record<string, unknown>) => ReactElement, props: Record<string, unknown>): void;
33
- /** Unmount the tree — emits the detach ops the driver replays. */
34
- unmountTree(): void;
35
- /** Passive effects + pending sync work — the `flush` task's React branch. */
36
- flush(): void;
37
- }
38
-
39
- /**
40
- * Legacy root (tag 0) — no concurrent features. Container creation is
41
- * per-instance, not module-level, so a second mount() can't collide with the
42
- * first instance's tree.
43
- */
44
- export function createReactInstance(key: string, app: string, pid: string): ReactInstance {
45
- const reconciler = Reconciler(hostConfig);
46
- const container = reconciler.createContainer(
47
- // Instance-stamped root container — createInstance reads `.instance` off it,
48
- // binding every element to this instance even in out-of-task commits.
49
- { id: 0, instance: key } as typeof ROOT_CONTAINER,
50
- 0,
51
- null,
52
- false,
53
- null,
54
- '',
55
- console.error, // onUncaughtError
56
- console.error, // onCaughtError
57
- console.error, // onRecoverableError
58
- null, // onDefaultTransitionIndicator
59
- );
60
- return {
61
- key,
62
- app,
63
- pid,
64
- reconciler,
65
- container,
66
- render: (App, props) =>
67
- reconciler.updateContainer(createElement(App, props), container, null, null),
68
- unmountTree: () => reconciler.updateContainer(null, container, null, null),
69
- flush: () => {
70
- reconciler.flushPassiveEffects();
71
- reconciler.flushSyncWork();
72
- },
73
- };
74
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * A transclusion slot — renders a leaf element marked `data-atoll-slot`.
3
- * The element's BOX belongs to this worker tree (layout/style ops apply as
4
- * usual); its CONTENTS belong to the shell: when the create op lands on the
5
- * main thread, the island's `slots[name]` callback gets the real element
6
- * and can mount anything inside — a canvas, Monaco, a main-thread React
7
- * root — all real DOM with real events and zero wire traffic.
8
- *
9
- * Must stay a LEAF in this tree: if worker-side children were appended here
10
- * their ops would interleave with shell-owned content. There is also no
11
- * measurement channel — the island knows the box it laid out, not the
12
- * pixel size the shell put inside it.
13
- */
14
- import type { ReactElement } from 'react';
15
-
16
- export function Slot({ name, style }: { name: string; style?: Record<string, unknown> }): ReactElement {
17
- return <div data-atoll-slot={name} style={style} />;
18
- }