@unrulysystems/native-motion-web 0.1.0-alpha.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,136 @@
1
+ 'use client'
2
+ // REQ-API-035 / REQ-WEB-022: motion/react owns web configuration inheritance and transition
3
+ // behavior. This adapter retains Native Motion's named aliases and validates only its own wrapper
4
+ // props; it never substitutes native transition semantics for the pin.
5
+
6
+ import type { Transition as CoreTransition } from '@unrulysystems/native-motion-core'
7
+ import {
8
+ MotionConfig as PinMotionConfig,
9
+ type MotionConfigProps as PinMotionConfigProps,
10
+ } from 'motion/react'
11
+ import { createContext, createElement, useContext, type ReactNode } from 'react'
12
+ import { MotionWebRejectionError } from './errors'
13
+ import { ambientMode } from './mode'
14
+ import type { NormalizeOptions } from './normalize'
15
+ import { mapWebTransitionAliases, type WebTransition } from './webTransition'
16
+
17
+ /** Retained compatibility seam; web hosts no longer consume a parallel validation context. */
18
+ export interface MotionConfigValidationValue {
19
+ readonly transition: undefined
20
+ }
21
+
22
+ const DEFAULT_VALIDATION: MotionConfigValidationValue = Object.freeze({ transition: undefined })
23
+ export const MotionConfigValidationContext = createContext(DEFAULT_VALIDATION)
24
+ export function useMotionConfigValidation(): MotionConfigValidationValue {
25
+ return useContext(MotionConfigValidationContext)
26
+ }
27
+
28
+ export type ReducedMotionConfig = 'always' | 'never' | 'user'
29
+ export type MotionConfigTransition =
30
+ | NonNullable<PinMotionConfigProps['transition']>
31
+ | (CoreTransition & { readonly inherit?: boolean })
32
+
33
+ export interface MotionConfigProps {
34
+ readonly children?: ReactNode
35
+ readonly transition?: MotionConfigTransition | undefined
36
+ readonly reducedMotion?: ReducedMotionConfig | undefined
37
+ readonly nonce?: string | undefined
38
+ readonly isStatic?: boolean | undefined
39
+ readonly skipAnimations?: boolean | undefined
40
+ readonly transformPagePoint?: PinMotionConfigProps['transformPagePoint']
41
+ }
42
+
43
+ const MOTION_CONFIG_OWNER = '<MotionConfig>'
44
+ const REDUCED_MOTION_VALUES = new Set(['always', 'never', 'user'])
45
+
46
+ function refuse(options: NormalizeOptions, message: string): void {
47
+ const error = new MotionWebRejectionError(message)
48
+ if (options.mode === 'development') throw error
49
+ options.report(error)
50
+ }
51
+
52
+ function mapTransition(
53
+ transition: MotionConfigTransition | undefined,
54
+ options: NormalizeOptions,
55
+ ): WebTransition | undefined {
56
+ if (transition === undefined) return undefined
57
+ try {
58
+ return mapWebTransitionAliases(transition as WebTransition, MOTION_CONFIG_OWNER)
59
+ } catch (error) {
60
+ if (!(error instanceof MotionWebRejectionError)) throw error
61
+ if (options.mode === 'development') throw error
62
+ options.report(error)
63
+ return undefined
64
+ }
65
+ }
66
+
67
+ function gateEnum<T extends string>(
68
+ name: string,
69
+ value: unknown,
70
+ accepted: ReadonlySet<string>,
71
+ options: NormalizeOptions,
72
+ ): T | undefined {
73
+ if (value === undefined) return undefined
74
+ if (typeof value === 'string' && accepted.has(value)) return value as T
75
+ refuse(options, `${MOTION_CONFIG_OWNER}: invalid ${name}; the wrapper requires a pinned value`)
76
+ return undefined
77
+ }
78
+
79
+ function gateBoolean(name: string, value: unknown, options: NormalizeOptions): boolean | undefined {
80
+ if (value === undefined) return undefined
81
+ if (typeof value === 'boolean') return value
82
+ refuse(options, `${MOTION_CONFIG_OWNER}: invalid ${name}; expected a boolean`)
83
+ return undefined
84
+ }
85
+
86
+ export function createMotionConfig(options: NormalizeOptions) {
87
+ return function MotionConfig({
88
+ children,
89
+ transition,
90
+ reducedMotion,
91
+ nonce,
92
+ isStatic,
93
+ skipAnimations,
94
+ transformPagePoint,
95
+ ...rest
96
+ }: MotionConfigProps) {
97
+ for (const [key, value] of Object.entries(rest)) {
98
+ if (value !== undefined)
99
+ refuse(options, `${MOTION_CONFIG_OWNER}: unsupported wrapper prop '${key}'`)
100
+ }
101
+ const mappedTransition = mapTransition(transition, options)
102
+ const mappedReduced = gateEnum<ReducedMotionConfig>(
103
+ 'reducedMotion',
104
+ reducedMotion,
105
+ REDUCED_MOTION_VALUES,
106
+ options,
107
+ )
108
+ const mappedStatic = gateBoolean('isStatic', isStatic, options)
109
+ const mappedSkip = gateBoolean('skipAnimations', skipAnimations, options)
110
+ if (nonce !== undefined && typeof nonce !== 'string')
111
+ refuse(options, `${MOTION_CONFIG_OWNER}: invalid nonce; expected a string`)
112
+ if (transformPagePoint !== undefined && typeof transformPagePoint !== 'function')
113
+ refuse(options, `${MOTION_CONFIG_OWNER}: invalid transformPagePoint; expected a function`)
114
+
115
+ const forwarded: PinMotionConfigProps = {
116
+ ...(mappedTransition === undefined
117
+ ? {}
118
+ : { transition: mappedTransition as NonNullable<PinMotionConfigProps['transition']> }),
119
+ ...(mappedReduced === undefined ? {} : { reducedMotion: mappedReduced }),
120
+ ...(mappedStatic === undefined ? {} : { isStatic: mappedStatic }),
121
+ ...(mappedSkip === undefined ? {} : { skipAnimations: mappedSkip }),
122
+ ...(typeof nonce === 'string' ? { nonce } : {}),
123
+ ...(typeof transformPagePoint === 'function' ? { transformPagePoint } : {}),
124
+ }
125
+ return createElement(PinMotionConfig, forwarded, children)
126
+ }
127
+ }
128
+
129
+ function consoleReporter(error: Error): void {
130
+ console.error(error)
131
+ }
132
+
133
+ export const MotionConfig = createMotionConfig({
134
+ mode: ambientMode(),
135
+ report: consoleReporter,
136
+ })
@@ -0,0 +1,18 @@
1
+ 'use client'
2
+ // Web entry counterpart of native MotionLengthLayoutProvider (REQ-VALUETYPE-013 namespace parity).
3
+ // Pin motion/react measure-resolves %/auto/calc itself — this provider is a no-op passthrough so
4
+ // dual-entry product code can share the same composition without branching.
5
+
6
+ import type { ReactNode } from 'react'
7
+ import type { LengthLayoutContext } from '@unrulysystems/native-motion-core'
8
+
9
+ export interface MotionLengthLayoutProviderProps {
10
+ readonly value: LengthLayoutContext
11
+ readonly children: ReactNode
12
+ }
13
+
14
+ export function MotionLengthLayoutProvider({
15
+ children,
16
+ }: MotionLengthLayoutProviderProps): ReactNode {
17
+ return children
18
+ }
@@ -0,0 +1,13 @@
1
+ // REQ-API-022 web counterpart (FLAG 5a, specs/SPEC-GESTURE.md) — MotionRoot on
2
+ // the web engine: the root primitive exists on BOTH entries (one-import universal surface, entry
3
+ // value-key parity), but the web engine needs no gesture root — motion/react's recognizers bind
4
+ // per element. Children render unchanged; no wrapper element, no style knob (a web-ignored style
5
+ // would be an accepted-but-ignored prop, REQ-API-016 — the native entry matches).
6
+
7
+ export interface MotionRootProps {
8
+ readonly children?: React.ReactNode
9
+ }
10
+
11
+ export function MotionRoot({ children }: MotionRootProps) {
12
+ return <>{children}</>
13
+ }