@octanejs/spring 0.0.1

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/UPSTREAM.md +103 -0
  4. package/package.json +73 -0
  5. package/src/animated/Animated.ts +24 -0
  6. package/src/animated/AnimatedArray.ts +26 -0
  7. package/src/animated/index.ts +3 -0
  8. package/src/browser.ts +246 -0
  9. package/src/components.tsrx +60 -0
  10. package/src/components.tsrx.d.ts +33 -0
  11. package/src/context.ts +13 -0
  12. package/src/core/AnimationConfig.ts +60 -0
  13. package/src/core/AnimationResult.ts +19 -0
  14. package/src/core/Controller.ts +200 -0
  15. package/src/core/FrameValue.ts +16 -0
  16. package/src/core/Interpolation.ts +46 -0
  17. package/src/core/SpringRef.ts +26 -0
  18. package/src/core/SpringValue.ts +384 -0
  19. package/src/core/index.ts +9 -0
  20. package/src/core/runAsync.ts +17 -0
  21. package/src/core/scheduleProps.ts +34 -0
  22. package/src/core/valueInterpolation.ts +54 -0
  23. package/src/engine.ts +9 -0
  24. package/src/hooks.ts +610 -0
  25. package/src/index.ts +26 -0
  26. package/src/parallax.ts +8 -0
  27. package/src/parallax.tsrx +321 -0
  28. package/src/parallax.tsrx.d.ts +60 -0
  29. package/src/shared/FrameLoop.ts +75 -0
  30. package/src/shared/createInterpolator.ts +85 -0
  31. package/src/shared/globals.ts +7 -0
  32. package/src/shared/index.ts +4 -0
  33. package/src/shared/stringInterpolation.ts +56 -0
  34. package/src/spring-component.ts +36 -0
  35. package/src/types/animated.ts +10 -0
  36. package/src/types/common.ts +29 -0
  37. package/src/types/fluid.ts +9 -0
  38. package/src/types/index.ts +9 -0
  39. package/src/types/interpolate.ts +50 -0
  40. package/src/types/interpolation.ts +18 -0
  41. package/src/types/objects.ts +17 -0
  42. package/src/types/props.ts +275 -0
  43. package/src/types/utils.ts +57 -0
  44. package/src/upstream-compat.ts +164 -0
  45. package/src/web/AnimatedStyle.ts +51 -0
  46. package/src/web/animated.ts +118 -0
  47. package/src/web/applyAnimatedValues.ts +92 -0
  48. package/src/web/index.ts +3 -0
