@use-voltra/core 2.0.0-rc.4 → 2.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.
@@ -0,0 +1,379 @@
1
+ /* eslint-disable */
2
+ // 🤖 AUTO-GENERATED from data/components.json
3
+ // DO NOT EDIT MANUALLY - Changes will be overwritten
4
+ // Schema version: 1.0.0
5
+
6
+ /**
7
+ * Unified mapping from full names to short names
8
+ * Used for props and style properties
9
+ */
10
+ export const NAME_TO_SHORT: Record<string, string> = {
11
+ absolutePosition: 'ap',
12
+ accentedRenderingMode: 'arm',
13
+ alignItems: 'ai',
14
+ alignment: 'al',
15
+ alignSelf: 'as',
16
+ alpha: 'a',
17
+ aspectRatio: 'ar',
18
+ assetName: 'an',
19
+ background: 'bkg',
20
+ backgroundColor: 'bg',
21
+ backgroundImage: 'bgi',
22
+ backgroundStyle: 'bgs',
23
+ base64: 'b64',
24
+ border: 'bd',
25
+ borderColor: 'bc',
26
+ borderRadius: 'br',
27
+ borderWidth: 'bw',
28
+ bottom: 'b',
29
+ buttonStyle: 'bs',
30
+ chartScrollableAxes: 'csa',
31
+ checked: 'chk',
32
+ checkedColor: 'chc',
33
+ clipped: 'clip',
34
+ color: 'c',
35
+ colorFilter: 'cf',
36
+ colors: 'cls',
37
+ columns: 'cols',
38
+ contentAlignment: 'ca',
39
+ contentColor: 'cc',
40
+ contentDescription: 'cdesc',
41
+ contentScale: 'csc',
42
+ cornerRadius: 'cr',
43
+ countDown: 'cd',
44
+ currentValueLabel: 'cvl',
45
+ deepLinkUrl: 'dlu',
46
+ defaultValue: 'dv',
47
+ destination: 'dest',
48
+ direction: 'dir',
49
+ dither: 'dth',
50
+ durationMs: 'dur',
51
+ enabled: 'en',
52
+ endAtMs: 'end',
53
+ endPoint: 'ep',
54
+ fallback: 'flb',
55
+ fillMaxHeight: 'fmh',
56
+ fillMaxWidth: 'fmw',
57
+ fixedSizeHorizontal: 'fsh',
58
+ fixedSizeVertical: 'fsv',
59
+ flex: 'fl',
60
+ flexBasis: 'fb',
61
+ flexDirection: 'fdir',
62
+ flexGrow: 'fg',
63
+ flexGrowWidth: 'fgw',
64
+ flexShrink: 'fk',
65
+ font: 'fnt',
66
+ fontFamily: 'ff',
67
+ fontSize: 'fs',
68
+ fontVariant: 'fvar',
69
+ fontWeight: 'fw',
70
+ foregroundStyle: 'fgs',
71
+ foregroundStyleScale: 'fss',
72
+ frame: 'f',
73
+ gap: 'g',
74
+ gaugeStyle: 'gs',
75
+ glassEffect: 'ge',
76
+ height: 'h',
77
+ horizontalAlignment: 'halig',
78
+ horizontalPadding: 'hp',
79
+ icon: 'ic',
80
+ iconColor: 'icc',
81
+ id: 'id',
82
+ italic: 'it',
83
+ justifyContent: 'jc',
84
+ kerning: 'kern',
85
+ label: 'lbl',
86
+ layout: 'ly',
87
+ layoutPriority: 'lp',
88
+ left: 'l',
89
+ legendVisibility: 'lgv',
90
+ letterSpacing: 'ls',
91
+ lineHeight: 'lh',
92
+ lineLimit: 'll',
93
+ lineSpacing: 'lsp',
94
+ lineWidth: 'lw',
95
+ margin: 'm',
96
+ marginBottom: 'mb',
97
+ marginHorizontal: 'mh',
98
+ marginLeft: 'ml',
99
+ marginRight: 'mr',
100
+ marginTop: 'mt',
101
+ marginVertical: 'mv',
102
+ marks: 'mrk',
103
+ maskElement: 'me',
104
+ maxHeight: 'maxh',
105
+ maximumValue: 'max',
106
+ maximumValueLabel: 'maxl',
107
+ maxLines: 'mxl',
108
+ maxWidth: 'maxw',
109
+ minHeight: 'minh',
110
+ minimumValue: 'min',
111
+ minimumValueLabel: 'minvl',
112
+ minLength: 'minl',
113
+ minWidth: 'minw',
114
+ monospacedDigit: 'md',
115
+ multilineTextAlignment: 'mta',
116
+ name: 'n',
117
+ numberOfLines: 'nol',
118
+ offset: 'off',
119
+ offsetX: 'ox',
120
+ offsetY: 'oy',
121
+ opacity: 'op',
122
+ overflow: 'ov',
123
+ padding: 'pad',
124
+ paddingBottom: 'pb',
125
+ paddingHorizontal: 'ph',
126
+ paddingLeft: 'pl',
127
+ paddingRight: 'pr',
128
+ paddingTop: 'pt',
129
+ paddingVertical: 'pv',
130
+ position: 'pos',
131
+ progress: 'prg',
132
+ progressColor: 'pc',
133
+ resizeMode: 'rm',
134
+ right: 'r',
135
+ rotationEffect: 're',
136
+ scale: 'sc',
137
+ scaleEffect: 'sce',
138
+ shadow: 'sh',
139
+ shadowColor: 'shc',
140
+ shadowOffset: 'sho',
141
+ shadowOpacity: 'shop',
142
+ shadowRadius: 'shr',
143
+ showHours: 'shrs',
144
+ size: 'sz',
145
+ smallCaps: 'smc',
146
+ source: 'src',
147
+ spacing: 'sp',
148
+ startAtMs: 'start',
149
+ startIcon: 'sic',
150
+ startPoint: 'stp',
151
+ stops: 'sts',
152
+ strikethrough: 'st',
153
+ style: 's',
154
+ systemImage: 'si',
155
+ text: 'txt',
156
+ textAlign: 'ta',
157
+ textColor: 'txc',
158
+ textDecorationLine: 'tdl',
159
+ textStyle: 'ts',
160
+ textTemplates: 'tt',
161
+ thumb: 'th',
162
+ thumbCheckedColor: 'thc',
163
+ thumbUncheckedColor: 'thu',
164
+ tint: 'tnt',
165
+ tintColor: 'tc',
166
+ title: 'ttl',
167
+ top: 't',
168
+ trackCheckedColor: 'trkc',
169
+ trackColor: 'trc',
170
+ trackUncheckedColor: 'trku',
171
+ transform: 'tf',
172
+ type: 'typ',
173
+ uncheckedColor: 'uchc',
174
+ underline: 'ul',
175
+ value: 'v',
176
+ verticalAlignment: 'valig',
177
+ weight: 'wt',
178
+ width: 'w',
179
+ xAxisGridStyle: 'xgs',
180
+ xAxisVisibility: 'xav',
181
+ yAxisGridStyle: 'ygs',
182
+ yAxisVisibility: 'yav',
183
+ zIndex: 'zi',
184
+ }
185
+
186
+ /**
187
+ * Reverse mapping from short names to full names
188
+ */
189
+ export const SHORT_TO_NAME: Record<string, string> = {
190
+ ap: 'absolutePosition',
191
+ arm: 'accentedRenderingMode',
192
+ ai: 'alignItems',
193
+ al: 'alignment',
194
+ as: 'alignSelf',
195
+ a: 'alpha',
196
+ ar: 'aspectRatio',
197
+ an: 'assetName',
198
+ bkg: 'background',
199
+ bg: 'backgroundColor',
200
+ bgi: 'backgroundImage',
201
+ bgs: 'backgroundStyle',
202
+ b64: 'base64',
203
+ bd: 'border',
204
+ bc: 'borderColor',
205
+ br: 'borderRadius',
206
+ bw: 'borderWidth',
207
+ b: 'bottom',
208
+ bs: 'buttonStyle',
209
+ csa: 'chartScrollableAxes',
210
+ chk: 'checked',
211
+ chc: 'checkedColor',
212
+ clip: 'clipped',
213
+ c: 'color',
214
+ cf: 'colorFilter',
215
+ cls: 'colors',
216
+ cols: 'columns',
217
+ ca: 'contentAlignment',
218
+ cc: 'contentColor',
219
+ cdesc: 'contentDescription',
220
+ csc: 'contentScale',
221
+ cr: 'cornerRadius',
222
+ cd: 'countDown',
223
+ cvl: 'currentValueLabel',
224
+ dlu: 'deepLinkUrl',
225
+ dv: 'defaultValue',
226
+ dest: 'destination',
227
+ dir: 'direction',
228
+ dth: 'dither',
229
+ dur: 'durationMs',
230
+ en: 'enabled',
231
+ end: 'endAtMs',
232
+ ep: 'endPoint',
233
+ flb: 'fallback',
234
+ fmh: 'fillMaxHeight',
235
+ fmw: 'fillMaxWidth',
236
+ fsh: 'fixedSizeHorizontal',
237
+ fsv: 'fixedSizeVertical',
238
+ fl: 'flex',
239
+ fb: 'flexBasis',
240
+ fdir: 'flexDirection',
241
+ fg: 'flexGrow',
242
+ fgw: 'flexGrowWidth',
243
+ fk: 'flexShrink',
244
+ fnt: 'font',
245
+ ff: 'fontFamily',
246
+ fs: 'fontSize',
247
+ fvar: 'fontVariant',
248
+ fw: 'fontWeight',
249
+ fgs: 'foregroundStyle',
250
+ fss: 'foregroundStyleScale',
251
+ f: 'frame',
252
+ g: 'gap',
253
+ gs: 'gaugeStyle',
254
+ ge: 'glassEffect',
255
+ h: 'height',
256
+ halig: 'horizontalAlignment',
257
+ hp: 'horizontalPadding',
258
+ ic: 'icon',
259
+ icc: 'iconColor',
260
+ id: 'id',
261
+ it: 'italic',
262
+ jc: 'justifyContent',
263
+ kern: 'kerning',
264
+ lbl: 'label',
265
+ ly: 'layout',
266
+ lp: 'layoutPriority',
267
+ l: 'left',
268
+ lgv: 'legendVisibility',
269
+ ls: 'letterSpacing',
270
+ lh: 'lineHeight',
271
+ ll: 'lineLimit',
272
+ lsp: 'lineSpacing',
273
+ lw: 'lineWidth',
274
+ m: 'margin',
275
+ mb: 'marginBottom',
276
+ mh: 'marginHorizontal',
277
+ ml: 'marginLeft',
278
+ mr: 'marginRight',
279
+ mt: 'marginTop',
280
+ mv: 'marginVertical',
281
+ mrk: 'marks',
282
+ me: 'maskElement',
283
+ maxh: 'maxHeight',
284
+ max: 'maximumValue',
285
+ maxl: 'maximumValueLabel',
286
+ mxl: 'maxLines',
287
+ maxw: 'maxWidth',
288
+ minh: 'minHeight',
289
+ min: 'minimumValue',
290
+ minvl: 'minimumValueLabel',
291
+ minl: 'minLength',
292
+ minw: 'minWidth',
293
+ md: 'monospacedDigit',
294
+ mta: 'multilineTextAlignment',
295
+ n: 'name',
296
+ nol: 'numberOfLines',
297
+ off: 'offset',
298
+ ox: 'offsetX',
299
+ oy: 'offsetY',
300
+ op: 'opacity',
301
+ ov: 'overflow',
302
+ pad: 'padding',
303
+ pb: 'paddingBottom',
304
+ ph: 'paddingHorizontal',
305
+ pl: 'paddingLeft',
306
+ pr: 'paddingRight',
307
+ pt: 'paddingTop',
308
+ pv: 'paddingVertical',
309
+ pos: 'position',
310
+ prg: 'progress',
311
+ pc: 'progressColor',
312
+ rm: 'resizeMode',
313
+ r: 'right',
314
+ re: 'rotationEffect',
315
+ sc: 'scale',
316
+ sce: 'scaleEffect',
317
+ sh: 'shadow',
318
+ shc: 'shadowColor',
319
+ sho: 'shadowOffset',
320
+ shop: 'shadowOpacity',
321
+ shr: 'shadowRadius',
322
+ shrs: 'showHours',
323
+ sz: 'size',
324
+ smc: 'smallCaps',
325
+ src: 'source',
326
+ sp: 'spacing',
327
+ start: 'startAtMs',
328
+ sic: 'startIcon',
329
+ stp: 'startPoint',
330
+ sts: 'stops',
331
+ st: 'strikethrough',
332
+ s: 'style',
333
+ si: 'systemImage',
334
+ txt: 'text',
335
+ ta: 'textAlign',
336
+ txc: 'textColor',
337
+ tdl: 'textDecorationLine',
338
+ ts: 'textStyle',
339
+ tt: 'textTemplates',
340
+ th: 'thumb',
341
+ thc: 'thumbCheckedColor',
342
+ thu: 'thumbUncheckedColor',
343
+ tnt: 'tint',
344
+ tc: 'tintColor',
345
+ ttl: 'title',
346
+ t: 'top',
347
+ trkc: 'trackCheckedColor',
348
+ trc: 'trackColor',
349
+ trku: 'trackUncheckedColor',
350
+ tf: 'transform',
351
+ typ: 'type',
352
+ uchc: 'uncheckedColor',
353
+ ul: 'underline',
354
+ v: 'value',
355
+ valig: 'verticalAlignment',
356
+ wt: 'weight',
357
+ w: 'width',
358
+ xgs: 'xAxisGridStyle',
359
+ xav: 'xAxisVisibility',
360
+ ygs: 'yAxisGridStyle',
361
+ yav: 'yAxisVisibility',
362
+ zi: 'zIndex',
363
+ }
364
+
365
+ /**
366
+ * Shorten a name using the unified mapping
367
+ * Returns the original name if no short form exists
368
+ */
369
+ export function shorten(name: string): string {
370
+ return NAME_TO_SHORT[name] ?? name
371
+ }
372
+
373
+ /**
374
+ * Expand a short name back to the full name
375
+ * Returns the original value if no expansion exists
376
+ */
377
+ export function expand(short: string): string {
378
+ return SHORT_TO_NAME[short] ?? short
379
+ }
package/src/payload.ts ADDED
@@ -0,0 +1,23 @@
1
+ const MAX_ACTIVITYKIT_BYTES = 4096
2
+ const EFFECTIVE_JSON_BUDGET = 3345
3
+
4
+ const utf8ByteLength = (s: string): number => {
5
+ try {
6
+ if (typeof TextEncoder !== 'undefined') return new TextEncoder().encode(s).length
7
+ } catch {}
8
+ try {
9
+ return encodeURI(s).replace(/%[A-F\d]{2}/gi, 'X').length
10
+ } catch {
11
+ return s.length
12
+ }
13
+ }
14
+
15
+ export const ensurePayloadWithinBudget = (compressedPayload: string): void => {
16
+ const bytes = utf8ByteLength(compressedPayload)
17
+
18
+ if (bytes > EFFECTIVE_JSON_BUDGET) {
19
+ throw new Error(
20
+ `[Voltra] Compressed payload size ${bytes}B exceeds safe budget ${EFFECTIVE_JSON_BUDGET}B (ActivityKit hard cap ${MAX_ACTIVITYKIT_BYTES}B). You need to reduce the complexity of your UI.`
21
+ )
22
+ }
23
+ }
@@ -0,0 +1,40 @@
1
+ import { Context } from 'react'
2
+
3
+ declare module 'react' {
4
+ interface Context<T> {
5
+ _currentValue: T
6
+ }
7
+ }
8
+
9
+ export type ContextRegistry = {
10
+ pushProvider: <T>(context: Context<T>, value: T) => void
11
+ popProvider: <T>(context: Context<T>) => void
12
+ readContext: <T>(context: Context<T>) => T
13
+ }
14
+
15
+ export const getContextRegistry = (): ContextRegistry => {
16
+ const contextMap = new Map()
17
+
18
+ return {
19
+ pushProvider: <T>(context: Context<T>, value: T) => {
20
+ const stack = contextMap.get(context) || []
21
+ stack.push(value)
22
+ contextMap.set(context, stack)
23
+ },
24
+ popProvider: <T>(context: Context<T>) => {
25
+ const stack = contextMap.get(context)
26
+ if (stack) {
27
+ stack.pop()
28
+ }
29
+ },
30
+ readContext: <T>(context: Context<T>) => {
31
+ const stack = contextMap.get(context)
32
+
33
+ if (stack && stack.length > 0) {
34
+ return stack[stack.length - 1]
35
+ }
36
+
37
+ return context._currentValue
38
+ },
39
+ }
40
+ }
@@ -0,0 +1,105 @@
1
+ import React, { Context, ReactDispatcher, ReactHooksDispatcher } from 'react'
2
+
3
+ import { ContextRegistry } from './context-registry.js'
4
+
5
+ const REACT_CONTEXT_TYPE = Symbol.for('react.context')
6
+ const REACT_MEMO_CACHE_SENTINEL = Symbol.for('react.memo_cache_sentinel')
7
+
8
+ declare module 'react' {
9
+ type HookFn = (...args: any[]) => any
10
+
11
+ export type ReactHooksDispatcher = {
12
+ useState: HookFn
13
+ useReducer: HookFn
14
+ useEffect: HookFn
15
+ useLayoutEffect: HookFn
16
+ useInsertionEffect: HookFn
17
+ useCallback: HookFn
18
+ useMemo: HookFn
19
+ useRef: HookFn
20
+ useContext: HookFn
21
+ useId: HookFn
22
+ useImperativeHandle: HookFn
23
+ useDebugValue: HookFn
24
+ useDeferredValue: HookFn
25
+ useTransition: HookFn
26
+ useSyncExternalStore: HookFn
27
+ use: HookFn
28
+ useActionState: HookFn
29
+ useOptimistic: HookFn
30
+ useEffectEvent: HookFn
31
+ useMemoCache: HookFn
32
+ useCacheRefresh: HookFn
33
+ }
34
+
35
+ export type ReactDispatcher = {
36
+ H: ReactHooksDispatcher
37
+ }
38
+
39
+ let __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE: ReactDispatcher
40
+ }
41
+
42
+ export const getHooksDispatcher = (registry: ContextRegistry): ReactHooksDispatcher => ({
43
+ useContext: <T>(context: Context<T>) => registry.readContext(context),
44
+ use: <T>(usable: React.Usable<T>): T => {
45
+ if (
46
+ usable !== null &&
47
+ typeof usable === 'object' &&
48
+ (usable as { $$typeof?: symbol }).$$typeof === REACT_CONTEXT_TYPE
49
+ ) {
50
+ return registry.readContext(usable as unknown as Context<T>)
51
+ }
52
+
53
+ if (
54
+ usable !== null &&
55
+ (typeof usable === 'object' || typeof usable === 'function') &&
56
+ typeof (usable as { then?: unknown }).then === 'function'
57
+ ) {
58
+ throw new Error(
59
+ 'use() with promises is not supported in Voltra. Async data fetching is not available in this synchronous renderer.'
60
+ )
61
+ }
62
+
63
+ throw new Error(`An unsupported type was passed to use(): ${String(usable)}`)
64
+ },
65
+ useState: <S>(initial?: S | (() => S)) => [
66
+ typeof initial === 'function' ? (initial as () => S)() : initial,
67
+ () => {},
68
+ ],
69
+ useReducer: <S, I, A extends React.AnyActionArg>(
70
+ _: (prevState: S, ...args: A) => S,
71
+ initialArg: I,
72
+ init?: (i: I) => S
73
+ ): [S, React.ActionDispatch<A>] => {
74
+ const state = init ? init(initialArg) : initialArg
75
+ return [state as S, () => {}]
76
+ },
77
+ useMemo: (factory) => factory(),
78
+ useCallback: (cb) => cb,
79
+ useRef: (initial) => ({ current: initial }),
80
+ useEffect: () => {},
81
+ useLayoutEffect: () => {},
82
+ useInsertionEffect: () => {},
83
+ useId: () => Math.random().toString(36).substr(2, 9),
84
+ useDebugValue: () => {},
85
+ useImperativeHandle: () => {},
86
+ useDeferredValue: <T>(value: T) => value,
87
+ useTransition: () => [false, (func: () => void) => func()],
88
+ useSyncExternalStore: (_, getSnapshot) => getSnapshot(),
89
+ useActionState: <S>(_: unknown, initialState: S, _permalink?: string) =>
90
+ [initialState, () => {}, false] as [S, () => void, boolean],
91
+ useOptimistic: <T>(passthrough: T) => [passthrough, () => {}] as [T, (action: unknown) => void],
92
+ useEffectEvent: <T extends Function>(callback: T): T => callback,
93
+ useMemoCache: (size: number) => {
94
+ const data = new Array<unknown>(size)
95
+ for (let i = 0; i < size; i++) {
96
+ data[i] = REACT_MEMO_CACHE_SENTINEL
97
+ }
98
+ return data
99
+ },
100
+ useCacheRefresh: () => () => {},
101
+ })
102
+
103
+ export const getReactCurrentDispatcher = (): ReactDispatcher => {
104
+ return React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
105
+ }
@@ -0,0 +1,69 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ import { VoltraNodeJson } from '../types.js'
4
+
5
+ export type ElementRegistry = {
6
+ isRegistered: (element: ReactNode) => number | undefined
7
+ register: (element: ReactNode, rendered: VoltraNodeJson) => number
8
+ getElements: () => VoltraNodeJson[]
9
+ }
10
+
11
+ export const createElementRegistry = (): ElementRegistry => {
12
+ const elementToIndex = new Map<ReactNode, number>()
13
+ const elements: VoltraNodeJson[] = []
14
+
15
+ return {
16
+ isRegistered: (element: ReactNode): number | undefined => {
17
+ return elementToIndex.get(element)
18
+ },
19
+ register: (element: ReactNode, rendered: VoltraNodeJson): number => {
20
+ const index = elements.length
21
+ elementToIndex.set(element, index)
22
+ elements.push(rendered)
23
+ return index
24
+ },
25
+ getElements: (): VoltraNodeJson[] => elements,
26
+ }
27
+ }
28
+
29
+ export const preScanForDuplicates = (element: ReactNode): Set<ReactNode> => {
30
+ const seen = new Set<ReactNode>()
31
+ const duplicates = new Set<ReactNode>()
32
+
33
+ const scan = (node: ReactNode): void => {
34
+ if (node === null || node === undefined || typeof node !== 'object') {
35
+ return
36
+ }
37
+
38
+ if (Array.isArray(node)) {
39
+ for (const child of node) {
40
+ scan(child)
41
+ }
42
+ return
43
+ }
44
+
45
+ if (seen.has(node)) {
46
+ duplicates.add(node)
47
+ } else {
48
+ seen.add(node)
49
+ }
50
+
51
+ if ('props' in node) {
52
+ const props = (node as { props: Record<string, unknown> }).props
53
+ if (props) {
54
+ if (props.children !== undefined) {
55
+ scan(props.children as ReactNode)
56
+ }
57
+
58
+ for (const [key, value] of Object.entries(props)) {
59
+ if (key !== 'children' && value && typeof value === 'object' && 'type' in value && 'props' in value) {
60
+ scan(value as ReactNode)
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ scan(element)
68
+ return duplicates
69
+ }
@@ -0,0 +1,24 @@
1
+ type StyleProp<T> = T | T[] | null | undefined | false
2
+
3
+ type FlatStyleProp<T> = T extends (infer U)[] ? FlatStyleProp<U> : T
4
+
5
+ export const flattenStyle = <T extends StyleProp<unknown>>(style: T | null | undefined): FlatStyleProp<T> | null => {
6
+ if (!style) {
7
+ return null
8
+ }
9
+
10
+ if (!Array.isArray(style)) {
11
+ return style as FlatStyleProp<T>
12
+ }
13
+
14
+ const result: Record<string, unknown> = {}
15
+ for (let i = 0, styleLength = style.length; i < styleLength; ++i) {
16
+ const computedStyle = flattenStyle(style[i])
17
+ if (computedStyle) {
18
+ for (const key in computedStyle) {
19
+ result[key] = computedStyle[key]
20
+ }
21
+ }
22
+ }
23
+ return result as FlatStyleProp<T>
24
+ }
@@ -0,0 +1,14 @@
1
+ export { type ContextRegistry, getContextRegistry } from './context-registry.js'
2
+ export { getHooksDispatcher, getReactCurrentDispatcher } from './dispatcher.js'
3
+ export { createElementRegistry, type ElementRegistry, preScanForDuplicates } from './element-registry.js'
4
+ export { flattenStyle } from './flatten-styles.js'
5
+ export { getRenderCache, type RenderCache } from './render-cache.js'
6
+ export {
7
+ type ComponentRegistry,
8
+ createVoltraRenderer,
9
+ renderVariantToJson,
10
+ transformProps,
11
+ VOLTRA_PAYLOAD_VERSION,
12
+ } from './renderer.js'
13
+ export { createStylesheetRegistry, type StylesheetRegistry } from './stylesheet-registry.js'
14
+ export type { VoltraVariantRenderer } from './types.js'
@@ -0,0 +1,27 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ import { VoltraNodeJson } from '../types.js'
4
+ import { VoltraVariantRenderer } from './types.js'
5
+
6
+ export type RenderCache = {
7
+ getOrRender: VoltraVariantRenderer
8
+ }
9
+
10
+ export const getRenderCache = (renderer: VoltraVariantRenderer): RenderCache => {
11
+ const renderCache = new Map<ReactNode, VoltraNodeJson>()
12
+
13
+ return {
14
+ getOrRender: (node: ReactNode): VoltraNodeJson => {
15
+ const cached = renderCache.get(node)
16
+
17
+ if (cached) {
18
+ return cached
19
+ }
20
+
21
+ const result = renderer(node)
22
+ renderCache.set(node, result)
23
+
24
+ return result
25
+ },
26
+ }
27
+ }