@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
package/src/index.ts ADDED
@@ -0,0 +1,278 @@
1
+ // @system @ref LLP 0007 — Renderer-facing behavior primitives
2
+ /**
3
+ * Exact Renderer
4
+ *
5
+ * Main entry point for the Exact React renderer.
6
+ *
7
+ * This module provides:
8
+ * - render/unmount functions for mounting React trees
9
+ * - Primitive components (web-style and RN-style)
10
+ * - Type definitions for props and styles
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import React from 'react';
15
+ * import { render, View, Text, Pressable } from '@exact/runtime';
16
+ *
17
+ * function App() {
18
+ * const [count, setCount] = React.useState(0);
19
+ *
20
+ * return (
21
+ * <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
22
+ * <Text style={{ fontSize: 24 }}>Count: {count}</Text>
23
+ * <Pressable
24
+ * style={{ padding: 16, backgroundColor: '#007AFF', borderRadius: 8 }}
25
+ * onPress={() => setCount(c => c + 1)}
26
+ * >
27
+ * <Text style={{ color: '#fff' }}>Increment</Text>
28
+ * </Pressable>
29
+ * </View>
30
+ * );
31
+ * }
32
+ *
33
+ * render(<App />);
34
+ * ```
35
+ */
36
+
37
+ import './jsx-augmentations.js';
38
+
39
+ // =============================================================================
40
+ // Render Functions
41
+ // =============================================================================
42
+
43
+ export {
44
+ render,
45
+ unmount,
46
+ forceUpdate,
47
+ syncWindowState,
48
+ reset,
49
+ createExactRoot,
50
+ prepareForBundleReplacement,
51
+ } from './reconciler.js';
52
+ export type { ExactRoot } from './reconciler.js';
53
+ // Keep the root barrel native-safe. Optional DOM/framework adapters belong on
54
+ // explicit subpaths so native startup does not eagerly import them.
55
+ export type { ExactDOMHandle } from './dom-shim.js';
56
+
57
+ // =============================================================================
58
+ // Web-Style Components
59
+ // =============================================================================
60
+
61
+ export {
62
+ div,
63
+ span,
64
+ text,
65
+ img,
66
+ input,
67
+ button,
68
+ toggle,
69
+ } from './components.js';
70
+
71
+ // =============================================================================
72
+ // React Native-Style Components
73
+ // =============================================================================
74
+
75
+ export {
76
+ View,
77
+ Text,
78
+ Image,
79
+ Svg,
80
+ SelectionGroup,
81
+ List,
82
+ ScrollView,
83
+ TextInput,
84
+ Pressable,
85
+ Toggle,
86
+ Switch,
87
+ } from './components.js';
88
+ export {
89
+ createNativeViewComponent,
90
+ } from './native-view.js';
91
+
92
+ // =============================================================================
93
+ // Style Types
94
+ // =============================================================================
95
+
96
+ export type {
97
+ // Style types
98
+ ViewStyle,
99
+ FullStyle,
100
+ FlexStyle,
101
+ LayoutStyle,
102
+ SpacingStyle,
103
+ PositionStyle,
104
+ AppearanceStyle,
105
+ TransformStyle,
106
+ TransitionStyle,
107
+ TextStyle,
108
+ TransitionPropertyName,
109
+ TransitionPhase,
110
+ TransitionEasingName,
111
+ CubicBezierEasing,
112
+ TransitionEasing,
113
+ TimingTransition,
114
+ SpringTransition,
115
+ NoneTransition,
116
+ TransitionConfig,
117
+ TransitionMap,
118
+ StyleTransition,
119
+
120
+ // Dimension types
121
+ DimensionValue,
122
+ DimensionInput,
123
+
124
+ // Flexbox types
125
+ FlexDirection,
126
+ FlexWrap,
127
+ JustifyContent,
128
+ AlignItems,
129
+ AlignSelf,
130
+ AlignContent,
131
+
132
+ // Other types
133
+ PositionType,
134
+ Overflow,
135
+ Display,
136
+ SafeAreaEdge,
137
+ SafeAreaEdgeMode,
138
+ SafeAreaInsetProp,
139
+ SafeAreaProp,
140
+ SafeAreaRegionSet,
141
+ FontWeight,
142
+ FontStyle,
143
+ TextAlign,
144
+ EllipsizeMode,
145
+ SelectableMode,
146
+ NativeViewSelectionTier,
147
+ NativeViewSelectionGesturePolicy,
148
+ ResizeMode,
149
+ ImageColorScheme,
150
+ ImageCandidate,
151
+ ImageThemeTreatment,
152
+ ColorSchemeImageSource,
153
+ ImagePlaceholder,
154
+ ImageLoading,
155
+ ImageObjectFit,
156
+ ImageSource,
157
+ SvgSource,
158
+ } from './types.js';
159
+
160
+ // =============================================================================
161
+ // Component Prop Types
162
+ // =============================================================================
163
+
164
+ export type {
165
+ // Base props
166
+ BaseProps,
167
+ ContainerProps,
168
+ TextElementProps,
169
+ ImageElementProps,
170
+ RNImageProps,
171
+ SvgProps,
172
+ PressableElementProps,
173
+ ScrollContainerProps,
174
+ TextInputProps,
175
+ ToggleProps,
176
+
177
+ // Event types
178
+ PressEvent,
179
+ ScrollEvent,
180
+ ChangeEvent,
181
+ ToggleChangeEvent,
182
+ TransitionEndEvent,
183
+ } from './types.js';
184
+
185
+ // =============================================================================
186
+ // Internal/Advanced Exports
187
+ // =============================================================================
188
+ //
189
+ // These exports are intended for advanced usage, testing, or building custom
190
+ // renderers. They are not part of the stable public API and may change.
191
+ //
192
+ // =============================================================================
193
+
194
+ /**
195
+ * Internal types for custom renderer implementations.
196
+ * @internal
197
+ */
198
+ export type {
199
+ RGBAColor,
200
+ CanonicalStyle,
201
+ CanonicalProps,
202
+ CanonicalTagType,
203
+ WebTagType,
204
+ RNTagType,
205
+ TagType,
206
+ } from './types.js';
207
+ export type {
208
+ NativeViewComponentProps,
209
+ NativeViewRuntimeDef,
210
+ NativeViewSelectionDef,
211
+ } from './native-view.js';
212
+
213
+ /**
214
+ * Style utilities for parsing and normalizing styles.
215
+ * @internal
216
+ */
217
+ export { parseColor, isValidColor, colorToHex, colorToRgba } from './style/index.js';
218
+ export {
219
+ normalizeStyle,
220
+ normalizeTransition,
221
+ stylesEqual,
222
+ transitionsEqual,
223
+ parseDimension,
224
+ parseFontWeight,
225
+ } from './style/index.js';
226
+
227
+ /**
228
+ * Props utilities for normalizing component props.
229
+ * @internal
230
+ */
231
+ export { normalizeProps, propsEqual, extractTextContent } from './props/index.js';
232
+
233
+ export {
234
+ CSSVariableProvider,
235
+ useCSSVariables,
236
+ extractCSSVariables,
237
+ } from './css-variable-context.js';
238
+ export type { CSSVariableMap } from './css-variable-context.js';
239
+
240
+ export {
241
+ GroupStateProvider,
242
+ useGroupStates,
243
+ extractGroupName,
244
+ } from './group-context.js';
245
+
246
+ export {
247
+ createPeerStore,
248
+ usePeerStore,
249
+ usePeerState,
250
+ extractPeerName,
251
+ } from './peer-context.js';
252
+ export type { PeerStore } from './peer-context.js';
253
+ export type {
254
+ ExactAdapterHostOps,
255
+ ExactAdapterOps,
256
+ ExactAdapterRoot,
257
+ } from './adapter-contract.js';
258
+ export type { ExactSvelteRoot } from './svelte.js';
259
+
260
+ /**
261
+ * Tag configuration utilities.
262
+ * @internal
263
+ */
264
+ export { getTagConfig, isValidTag, isWebTag, isRNTag } from './tags/index.js';
265
+
266
+ /**
267
+ * Internal debugging utilities.
268
+ * These are prefixed with underscore and should not be used in production code.
269
+ * @internal
270
+ */
271
+ export {
272
+ _getRootContainer,
273
+ _isRendering,
274
+ _getEventQueueLength,
275
+ } from './reconciler.js';
276
+
277
+ export { _getEncoder, _clearHandlers, getHandler } from './host-config.js';
278
+ export { NodeKind, _resetNodeIdCounter } from './nodes/index.js';
@@ -0,0 +1,244 @@
1
+ import type {
2
+ AgentEvent,
3
+ ElementRef,
4
+ ElementRefInfo,
5
+ Frame,
6
+ Point,
7
+ RootInfo,
8
+ ViewNode,
9
+ } from '@exact/core/agent/types';
10
+ import {
11
+ recordCommitTimestamp,
12
+ resetAgentInteractionStateTracking,
13
+ } from '@exact/core/agent/interaction-state';
14
+
15
+ import type { ElementNode, HostNode, RootNode } from './nodes/node.js';
16
+ import { NodeKind } from './nodes/node.js';
17
+ import { getScreenDimensions } from './protocol/index.js';
18
+
19
+ type InspectorListener = (event: AgentEvent) => void;
20
+ type CommitAnalyzer = (root: RootNode) => void;
21
+ type ResetHook = () => void;
22
+
23
+ interface SnapshotRecord {
24
+ snapshotId: string;
25
+ rootId: number;
26
+ stateVersion: number;
27
+ timestamp: number;
28
+ frameCount: number;
29
+ refs: Record<ElementRef, ElementRefInfo>;
30
+ tree?: ViewNode;
31
+ }
32
+
33
+ interface SharedInspectorState {
34
+ roots: Map<number, RootNode>;
35
+ listeners: Set<InspectorListener>;
36
+ snapshots: Map<string, SnapshotRecord>;
37
+ snapshotOrder: string[];
38
+ textOverrides: Map<number, string>;
39
+ toggleOverrides: Map<number, boolean>;
40
+ valueOverrides: Map<number, unknown>;
41
+ scrollOffsets: Map<number, Point>;
42
+ snapshotCounter: number;
43
+ stateVersion: number;
44
+ frameCount: number;
45
+ lastCommitAt: number;
46
+ }
47
+
48
+ type InspectorGlobal = typeof globalThis & {
49
+ __exactRendererInspectorState?: SharedInspectorState;
50
+ __exactRendererCommitAnalyzers?: Set<CommitAnalyzer>;
51
+ __exactRendererInspectorResetHooks?: Set<ResetHook>;
52
+ };
53
+
54
+ function inspectorGlobal(): InspectorGlobal {
55
+ return globalThis as InspectorGlobal;
56
+ }
57
+
58
+ function getSharedInspectorState(): SharedInspectorState {
59
+ const scope = inspectorGlobal();
60
+ if (scope.__exactRendererInspectorState) {
61
+ return scope.__exactRendererInspectorState;
62
+ }
63
+
64
+ const state: SharedInspectorState = {
65
+ roots: new Map<number, RootNode>(),
66
+ listeners: new Set<InspectorListener>(),
67
+ snapshots: new Map<string, SnapshotRecord>(),
68
+ snapshotOrder: [],
69
+ textOverrides: new Map<number, string>(),
70
+ toggleOverrides: new Map<number, boolean>(),
71
+ valueOverrides: new Map<number, unknown>(),
72
+ scrollOffsets: new Map<number, Point>(),
73
+ snapshotCounter: 0,
74
+ stateVersion: 0,
75
+ frameCount: 0,
76
+ lastCommitAt: 0,
77
+ };
78
+ scope.__exactRendererInspectorState = state;
79
+ return state;
80
+ }
81
+
82
+ const sharedState = getSharedInspectorState();
83
+ const roots = sharedState.roots;
84
+ const listeners = sharedState.listeners;
85
+ const snapshots = sharedState.snapshots;
86
+ const snapshotOrder = sharedState.snapshotOrder;
87
+ const textOverrides = sharedState.textOverrides;
88
+ const toggleOverrides = sharedState.toggleOverrides;
89
+ const valueOverrides = sharedState.valueOverrides;
90
+ const scrollOffsets = sharedState.scrollOffsets;
91
+
92
+ function currentTimestamp(): number {
93
+ return Date.now();
94
+ }
95
+
96
+ function makeRootInfo(root: RootNode): RootInfo {
97
+ const { width, height } = getScreenDimensions(root.rootId);
98
+ return {
99
+ rootId: root.rootId,
100
+ label: root.rootId === 0 ? 'Main' : `Root ${root.rootId}`,
101
+ size: {
102
+ width,
103
+ height,
104
+ },
105
+ };
106
+ }
107
+
108
+ function emit(event: AgentEvent): void {
109
+ for (const listener of listeners) {
110
+ try {
111
+ listener(event);
112
+ } catch (error) {
113
+ console.error('[RendererInspector] listener failed:', error);
114
+ }
115
+ }
116
+ }
117
+
118
+ function collectChangedViews(root: RootNode): number[] {
119
+ const collected: number[] = [];
120
+ const collect = (node: HostNode): void => {
121
+ collected.push(node.id);
122
+ if (node.kind === NodeKind.Element || node.kind === NodeKind.Root) {
123
+ for (const child of node.children) {
124
+ collect(child);
125
+ }
126
+ }
127
+ };
128
+
129
+ collect(root);
130
+ return collected;
131
+ }
132
+
133
+ function commitAnalyzers(): Set<CommitAnalyzer> {
134
+ const scope = inspectorGlobal();
135
+ if (!scope.__exactRendererCommitAnalyzers) {
136
+ scope.__exactRendererCommitAnalyzers = new Set<CommitAnalyzer>();
137
+ }
138
+ return scope.__exactRendererCommitAnalyzers;
139
+ }
140
+
141
+ function resetHooks(): Set<ResetHook> {
142
+ const scope = inspectorGlobal();
143
+ if (!scope.__exactRendererInspectorResetHooks) {
144
+ scope.__exactRendererInspectorResetHooks = new Set<ResetHook>();
145
+ }
146
+ return scope.__exactRendererInspectorResetHooks;
147
+ }
148
+
149
+ export function registerInspectorCommitAnalyzer(analyzer: CommitAnalyzer): () => void {
150
+ const analyzers = commitAnalyzers();
151
+ analyzers.add(analyzer);
152
+ return () => {
153
+ analyzers.delete(analyzer);
154
+ };
155
+ }
156
+
157
+ export function registerInspectorResetHook(hook: ResetHook): () => void {
158
+ const hooks = resetHooks();
159
+ hooks.add(hook);
160
+ return () => {
161
+ hooks.delete(hook);
162
+ };
163
+ }
164
+
165
+ export function registerRoot(root: RootNode): void {
166
+ roots.set(root.rootId, root);
167
+ sharedState.stateVersion += 1;
168
+ const rootInfo = makeRootInfo(root);
169
+ emit({
170
+ type: 'connect',
171
+ rootId: root.rootId,
172
+ label: rootInfo.label,
173
+ size: rootInfo.size,
174
+ });
175
+ }
176
+
177
+ export function unregisterRoot(rootId: number): void {
178
+ if (!roots.delete(rootId)) {
179
+ return;
180
+ }
181
+ sharedState.stateVersion += 1;
182
+ emit({
183
+ type: 'disconnect',
184
+ reason: `root:${rootId}:unregistered`,
185
+ exitCode: null,
186
+ signal: null,
187
+ });
188
+ }
189
+
190
+ export function notifyCommit(
191
+ root: RootNode,
192
+ options: {
193
+ changedViews?: Iterable<number>;
194
+ analyzeWarnings?: boolean;
195
+ } = {},
196
+ ): void {
197
+ roots.set(root.rootId, root);
198
+ sharedState.frameCount += 1;
199
+ sharedState.stateVersion += 1;
200
+ sharedState.lastCommitAt = currentTimestamp();
201
+ recordCommitTimestamp(sharedState.lastCommitAt);
202
+
203
+ if (options.analyzeWarnings !== false) {
204
+ for (const analyzer of commitAnalyzers()) {
205
+ analyzer(root);
206
+ }
207
+ }
208
+
209
+ if (listeners.size === 0) {
210
+ return;
211
+ }
212
+
213
+ emit({
214
+ type: 'render',
215
+ frameCount: sharedState.frameCount,
216
+ changedViews:
217
+ options.changedViews !== undefined
218
+ ? Array.from(options.changedViews)
219
+ : collectChangedViews(root),
220
+ });
221
+ }
222
+
223
+ export function _resetInspectorState(): void {
224
+ roots.clear();
225
+ listeners.clear();
226
+ snapshots.clear();
227
+ snapshotOrder.length = 0;
228
+ resetAgentInteractionStateTracking();
229
+ for (const hook of resetHooks()) {
230
+ try {
231
+ hook();
232
+ } catch (error) {
233
+ console.error('[RendererInspector] reset hook failed:', error);
234
+ }
235
+ }
236
+ textOverrides.clear();
237
+ toggleOverrides.clear();
238
+ valueOverrides.clear();
239
+ scrollOffsets.clear();
240
+ sharedState.snapshotCounter = 0;
241
+ sharedState.stateVersion = 0;
242
+ sharedState.frameCount = 0;
243
+ sharedState.lastCommitAt = 0;
244
+ }