@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,553 @@
1
+ import type { CanonicalStyle } from './types.js';
2
+ import type {
3
+ ClassNameResolutionContext,
4
+ InteractionState,
5
+ MediaCondition,
6
+ MediaState,
7
+ SerializedCompiledFragment,
8
+ SerializedExactStyleSheet,
9
+ SerializedFragmentCondition,
10
+ SiblingPosition,
11
+ SupportedPseudo,
12
+ } from './classname-contract.js';
13
+
14
+ const __DEV__ = process.env.NODE_ENV !== 'production';
15
+
16
+ /** Layer 1 cache limit from RFC 0024. */
17
+ const CLASS_CACHE_LIMIT = 1024;
18
+
19
+ interface CompiledFragment {
20
+ readonly order: number;
21
+ readonly condition: SerializedFragmentCondition | null;
22
+ readonly mediaGate: readonly MediaCondition[] | null;
23
+ readonly style: CanonicalStyle;
24
+ }
25
+
26
+ interface RegisteredExactStyleSheet {
27
+ readonly sourceId: string;
28
+ readonly sheetOrder: number;
29
+ readonly classes: Map<string, CompiledFragment[]>;
30
+ readonly warnings: string[];
31
+ }
32
+
33
+ interface CachedClassResolution {
34
+ readonly base: CanonicalStyle;
35
+ readonly conditionals: CompiledFragment[];
36
+ readonly hasConditionals: boolean;
37
+ }
38
+
39
+ const __initW =
40
+ typeof globalThis.innerWidth === 'number' ? globalThis.innerWidth : 390;
41
+ const __initH =
42
+ typeof globalThis.innerHeight === 'number' ? globalThis.innerHeight : 844;
43
+
44
+ let currentMediaState: MediaState = {
45
+ width: __initW,
46
+ height: __initH,
47
+ orientation: __initH > __initW ? 'portrait' : 'landscape',
48
+ colorScheme: 'light',
49
+ reducedMotion: false,
50
+ };
51
+
52
+ const mediaStateListeners = new Set<() => void>();
53
+ const stylesheetRegistry = new Map<string, RegisteredExactStyleSheet>();
54
+ const emittedCompilerWarnings = new Set<string>();
55
+ const emittedUnknownClassWarnings = new Set<string>();
56
+ const classResolutionCache = new Map<string, CachedClassResolution>();
57
+ let nextSheetOrder = 0;
58
+
59
+ type RuntimeClassNameResolver = (
60
+ className: unknown,
61
+ context?: ClassNameResolutionContext,
62
+ ) => CanonicalStyle;
63
+
64
+ declare global {
65
+ var __exactClassNameResolver: RuntimeClassNameResolver | undefined;
66
+ var __exactHasExactStylesheets: boolean | undefined;
67
+ }
68
+
69
+ export type {
70
+ ClassNameResolutionContext,
71
+ InteractionState,
72
+ MediaState,
73
+ MediaCondition,
74
+ SiblingPosition,
75
+ } from './classname-contract.js';
76
+
77
+ export function getMediaState(): MediaState {
78
+ return currentMediaState;
79
+ }
80
+
81
+ export function setMediaState(state: Partial<MediaState>): void {
82
+ const next: MediaState = { ...currentMediaState, ...state };
83
+ if (
84
+ next.width === currentMediaState.width &&
85
+ next.height === currentMediaState.height &&
86
+ next.orientation === currentMediaState.orientation &&
87
+ next.colorScheme === currentMediaState.colorScheme &&
88
+ next.reducedMotion === currentMediaState.reducedMotion
89
+ ) {
90
+ return;
91
+ }
92
+
93
+ currentMediaState = next;
94
+ classResolutionCache.clear();
95
+
96
+ for (const listener of mediaStateListeners) {
97
+ try {
98
+ listener();
99
+ } catch (error) {
100
+ if (__DEV__) {
101
+ console.warn('Media state listener error:', error);
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ export function subscribeMediaState(listener: () => void): () => void {
108
+ mediaStateListeners.add(listener);
109
+ return () => {
110
+ mediaStateListeners.delete(listener);
111
+ };
112
+ }
113
+
114
+ if (typeof globalThis.addEventListener === 'function') {
115
+ globalThis.addEventListener('resize', () => {
116
+ const width = globalThis.innerWidth ?? currentMediaState.width;
117
+ const height = globalThis.innerHeight ?? currentMediaState.height;
118
+ setMediaState({
119
+ width,
120
+ height,
121
+ orientation: height > width ? 'portrait' : 'landscape',
122
+ });
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Register a precompiled stylesheet artifact.
128
+ *
129
+ * The runtime intentionally accepts only serialized data here. Parsing raw CSS
130
+ * text would pull the compiler back into the client bundle and reintroduce the
131
+ * exact environment leak this RFC removes.
132
+ */
133
+ export function __registerExactStylesheet(
134
+ sourceId: string,
135
+ artifact: SerializedExactStyleSheet,
136
+ ): void {
137
+ const previous = stylesheetRegistry.get(sourceId);
138
+ const compiled = deserializeExactStylesheet(
139
+ sourceId,
140
+ artifact,
141
+ previous?.sheetOrder,
142
+ );
143
+
144
+ stylesheetRegistry.set(sourceId, compiled);
145
+ classResolutionCache.clear();
146
+ publishClassNameRuntimeState();
147
+
148
+ if (__DEV__) {
149
+ for (const warning of compiled.warnings) {
150
+ if (emittedCompilerWarnings.has(warning)) {
151
+ continue;
152
+ }
153
+ emittedCompilerWarnings.add(warning);
154
+ console.warn(warning);
155
+ }
156
+ }
157
+ }
158
+
159
+ export function __clearExactStylesheets(): void {
160
+ stylesheetRegistry.clear();
161
+ classResolutionCache.clear();
162
+ emittedCompilerWarnings.clear();
163
+ emittedUnknownClassWarnings.clear();
164
+ nextSheetOrder = 0;
165
+ publishClassNameRuntimeState();
166
+ }
167
+
168
+ export function resolveClassNameStyle(
169
+ className: unknown,
170
+ context?: ClassNameResolutionContext,
171
+ ): CanonicalStyle {
172
+ const normalizedClassName = normalizeClassNameValue(className);
173
+ if (normalizedClassName.length === 0 || stylesheetRegistry.size === 0) {
174
+ return {};
175
+ }
176
+
177
+ const cached = getOrComputeCachedResolution(normalizedClassName);
178
+
179
+ if (!cached.hasConditionals) {
180
+ return cached.base;
181
+ }
182
+
183
+ let resolved: CanonicalStyle | undefined;
184
+ for (const fragment of cached.conditionals) {
185
+ if (fragment.mediaGate && !fragment.mediaGate.every(evaluateMediaCondition)) {
186
+ continue;
187
+ }
188
+
189
+ if (evaluateCondition(fragment.condition!, context)) {
190
+ if (!resolved) {
191
+ resolved = { ...cached.base };
192
+ }
193
+ Object.assign(resolved, fragment.style);
194
+ }
195
+ }
196
+
197
+ return resolved ?? cached.base;
198
+ }
199
+
200
+ export function classNameHasInteractiveConditions(className: unknown): boolean {
201
+ const normalizedClassName = normalizeClassNameValue(className);
202
+ if (normalizedClassName.length === 0 || stylesheetRegistry.size === 0) {
203
+ return false;
204
+ }
205
+
206
+ const cached = getOrComputeCachedResolution(normalizedClassName);
207
+ return cached.conditionals.some((fragment) => {
208
+ if (!fragment.condition) {
209
+ return false;
210
+ }
211
+
212
+ if (fragment.condition.type === 'pseudo') {
213
+ return (
214
+ fragment.condition.pseudo === 'hover' ||
215
+ fragment.condition.pseudo === 'focus' ||
216
+ fragment.condition.pseudo === 'active' ||
217
+ fragment.condition.pseudo === 'focus-visible'
218
+ );
219
+ }
220
+
221
+ return false;
222
+ });
223
+ }
224
+
225
+ export function classNameHasMediaConditions(className: unknown): boolean {
226
+ const normalizedClassName = normalizeClassNameValue(className);
227
+ if (normalizedClassName.length === 0 || stylesheetRegistry.size === 0) {
228
+ return false;
229
+ }
230
+
231
+ const cached = getOrComputeCachedResolution(normalizedClassName);
232
+ return cached.conditionals.some(
233
+ (fragment) =>
234
+ fragment.condition?.type === 'media' ||
235
+ Boolean(fragment.mediaGate && fragment.mediaGate.length > 0),
236
+ );
237
+ }
238
+
239
+ function publishClassNameRuntimeState(): void {
240
+ globalThis.__exactClassNameResolver = resolveClassNameStyle;
241
+ globalThis.__exactHasExactStylesheets = stylesheetRegistry.size > 0;
242
+ }
243
+
244
+ function deserializeExactStylesheet(
245
+ sourceId: string,
246
+ artifact: SerializedExactStyleSheet,
247
+ preserveSheetOrder?: number,
248
+ ): RegisteredExactStyleSheet {
249
+ const classes = new Map<string, CompiledFragment[]>();
250
+
251
+ for (const [className, fragments] of Object.entries(artifact.classes)) {
252
+ classes.set(
253
+ className,
254
+ fragments.map((fragment) => deserializeFragment(fragment)),
255
+ );
256
+ }
257
+
258
+ return {
259
+ sourceId,
260
+ sheetOrder: preserveSheetOrder ?? nextSheetOrder++,
261
+ classes,
262
+ warnings: [...artifact.warnings],
263
+ };
264
+ }
265
+
266
+ function deserializeFragment(fragment: SerializedCompiledFragment): CompiledFragment {
267
+ return {
268
+ order: fragment.order,
269
+ condition: fragment.condition ? cloneCondition(fragment.condition) : null,
270
+ mediaGate: cloneMediaConditions(fragment.mediaGate),
271
+ style: { ...fragment.style },
272
+ };
273
+ }
274
+
275
+ function cloneCondition(
276
+ condition: SerializedFragmentCondition,
277
+ ): SerializedFragmentCondition {
278
+ switch (condition.type) {
279
+ case 'pseudo':
280
+ return {
281
+ type: 'pseudo',
282
+ pseudo: condition.pseudo,
283
+ };
284
+ case 'media':
285
+ return {
286
+ type: 'media',
287
+ media: cloneMediaConditions(condition.media) ?? [],
288
+ };
289
+ case 'group':
290
+ return {
291
+ type: 'group',
292
+ groupName: condition.groupName,
293
+ pseudo: condition.pseudo,
294
+ };
295
+ case 'peer':
296
+ return {
297
+ type: 'peer',
298
+ peerName: condition.peerName,
299
+ pseudo: condition.pseudo,
300
+ };
301
+ }
302
+ }
303
+
304
+ function cloneMediaConditions(
305
+ conditions: readonly MediaCondition[] | null | undefined,
306
+ ): readonly MediaCondition[] | null {
307
+ if (!conditions || conditions.length === 0) {
308
+ return null;
309
+ }
310
+
311
+ return conditions.map((condition) => ({
312
+ feature: condition.feature,
313
+ value: condition.value,
314
+ }));
315
+ }
316
+
317
+ function getOrComputeCachedResolution(className: string): CachedClassResolution {
318
+ const existing = classResolutionCache.get(className);
319
+ if (existing) {
320
+ touchCacheEntry(className, existing);
321
+ return existing;
322
+ }
323
+
324
+ const tokens = className.split(/\s+/).filter(Boolean);
325
+ const orderedSheets = [...stylesheetRegistry.values()].sort(
326
+ (left, right) => left.sheetOrder - right.sheetOrder,
327
+ );
328
+
329
+ const baseFragments: Array<{
330
+ sheetOrder: number;
331
+ fragment: CompiledFragment;
332
+ }> = [];
333
+ const conditionalFragments: Array<{
334
+ sheetOrder: number;
335
+ fragment: CompiledFragment;
336
+ }> = [];
337
+
338
+ for (const sheet of orderedSheets) {
339
+ for (const token of tokens) {
340
+ const fragments = sheet.classes.get(token);
341
+ if (!fragments || fragments.length === 0) {
342
+ continue;
343
+ }
344
+
345
+ for (const fragment of fragments) {
346
+ // `mediaGate` is part of the conditional evaluation contract even when
347
+ // a fragment has no additional pseudo/group/peer discriminator.
348
+ // Treating those fragments as unconditional would permanently apply the
349
+ // gated style and make media-only overlays impossible to model.
350
+ if (fragment.condition === null && fragment.mediaGate === null) {
351
+ baseFragments.push({ sheetOrder: sheet.sheetOrder, fragment });
352
+ } else {
353
+ conditionalFragments.push({ sheetOrder: sheet.sheetOrder, fragment });
354
+ }
355
+ }
356
+ }
357
+ }
358
+
359
+ if (__DEV__) {
360
+ warnAboutUnknownClasses(tokens);
361
+ }
362
+
363
+ baseFragments.sort(compareFragmentOrder);
364
+ conditionalFragments.sort(compareFragmentOrder);
365
+
366
+ let base: CanonicalStyle = {};
367
+ for (const entry of baseFragments) {
368
+ base = { ...base, ...entry.fragment.style };
369
+ }
370
+
371
+ const cached: CachedClassResolution = {
372
+ base,
373
+ conditionals: conditionalFragments.map((entry) => entry.fragment),
374
+ hasConditionals: conditionalFragments.length > 0,
375
+ };
376
+
377
+ touchCacheEntry(className, cached);
378
+ return cached;
379
+ }
380
+
381
+ function touchCacheEntry(
382
+ className: string,
383
+ cached: CachedClassResolution,
384
+ ): void {
385
+ classResolutionCache.delete(className);
386
+ classResolutionCache.set(className, cached);
387
+
388
+ if (classResolutionCache.size <= CLASS_CACHE_LIMIT) {
389
+ return;
390
+ }
391
+
392
+ const oldestKey = classResolutionCache.keys().next().value;
393
+ if (typeof oldestKey === 'string') {
394
+ classResolutionCache.delete(oldestKey);
395
+ }
396
+ }
397
+
398
+ function warnAboutUnknownClasses(tokens: string[]): void {
399
+ for (const token of tokens) {
400
+ if (token.length === 0) {
401
+ continue;
402
+ }
403
+
404
+ const known = [...stylesheetRegistry.values()].some((sheet) =>
405
+ sheet.classes.has(token),
406
+ );
407
+ if (known) {
408
+ continue;
409
+ }
410
+
411
+ const warning = `⚠ exact-css: Unknown class "${token}" in className. No matching native CSS rule was registered.`;
412
+ if (emittedUnknownClassWarnings.has(warning)) {
413
+ continue;
414
+ }
415
+
416
+ emittedUnknownClassWarnings.add(warning);
417
+ console.warn(warning);
418
+ }
419
+ }
420
+
421
+ function evaluateCondition(
422
+ condition: SerializedFragmentCondition,
423
+ context: ClassNameResolutionContext | undefined,
424
+ ): boolean {
425
+ switch (condition.type) {
426
+ case 'pseudo':
427
+ return evaluatePseudoCondition(condition.pseudo, context);
428
+ case 'media':
429
+ return condition.media.every(evaluateMediaCondition);
430
+ case 'group': {
431
+ const groupState = context?.groupStates?.get(condition.groupName);
432
+ if (!groupState) {
433
+ return false;
434
+ }
435
+ return getPseudoFromInteraction(condition.pseudo, groupState);
436
+ }
437
+ case 'peer': {
438
+ const peerState = context?.peerStates?.getState(condition.peerName);
439
+ if (!peerState) {
440
+ return false;
441
+ }
442
+ return getPseudoFromInteraction(condition.pseudo, peerState);
443
+ }
444
+ }
445
+ }
446
+
447
+ function evaluatePseudoCondition(
448
+ pseudo: SupportedPseudo,
449
+ context: ClassNameResolutionContext | undefined,
450
+ ): boolean {
451
+ switch (pseudo) {
452
+ case 'disabled':
453
+ return isDisabledPseudoActive(context?.props);
454
+ case 'hover':
455
+ return context?.interaction?.hovered === true;
456
+ case 'focus':
457
+ return context?.interaction?.focused === true;
458
+ case 'active':
459
+ return context?.interaction?.active === true;
460
+ case 'focus-visible':
461
+ return context?.interaction?.focusVisible === true;
462
+ case 'first-child':
463
+ return context?.position?.isFirst === true;
464
+ case 'last-child':
465
+ return context?.position?.isLast === true;
466
+ }
467
+ }
468
+
469
+ function evaluateMediaCondition(condition: MediaCondition): boolean {
470
+ const state = currentMediaState;
471
+ switch (condition.feature) {
472
+ case 'min-width':
473
+ return typeof condition.value === 'number' && state.width >= condition.value;
474
+ case 'max-width':
475
+ return typeof condition.value === 'number' && state.width <= condition.value;
476
+ case 'gt-width':
477
+ return typeof condition.value === 'number' && state.width > condition.value;
478
+ case 'lt-width':
479
+ return typeof condition.value === 'number' && state.width < condition.value;
480
+ case 'min-height':
481
+ return typeof condition.value === 'number' && state.height >= condition.value;
482
+ case 'max-height':
483
+ return typeof condition.value === 'number' && state.height <= condition.value;
484
+ case 'gt-height':
485
+ return typeof condition.value === 'number' && state.height > condition.value;
486
+ case 'lt-height':
487
+ return typeof condition.value === 'number' && state.height < condition.value;
488
+ case 'orientation':
489
+ return state.orientation === condition.value;
490
+ case 'prefers-color-scheme':
491
+ return state.colorScheme === condition.value;
492
+ case 'prefers-reduced-motion':
493
+ if (condition.value === 'reduce') {
494
+ return state.reducedMotion;
495
+ }
496
+ if (condition.value === 'no-preference') {
497
+ return !state.reducedMotion;
498
+ }
499
+ return false;
500
+ }
501
+ }
502
+
503
+ function getPseudoFromInteraction(
504
+ pseudo: 'hover' | 'focus' | 'active',
505
+ state: InteractionState,
506
+ ): boolean {
507
+ switch (pseudo) {
508
+ case 'hover':
509
+ return state.hovered;
510
+ case 'focus':
511
+ return state.focused;
512
+ case 'active':
513
+ return state.active;
514
+ }
515
+ }
516
+
517
+ function compareFragmentOrder(
518
+ left: { sheetOrder: number; fragment: CompiledFragment },
519
+ right: { sheetOrder: number; fragment: CompiledFragment },
520
+ ): number {
521
+ if (left.sheetOrder !== right.sheetOrder) {
522
+ return left.sheetOrder - right.sheetOrder;
523
+ }
524
+ return left.fragment.order - right.fragment.order;
525
+ }
526
+
527
+ function normalizeClassNameValue(value: unknown): string {
528
+ if (typeof value !== 'string') {
529
+ return '';
530
+ }
531
+
532
+ return value
533
+ .trim()
534
+ .split(/\s+/)
535
+ .filter(Boolean)
536
+ .join(' ');
537
+ }
538
+
539
+ function isDisabledPseudoActive(
540
+ props: Record<string, unknown> | undefined,
541
+ ): boolean {
542
+ if (!props) {
543
+ return false;
544
+ }
545
+
546
+ return (
547
+ props.disabled === true ||
548
+ props.accessibilityDisabled === true ||
549
+ props['aria-disabled'] === true
550
+ );
551
+ }
552
+
553
+ publishClassNameRuntimeState();
@@ -0,0 +1,29 @@
1
+ import type { CanonicalStyle } from './types.js';
2
+ import type { ClassNameResolutionContext } from './classname-contract.js';
3
+
4
+ type RuntimeClassNameResolver = (
5
+ className: unknown,
6
+ context?: ClassNameResolutionContext,
7
+ ) => CanonicalStyle;
8
+
9
+ declare global {
10
+ var __exactClassNameResolver: RuntimeClassNameResolver | undefined;
11
+ var __exactHasExactStylesheets: boolean | undefined;
12
+ }
13
+
14
+ export type { ClassNameResolutionContext } from './classname-contract.js';
15
+
16
+ export function resolveClassNameStyle(
17
+ className: unknown,
18
+ context?: ClassNameResolutionContext,
19
+ ): CanonicalStyle {
20
+ if (
21
+ typeof className !== 'string' ||
22
+ className.trim().length === 0 ||
23
+ globalThis.__exactHasExactStylesheets !== true
24
+ ) {
25
+ return {};
26
+ }
27
+
28
+ return globalThis.__exactClassNameResolver?.(className, context) ?? {};
29
+ }