@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,69 @@
1
+ /**
2
+ * Group state context for Tailwind group-* modifiers (RFC 0024 Phase 4).
3
+ *
4
+ * A view with `className="group"` provides its interaction state via context.
5
+ * Descendant views with `group-hover:*` classes consume this context to
6
+ * conditionally apply styles.
7
+ *
8
+ * Named groups (`group/sidebar`) get separate context entries so multiple
9
+ * nested groups don't conflict.
10
+ */
11
+ import { createContext, createElement, useContext, useMemo, type ReactNode } from 'react';
12
+ import type { InteractionState } from './classname-contract.js';
13
+
14
+ /**
15
+ * Map from group name (null = unnamed default group) to its interaction state.
16
+ */
17
+ type GroupStateMap = ReadonlyMap<string | null, InteractionState>;
18
+
19
+ const EMPTY_MAP: GroupStateMap = new Map();
20
+
21
+ const GroupStateContext = createContext<GroupStateMap>(EMPTY_MAP);
22
+
23
+ /**
24
+ * Read the current group states from context. Used by the className resolver
25
+ * to evaluate `group-hover:*`, `group-focus:*`, `group-active:*` conditions.
26
+ */
27
+ export function useGroupStates(): GroupStateMap {
28
+ return useContext(GroupStateContext);
29
+ }
30
+
31
+ /**
32
+ * Provide a group's interaction state to descendants.
33
+ *
34
+ * @param name - The group name (null for unnamed `group`, string for `group/name`)
35
+ * @param state - The group view's current interaction state
36
+ */
37
+ export function GroupStateProvider({
38
+ name,
39
+ state,
40
+ children,
41
+ }: {
42
+ name: string | null;
43
+ state: InteractionState;
44
+ children: ReactNode;
45
+ }): ReactNode {
46
+ const inherited = useContext(GroupStateContext);
47
+ const merged = useMemo(() => {
48
+ const next = new Map(inherited);
49
+ next.set(name, state);
50
+ return next;
51
+ }, [inherited, name, state]);
52
+
53
+ return createElement(GroupStateContext.Provider, { value: merged }, children);
54
+ }
55
+
56
+ /**
57
+ * Check if a className string includes "group" or "group/{name}".
58
+ * Returns the group name (null for unnamed) or undefined if not a group.
59
+ */
60
+ const WS_RE = /\s+/;
61
+
62
+ export function extractGroupName(className: string): string | null | undefined {
63
+ const tokens = className.split(WS_RE);
64
+ for (const token of tokens) {
65
+ if (token === 'group') return null;
66
+ if (token.startsWith('group/')) return token.slice(6);
67
+ }
68
+ return undefined;
69
+ }
@@ -0,0 +1,431 @@
1
+ // @system @ref LLP 0007 — React reconciler host integration
2
+ /**
3
+ * React Reconciler Host Config
4
+ *
5
+ * This module implements the HostConfig interface required by react-reconciler.
6
+ * It bridges React's component tree to our host node model and protocol encoder.
7
+ *
8
+ * Design Principles:
9
+ * - Mutation-based rendering (supportsMutation = true)
10
+ * - No persistence or hydration
11
+ * - Clear separation between tree building and commit phases
12
+ * - Batched mutations for efficient updates
13
+ *
14
+ * Key Concepts:
15
+ * - Instance: Our ElementNode representing a host element
16
+ * - TextInstance: Our TextNode representing raw text
17
+ * - Container: RootNode (the root container with ID 0)
18
+ * - HostContext: Tag defaults for child elements
19
+ */
20
+
21
+ // Note: We use 'any' for the HostConfig type because the exact type signature
22
+ // varies between react-reconciler versions and is complex to maintain.
23
+ // The implementation is correct; this is just for type checking convenience.
24
+
25
+ import type { FullStyle } from './types.js';
26
+
27
+ // Import shared infrastructure from host-ops
28
+ import {
29
+ // Types
30
+ type ElementNode,
31
+ type TextNode,
32
+ type RootNode,
33
+ type TagConfig,
34
+
35
+ // Node operations
36
+ NodeKind,
37
+ nodeAppendChild,
38
+ nodeInsertBefore,
39
+ nodeRemoveChild,
40
+ nodeClearChildren,
41
+
42
+ // Instance creation
43
+ createInstance,
44
+ createTextInstance,
45
+
46
+ // Tree operations
47
+ appendChild as hostAppendChild,
48
+ insertBefore as hostInsertBefore,
49
+ removeChild as hostRemoveChild,
50
+ clearContainer as hostClearContainer,
51
+ commitBatch,
52
+
53
+ // Updates
54
+ updateInstanceProps,
55
+ updateTextContent,
56
+ finalizeInstance,
57
+
58
+ // Tag config
59
+ getTagConfig,
60
+ defaultTagConfig,
61
+
62
+ // Event handling
63
+ processEventProps,
64
+ cleanupNodeHandlers,
65
+
66
+ // Encoder access
67
+ getEncoder,
68
+ _getEncoder,
69
+
70
+ // Handler registry (re-export for reconciler.ts)
71
+ getHandler,
72
+ _clearHandlers,
73
+
74
+ // Protocol encoding
75
+ encodeChildren,
76
+ encodeEvents,
77
+ encodeDestroy,
78
+ } from './host-ops.js';
79
+
80
+ // Re-export for reconciler.ts
81
+ export { getHandler, _getEncoder, _clearHandlers };
82
+
83
+ // =============================================================================
84
+ // Development Mode Flag
85
+ // =============================================================================
86
+
87
+ /**
88
+ * Development mode flag - gate verbose logging.
89
+ * In production builds, bundlers should replace this with false.
90
+ */
91
+ const __DEV__ = process.env.NODE_ENV !== 'production';
92
+
93
+ function hasOnlyTextChildren(children: unknown): boolean {
94
+ if (children === null || children === undefined) {
95
+ return true;
96
+ }
97
+
98
+ switch (typeof children) {
99
+ case 'string':
100
+ case 'number':
101
+ return true;
102
+ case 'boolean':
103
+ return true;
104
+ default:
105
+ break;
106
+ }
107
+
108
+ if (Array.isArray(children)) {
109
+ return children.every(hasOnlyTextChildren);
110
+ }
111
+
112
+ return false;
113
+ }
114
+
115
+
116
+ // =============================================================================
117
+ // Type Aliases for React Reconciler
118
+ // =============================================================================
119
+
120
+ type Type = string;
121
+ type Props = Record<string, unknown>;
122
+ type Instance = ElementNode;
123
+ type TextInstance = TextNode;
124
+ type Container = RootNode;
125
+ type ChildSet = never; // Not used in mutation mode
126
+ type HostContext = TagConfig;
127
+ type SuspenseInstance = never;
128
+ type HydratableInstance = never;
129
+ type PublicInstance = ElementNode | TextNode;
130
+ type TimeoutHandle = ReturnType<typeof setTimeout>;
131
+ type NoTimeout = -1;
132
+
133
+ // Get the shared encoder
134
+ const encoder = getEncoder();
135
+
136
+ // =============================================================================
137
+ // Event Priority Constants (React 19)
138
+ // =============================================================================
139
+
140
+ const DiscreteEventPriority = 2;
141
+ const ContinuousEventPriority = 8;
142
+ const DefaultEventPriority = 32;
143
+ const IdleEventPriority = 268435456;
144
+
145
+ let currentUpdatePriority = DefaultEventPriority;
146
+
147
+ // =============================================================================
148
+ // Host Config Implementation
149
+ // =============================================================================
150
+
151
+ /**
152
+ * The host config for the Exact renderer.
153
+ * We use 'any' here because HostConfig generic signature varies between
154
+ * react-reconciler versions. The implementation is type-safe internally.
155
+ */
156
+ export const hostConfig: any = {
157
+ // ===========================================================================
158
+ // Feature Flags
159
+ // ===========================================================================
160
+
161
+ supportsMutation: true,
162
+ supportsPersistence: false,
163
+ supportsHydration: false,
164
+ isPrimaryRenderer: true,
165
+ noTimeout: -1 as const,
166
+ warnsIfNotActing: false,
167
+
168
+ // ===========================================================================
169
+ // Renderer Info
170
+ // ===========================================================================
171
+
172
+ rendererVersion: '19.0.0',
173
+ rendererPackageName: 'exact-renderer',
174
+ extraDevToolsConfig: null,
175
+
176
+ // ===========================================================================
177
+ // Priority Management (React 19)
178
+ // ===========================================================================
179
+
180
+ setCurrentUpdatePriority(priority: number): void {
181
+ currentUpdatePriority = priority;
182
+ },
183
+
184
+ getCurrentUpdatePriority(): number {
185
+ return currentUpdatePriority;
186
+ },
187
+
188
+ resolveUpdatePriority(): number {
189
+ return currentUpdatePriority || DefaultEventPriority;
190
+ },
191
+
192
+ resolveEventTimeStamp(): number {
193
+ return performance.now();
194
+ },
195
+
196
+ resolveEventType(): null {
197
+ return null;
198
+ },
199
+
200
+ trackSchedulerEvent(): void {},
201
+
202
+ // ===========================================================================
203
+ // Suspense Stubs (React 19)
204
+ // ===========================================================================
205
+
206
+ maySuspendCommit(): boolean {
207
+ return false;
208
+ },
209
+
210
+ maySuspendCommitOnUpdate(): boolean {
211
+ return false;
212
+ },
213
+
214
+ maySuspendCommitInSyncRender(): boolean {
215
+ return false;
216
+ },
217
+
218
+ preloadInstance(): boolean {
219
+ return true;
220
+ },
221
+
222
+ startSuspendingCommit(): void {},
223
+
224
+ suspendInstance(): void {},
225
+
226
+ waitForCommitToBeReady(): null {
227
+ return null;
228
+ },
229
+
230
+ shouldAttemptEagerTransition(): boolean {
231
+ return false;
232
+ },
233
+
234
+ // ===========================================================================
235
+ // Context
236
+ // ===========================================================================
237
+
238
+ getRootHostContext(_rootContainer: Container): HostContext {
239
+ // Root context uses web defaults
240
+ return defaultTagConfig;
241
+ },
242
+
243
+ getChildHostContext(parentContext: HostContext, type: Type, _rootContainer: Container): HostContext {
244
+ // Get the config for this child type
245
+ const config = getTagConfig(type);
246
+ if (config) {
247
+ return config;
248
+ }
249
+
250
+ // Unknown type - warn in dev and use default
251
+ if (process.env.NODE_ENV !== 'production') {
252
+ console.warn(`[Exact] Unknown element type: ${type}. Treating as view.`);
253
+ }
254
+ return defaultTagConfig;
255
+ },
256
+
257
+ // ===========================================================================
258
+ // Instance Creation
259
+ // ===========================================================================
260
+
261
+ createInstance(
262
+ type: Type,
263
+ props: Props,
264
+ _rootContainer: Container,
265
+ hostContext: HostContext,
266
+ _internalHandle: unknown
267
+ ): Instance {
268
+ // Use shared createInstance from host-ops
269
+ return createInstance(type, props);
270
+ },
271
+
272
+ createTextInstance(
273
+ text: string,
274
+ _rootContainer: Container,
275
+ _hostContext: HostContext,
276
+ _internalHandle: unknown
277
+ ): TextInstance {
278
+ // Use shared createTextInstance from host-ops
279
+ return createTextInstance(text);
280
+ },
281
+
282
+ // ===========================================================================
283
+ // Initial Children (during createInstance tree building)
284
+ // ===========================================================================
285
+
286
+ appendInitialChild(parent: Instance, child: Instance | TextInstance): void {
287
+ // During initial mounting, just build the tree structure (no encoding yet)
288
+ nodeAppendChild(parent, child);
289
+ },
290
+
291
+ finalizeInitialChildren(
292
+ instance: Instance,
293
+ _type: Type,
294
+ _props: Props,
295
+ _rootContainer: Container,
296
+ _hostContext: HostContext
297
+ ): boolean {
298
+ // Use shared finalizeInstance from host-ops
299
+ finalizeInstance(instance);
300
+ // Return false - no commit-time work needed
301
+ return false;
302
+ },
303
+
304
+ // ===========================================================================
305
+ // Mutation Methods
306
+ // ===========================================================================
307
+
308
+ appendChild(parent: Instance, child: Instance | TextInstance): void {
309
+ hostAppendChild(parent, child);
310
+ },
311
+
312
+ appendChildToContainer(container: Container, child: Instance | TextInstance): void {
313
+ hostAppendChild(container, child);
314
+ },
315
+
316
+ insertBefore(
317
+ parent: Instance,
318
+ child: Instance | TextInstance,
319
+ beforeChild: Instance | TextInstance
320
+ ): void {
321
+ hostInsertBefore(parent, child, beforeChild);
322
+ },
323
+
324
+ insertInContainerBefore(
325
+ container: Container,
326
+ child: Instance | TextInstance,
327
+ beforeChild: Instance | TextInstance
328
+ ): void {
329
+ hostInsertBefore(container, child, beforeChild);
330
+ },
331
+
332
+ removeChild(parent: Instance, child: Instance | TextInstance): void {
333
+ hostRemoveChild(parent, child);
334
+ },
335
+
336
+ removeChildFromContainer(container: Container, child: Instance | TextInstance): void {
337
+ hostRemoveChild(container, child);
338
+ },
339
+
340
+ clearContainer(container: Container): void {
341
+ hostClearContainer(container);
342
+ },
343
+
344
+ // ===========================================================================
345
+ // Updates
346
+ // ===========================================================================
347
+
348
+ // react-reconciler 0.33 removed prepareUpdate; commitUpdate is called as
349
+ // commitUpdate(instance, type, oldProps, newProps, fiber).
350
+ commitUpdate(
351
+ instance: Instance,
352
+ _type: Type,
353
+ oldProps: Props,
354
+ newProps: Props,
355
+ _internalHandle: unknown
356
+ ): void {
357
+ // Use shared updateInstanceProps from host-ops
358
+ updateInstanceProps(instance, oldProps, newProps);
359
+ },
360
+
361
+ commitTextUpdate(
362
+ instance: TextInstance,
363
+ oldText: string,
364
+ newText: string
365
+ ): void {
366
+ // Use shared updateTextContent from host-ops
367
+ updateTextContent(instance, newText);
368
+ },
369
+
370
+ // ===========================================================================
371
+ // Text Content
372
+ // ===========================================================================
373
+
374
+ shouldSetTextContent(type: Type, props: Props): boolean {
375
+ const config = getTagConfig(type) ?? defaultTagConfig;
376
+ return config.isTextContainer && hasOnlyTextChildren(props.children);
377
+ },
378
+
379
+ // ===========================================================================
380
+ // Commit Phase
381
+ // ===========================================================================
382
+
383
+ prepareForCommit(_containerInfo: Container): Record<string, unknown> | null {
384
+ return null;
385
+ },
386
+
387
+ resetAfterCommit(container: Container): void {
388
+ commitBatch(container);
389
+ },
390
+
391
+ // ===========================================================================
392
+ // Public Instance
393
+ // ===========================================================================
394
+
395
+ getPublicInstance(instance: Instance | TextInstance): PublicInstance {
396
+ return instance;
397
+ },
398
+
399
+ // ===========================================================================
400
+ // Scheduling
401
+ // ===========================================================================
402
+
403
+ scheduleTimeout: setTimeout,
404
+ cancelTimeout: clearTimeout,
405
+
406
+ getCurrentEventPriority(): number {
407
+ return DefaultEventPriority;
408
+ },
409
+
410
+ // ===========================================================================
411
+ // Misc Stubs
412
+ // ===========================================================================
413
+
414
+ getInstanceFromNode(): Instance | null {
415
+ return null;
416
+ },
417
+
418
+ beforeActiveInstanceBlur(): void {},
419
+
420
+ afterActiveInstanceBlur(): void {},
421
+
422
+ prepareScopeUpdate(): void {},
423
+
424
+ getInstanceFromScope(): Instance | null {
425
+ return null;
426
+ },
427
+
428
+ detachDeletedInstance(_instance: Instance): void {},
429
+
430
+ preparePortalMount(): void {},
431
+ };