@@ -0,0 +1,118 @@
1
+ import { raf } from '@react-spring/rafz';
2
+ import { createElement, useLayoutEffect, useState } from 'octane';
3
+ import { FrameValue } from '../core/FrameValue';
4
+ import { resolveAnimatedStyle } from './AnimatedStyle';
5
+ import { applyAnimatedValues } from './applyAnimatedValues';
6
+
7
+ const STATE = Symbol.for('octane-react-spring:animated-host-state');
8
+ const EFFECT = Symbol.for('octane-react-spring:animated-host-effect');
9
+ const FALLBACK = Symbol.for('octane-react-spring:animated-host-fallback');
10
+
11
+ function isPlainObject(value: any): value is Record<string, any> {
12
+ if (!value || typeof value !== 'object') return false;
13
+ const prototype = Object.getPrototypeOf(value);
14
+ return prototype === Object.prototype || prototype === null;
15
+ }
16
+
17
+ function collect(value: any, found = new Set<FrameValue<any>>()): Set<FrameValue<any>> {
18
+ if (value instanceof FrameValue) found.add(value);
19
+ else if (Array.isArray(value)) for (const item of value) collect(item, found);
20
+ else if (isPlainObject(value)) for (const key in value) collect(value[key], found);
21
+ return found;
22
+ }
23
+
24
+ function read(value: any): any {
25
+ if (value instanceof FrameValue) return value.get();
26
+ if (Array.isArray(value)) return value.map(read);
27
+ if (isPlainObject(value)) {
28
+ const result: Record<string, any> = {};
29
+ for (const key in value) result[key] = read(value[key]);
30
+ return result;
31
+ }
32
+ return value;
33
+ }
34
+
35
+ function snapshot(props: Record<string, any>): Record<string, any> {
36
+ const result = read(props);
37
+ if (props.style) result.style = resolveAnimatedStyle(props.style);
38
+ return result;
39
+ }
40
+
41
+ function forwardRef(ref: any, value: any): void {
42
+ if (typeof ref === 'function') ref(value);
43
+ else if (ref) ref.current = value;
44
+ }
45
+
46
+ function createAnimatedComponent(Component: any) {
47
+ return function AnimatedComponent(props: Record<string, any>): unknown {
48
+ const deps = [...collect(props)];
49
+ const initial = snapshot(props);
50
+ const [state] = useState(
51
+ () => ({ node: null as any, deps, props, pending: false, live: true }),
52
+ STATE,
53
+ );
54
+ const [, forceUpdate] = useState(0, FALLBACK);
55
+ state.deps = deps;
56
+ state.props = props;
57
+ state.live = true;
58
+ const ref = (node: any) => {
59
+ state.node = node;
60
+ forwardRef(props.ref, node);
61
+ };
62
+
63
+ useLayoutEffect(
64
+ () => {
65
+ state.live = true;
66
+ const apply = () => {
67
+ state.pending = false;
68
+ if (!state.live) return;
69
+ if (!state.node) {
70
+ forceUpdate((version) => version + 1);
71
+ return;
72
+ }
73
+ applyAnimatedValues(state.node, snapshot(state.props));
74
+ if (state.props.children instanceof FrameValue) {
75
+ state.node.textContent = String(state.props.children.get() ?? '');
76
+ }
77
+ };
78
+ const schedule = () => {
79
+ if (state.pending) return;
80
+ state.pending = true;
81
+ raf.write(apply);
82
+ };
83
+ const cleanups = state.deps.map((dep: FrameValue<any>) => dep.onChange(schedule));
84
+ apply();
85
+ return () => {
86
+ state.live = false;
87
+ state.pending = false;
88
+ for (const cleanup of cleanups) cleanup();
89
+ raf.cancel(apply);
90
+ };
91
+ },
92
+ deps,
93
+ EFFECT,
94
+ );
95
+ return createElement(Component, { ...initial, ref });
96
+ };
97
+ }
98
+
99
+ const cache = new Map<any, ReturnType<typeof createAnimatedComponent>>();
100
+ function animatedFactory(Component: any) {
101
+ let wrapped = cache.get(Component);
102
+ if (!wrapped) {
103
+ wrapped = createAnimatedComponent(Component);
104
+ cache.set(Component, wrapped);
105
+ }
106
+ return wrapped;
107
+ }
108
+
109
+ export const animated = new Proxy(animatedFactory, {
110
+ apply(_target, _this, args: [any]) {
111
+ return animatedFactory(args[0]);
112
+ },
113
+ get(_target, key) {
114
+ return typeof key === 'string' ? animatedFactory(key) : undefined;
115
+ },
116
+ }) as typeof animatedFactory & Record<string, ReturnType<typeof createAnimatedComponent>>;
117
+
118
+ export const a = animated;
@@ -0,0 +1,92 @@
1
+ const customProperty = /^--/;
2
+
3
+ const unitless = new Set([
4
+ 'animationIterationCount',
5
+ 'borderImageOutset',
6
+ 'borderImageSlice',
7
+ 'borderImageWidth',
8
+ 'columnCount',
9
+ 'columns',
10
+ 'flex',
11
+ 'flexGrow',
12
+ 'flexShrink',
13
+ 'fontWeight',
14
+ 'gridColumn',
15
+ 'gridColumnEnd',
16
+ 'gridColumnStart',
17
+ 'gridRow',
18
+ 'gridRowEnd',
19
+ 'gridRowStart',
20
+ 'lineClamp',
21
+ 'lineHeight',
22
+ 'opacity',
23
+ 'order',
24
+ 'orphans',
25
+ 'tabSize',
26
+ 'widows',
27
+ 'zIndex',
28
+ 'zoom',
29
+ 'fillOpacity',
30
+ 'floodOpacity',
31
+ 'stopOpacity',
32
+ 'strokeDasharray',
33
+ 'strokeDashoffset',
34
+ 'strokeMiterlimit',
35
+ 'strokeOpacity',
36
+ 'strokeWidth',
37
+ ]);
38
+
39
+ export function dangerousStyleValue(name: string, value: unknown): string {
40
+ if (value == null || typeof value === 'boolean' || value === '') return '';
41
+ if (
42
+ typeof value === 'number' &&
43
+ value !== 0 &&
44
+ !customProperty.test(name) &&
45
+ !unitless.has(name)
46
+ ) {
47
+ return `${value}px`;
48
+ }
49
+ return String(value).trim();
50
+ }
51
+
52
+ export function applyAnimatedValues(instance: any, props: Record<string, any>): boolean {
53
+ if (!instance?.nodeType || !instance.setAttribute || !instance.removeAttribute) return false;
54
+ const {
55
+ className,
56
+ style,
57
+ children: _children,
58
+ scrollTop,
59
+ scrollLeft,
60
+ viewBox,
61
+ ref: _ref,
62
+ ...attributes
63
+ } = props;
64
+ for (const name in style ?? {}) {
65
+ const value = dangerousStyleValue(name, style[name]);
66
+ if (customProperty.test(name)) instance.style.setProperty(name, value);
67
+ else instance.style[name] = value;
68
+ }
69
+ for (const originalName in attributes) {
70
+ if (originalName === 'key') continue;
71
+ const value = attributes[originalName];
72
+ // Octane's host component owns event listeners. Re-applying them as DOM
73
+ // attributes would stringify callbacks instead of updating the listener.
74
+ if (typeof value === 'function' || /^on[A-Z]/.test(originalName)) continue;
75
+ const name = instance.hasAttribute(originalName)
76
+ ? originalName
77
+ : originalName.replace(/([A-Z])/g, (letter: string) => `-${letter.toLowerCase()}`);
78
+ if (value === undefined) instance.removeAttribute(name);
79
+ else instance.setAttribute(name, String(value));
80
+ }
81
+ if (Object.prototype.hasOwnProperty.call(props, 'className')) {
82
+ if (className === undefined) instance.removeAttribute('class');
83
+ else instance.className = className;
84
+ }
85
+ if (scrollTop !== undefined) instance.scrollTop = scrollTop;
86
+ if (scrollLeft !== undefined) instance.scrollLeft = scrollLeft;
87
+ if (Object.prototype.hasOwnProperty.call(props, 'viewBox')) {
88
+ if (viewBox === undefined) instance.removeAttribute('viewBox');
89
+ else instance.setAttribute('viewBox', viewBox);
90
+ }
91
+ return true;
92
+ }
@@ -0,0 +1,3 @@
1
+ export { animated, a } from './animated';
2
+ export { applyAnimatedValues, dangerousStyleValue } from './applyAnimatedValues';
3
+ export { resolveAnimatedStyle } from './AnimatedStyle';