@efxlab/motion-canvas-responsive 4.0.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 (133) hide show
  1. package/lib/components/ParticleGroup.d.ts +9 -0
  2. package/lib/components/ParticleGroup.d.ts.map +1 -0
  3. package/lib/components/ParticleGroup.js +20 -0
  4. package/lib/components/ParticleGroup.js.map +1 -0
  5. package/lib/components/RatioLayer.d.ts +7 -0
  6. package/lib/components/RatioLayer.d.ts.map +1 -0
  7. package/lib/components/RatioLayer.js +8 -0
  8. package/lib/components/RatioLayer.js.map +1 -0
  9. package/lib/components/index.d.ts +3 -0
  10. package/lib/components/index.d.ts.map +1 -0
  11. package/lib/components/index.js +3 -0
  12. package/lib/components/index.js.map +1 -0
  13. package/lib/context/ResponsiveContext.d.ts +45 -0
  14. package/lib/context/ResponsiveContext.d.ts.map +1 -0
  15. package/lib/context/ResponsiveContext.js +118 -0
  16. package/lib/context/ResponsiveContext.js.map +1 -0
  17. package/lib/context/ResponsiveContext.test.d.ts +2 -0
  18. package/lib/context/ResponsiveContext.test.d.ts.map +1 -0
  19. package/lib/context/ResponsiveContext.test.js +74 -0
  20. package/lib/helpers/color.d.ts +26 -0
  21. package/lib/helpers/color.d.ts.map +1 -0
  22. package/lib/helpers/color.js +10 -0
  23. package/lib/helpers/color.js.map +1 -0
  24. package/lib/helpers/filter.d.ts +25 -0
  25. package/lib/helpers/filter.d.ts.map +1 -0
  26. package/lib/helpers/filter.js +8 -0
  27. package/lib/helpers/filter.js.map +1 -0
  28. package/lib/helpers/helpers.test.d.ts +2 -0
  29. package/lib/helpers/helpers.test.d.ts.map +1 -0
  30. package/lib/helpers/helpers.test.js +224 -0
  31. package/lib/helpers/index.d.ts +8 -0
  32. package/lib/helpers/index.d.ts.map +1 -0
  33. package/lib/helpers/index.js +8 -0
  34. package/lib/helpers/index.js.map +1 -0
  35. package/lib/helpers/path.d.ts +32 -0
  36. package/lib/helpers/path.d.ts.map +1 -0
  37. package/lib/helpers/path.js +28 -0
  38. package/lib/helpers/path.js.map +1 -0
  39. package/lib/helpers/position.d.ts +20 -0
  40. package/lib/helpers/position.d.ts.map +1 -0
  41. package/lib/helpers/position.js +52 -0
  42. package/lib/helpers/position.js.map +1 -0
  43. package/lib/helpers/rotation.d.ts +33 -0
  44. package/lib/helpers/rotation.d.ts.map +1 -0
  45. package/lib/helpers/rotation.js +14 -0
  46. package/lib/helpers/rotation.js.map +1 -0
  47. package/lib/helpers/scale.d.ts +31 -0
  48. package/lib/helpers/scale.d.ts.map +1 -0
  49. package/lib/helpers/scale.js +24 -0
  50. package/lib/helpers/scale.js.map +1 -0
  51. package/lib/helpers/visibility.d.ts +21 -0
  52. package/lib/helpers/visibility.d.ts.map +1 -0
  53. package/lib/helpers/visibility.js +7 -0
  54. package/lib/helpers/visibility.js.map +1 -0
  55. package/lib/hooks/index.d.ts +5 -0
  56. package/lib/hooks/index.d.ts.map +1 -0
  57. package/lib/hooks/index.js +5 -0
  58. package/lib/hooks/index.js.map +1 -0
  59. package/lib/hooks/useRatio.d.ts +2 -0
  60. package/lib/hooks/useRatio.d.ts.map +1 -0
  61. package/lib/hooks/useRatio.js +2 -0
  62. package/lib/hooks/useRatio.js.map +1 -0
  63. package/lib/hooks/useRatioEffect.d.ts +5 -0
  64. package/lib/hooks/useRatioEffect.d.ts.map +1 -0
  65. package/lib/hooks/useRatioEffect.js +29 -0
  66. package/lib/hooks/useRatioEffect.js.map +1 -0
  67. package/lib/hooks/useRatioElement.d.ts +11 -0
  68. package/lib/hooks/useRatioElement.d.ts.map +1 -0
  69. package/lib/hooks/useRatioElement.js +47 -0
  70. package/lib/hooks/useRatioElement.js.map +1 -0
  71. package/lib/hooks/useResponsive.d.ts +3 -0
  72. package/lib/hooks/useResponsive.d.ts.map +1 -0
  73. package/lib/hooks/useResponsive.js +29 -0
  74. package/lib/hooks/useResponsive.js.map +1 -0
  75. package/lib/hooks/useResponsiveAnimation.d.ts +3 -0
  76. package/lib/hooks/useResponsiveAnimation.d.ts.map +1 -0
  77. package/lib/hooks/useResponsiveAnimation.js +5 -0
  78. package/lib/hooks/useResponsiveAnimation.js.map +1 -0
  79. package/lib/index.d.ts +20 -0
  80. package/lib/index.d.ts.map +1 -0
  81. package/lib/index.js +12 -0
  82. package/lib/index.js.map +1 -0
  83. package/lib/layout/ResponsiveLayout.d.ts +39 -0
  84. package/lib/layout/ResponsiveLayout.d.ts.map +1 -0
  85. package/lib/layout/ResponsiveLayout.js +48 -0
  86. package/lib/layout/ResponsiveLayout.js.map +1 -0
  87. package/lib/layout/ResponsiveLayout.test.d.ts +2 -0
  88. package/lib/layout/ResponsiveLayout.test.d.ts.map +1 -0
  89. package/lib/layout/ResponsiveLayout.test.js +65 -0
  90. package/lib/scene/index.d.ts +3 -0
  91. package/lib/scene/index.d.ts.map +1 -0
  92. package/lib/scene/index.js +3 -0
  93. package/lib/scene/index.js.map +1 -0
  94. package/lib/scene/integration.d.ts +7 -0
  95. package/lib/scene/integration.d.ts.map +1 -0
  96. package/lib/scene/integration.js +7 -0
  97. package/lib/scene/integration.js.map +1 -0
  98. package/lib/scene/makeResponsiveScene.d.ts +19 -0
  99. package/lib/scene/makeResponsiveScene.d.ts.map +1 -0
  100. package/lib/scene/makeResponsiveScene.js +19 -0
  101. package/lib/scene/makeResponsiveScene.js.map +1 -0
  102. package/lib/types.d.ts +35 -0
  103. package/lib/types.d.ts.map +1 -0
  104. package/lib/types.js +2 -0
  105. package/lib/types.js.map +1 -0
  106. package/package.json +42 -0
  107. package/src/components/ParticleGroup.tsx +36 -0
  108. package/src/components/RatioLayer.tsx +19 -0
  109. package/src/components/index.ts +2 -0
  110. package/src/context/ResponsiveContext.test.ts +112 -0
  111. package/src/context/ResponsiveContext.ts +146 -0
  112. package/src/helpers/color.ts +21 -0
  113. package/src/helpers/filter.ts +18 -0
  114. package/src/helpers/helpers.test.ts +252 -0
  115. package/src/helpers/index.ts +7 -0
  116. package/src/helpers/path.ts +49 -0
  117. package/src/helpers/position.ts +75 -0
  118. package/src/helpers/rotation.ts +27 -0
  119. package/src/helpers/scale.ts +42 -0
  120. package/src/helpers/visibility.ts +13 -0
  121. package/src/hooks/index.ts +4 -0
  122. package/src/hooks/useRatio.ts +1 -0
  123. package/src/hooks/useRatioEffect.ts +42 -0
  124. package/src/hooks/useRatioElement.ts +61 -0
  125. package/src/hooks/useResponsive.ts +36 -0
  126. package/src/hooks/useResponsiveAnimation.ts +8 -0
  127. package/src/index.ts +17 -0
  128. package/src/layout/ResponsiveLayout.test.ts +82 -0
  129. package/src/layout/ResponsiveLayout.ts +89 -0
  130. package/src/scene/index.ts +2 -0
  131. package/src/scene/integration.ts +13 -0
  132. package/src/scene/makeResponsiveScene.ts +49 -0
  133. package/src/types.ts +44 -0
@@ -0,0 +1,27 @@
1
+ export interface RotationHelpers {
2
+ spin: (turns: number) => number;
3
+ spinCW: (turns: number) => number;
4
+ spinCCW: (turns: number) => number;
5
+ pivot: {
6
+ center: () => {x: number; y: number};
7
+ topLeft: () => {x: number; y: number};
8
+ topRight: () => {x: number; y: number};
9
+ bottomLeft: () => {x: number; y: number};
10
+ bottomRight: () => {x: number; y: number};
11
+ custom: (x: number, y: number) => {x: number; y: number};
12
+ };
13
+ }
14
+
15
+ export const rotate: RotationHelpers = {
16
+ spin: (turns: number) => turns * 360,
17
+ spinCW: (turns: number) => turns * 360,
18
+ spinCCW: (turns: number) => -turns * 360,
19
+ pivot: {
20
+ center: () => ({x: 0, y: 0}),
21
+ topLeft: () => ({x: -0.5, y: -0.5}),
22
+ topRight: () => ({x: 0.5, y: -0.5}),
23
+ bottomLeft: () => ({x: -0.5, y: 0.5}),
24
+ bottomRight: () => ({x: 0.5, y: 0.5}),
25
+ custom: (x: number, y: number) => ({x, y}),
26
+ },
27
+ };
@@ -0,0 +1,42 @@
1
+ export interface ScaleHelpers {
2
+ from: (value: number) => {from: number; to: number};
3
+ to: (value: number) => {from: number; to: number};
4
+ pop: () => {
5
+ from: number;
6
+ to: number;
7
+ keyframes: Array<{progress: number; scale: number}>;
8
+ };
9
+ pulse: (intensity: number) => {from: number; to: number};
10
+ }
11
+
12
+ export interface TransformHelpers {
13
+ scaleX: (value: number) => number;
14
+ scaleY: (value: number) => number;
15
+ skewX: (degrees: number) => number;
16
+ skewY: (degrees: number) => number;
17
+ }
18
+
19
+ export const scale: ScaleHelpers = {
20
+ from: (value: number) => ({from: value, to: 1}),
21
+ to: (value: number) => ({from: 1, to: value}),
22
+ pop: () => ({
23
+ from: 1,
24
+ to: 1,
25
+ keyframes: [
26
+ {progress: 0, scale: 1},
27
+ {progress: 0.5, scale: 1.1},
28
+ {progress: 1, scale: 1},
29
+ ],
30
+ }),
31
+ pulse: (intensity: number) => ({
32
+ from: 1 - intensity * 0.1,
33
+ to: 1 + intensity * 0.1,
34
+ }),
35
+ };
36
+
37
+ export const transform: TransformHelpers = {
38
+ scaleX: (value: number) => value,
39
+ scaleY: (value: number) => value,
40
+ skewX: (degrees: number) => degrees,
41
+ skewY: (degrees: number) => degrees,
42
+ };
@@ -0,0 +1,13 @@
1
+ export interface VisibilityHelpers {
2
+ fadeIn: (duration: number) => {from: number; to: number; duration: number};
3
+ fadeOut: (duration: number) => {from: number; to: number; duration: number};
4
+ flash: (times: number) => {times: number; duration: number};
5
+ blink: (interval: number) => {interval: number};
6
+ }
7
+
8
+ export const opacity: VisibilityHelpers = {
9
+ fadeIn: (duration: number) => ({from: 0, to: 1, duration}),
10
+ fadeOut: (duration: number) => ({from: 1, to: 0, duration}),
11
+ flash: (times: number) => ({times, duration: times * 0.2}),
12
+ blink: (interval: number) => ({interval}),
13
+ };
@@ -0,0 +1,4 @@
1
+ export * from './useRatioEffect';
2
+ export * from './useRatioElement';
3
+ export * from './useResponsive';
4
+ export * from './useResponsiveAnimation';
@@ -0,0 +1 @@
1
+ export {useRatio} from '../layout/ResponsiveLayout';
@@ -0,0 +1,42 @@
1
+ import {useEffect} from 'react';
2
+ import {
3
+ detectRatioClass,
4
+ useResponsiveContext,
5
+ } from '../context/ResponsiveContext';
6
+ import type {RatioClass, RatioId} from '../types';
7
+
8
+ type RatioCondition =
9
+ | RatioId
10
+ | RatioClass
11
+ | 'portrait'
12
+ | 'landscape'
13
+ | 'square'
14
+ | 'ultrawide';
15
+
16
+ export function useRatioEffect(
17
+ condition: RatioCondition | RatioCondition[],
18
+ callback: () => void | (() => void),
19
+ deps: any[] = [],
20
+ ) {
21
+ const state = useResponsiveContext();
22
+ const aspectRatio = state.width / state.height;
23
+ const ratioClass = detectRatioClass(aspectRatio);
24
+
25
+ const conditions = Array.isArray(condition) ? condition : [condition];
26
+
27
+ const matches = conditions.some(cond => {
28
+ if (cond === state.ratio) return true;
29
+ if (cond === ratioClass) return true;
30
+ if (cond === 'portrait' && ratioClass === 'portrait') return true;
31
+ if (cond === 'landscape' && ratioClass === 'landscape') return true;
32
+ if (cond === 'square' && ratioClass === 'square') return true;
33
+ if (cond === 'ultrawide' && ratioClass === 'ultrawide') return true;
34
+ return false;
35
+ });
36
+
37
+ useEffect(() => {
38
+ if (matches) {
39
+ return callback();
40
+ }
41
+ }, [matches, ...deps]);
42
+ }
@@ -0,0 +1,61 @@
1
+ import React, {useMemo} from 'react';
2
+ import {
3
+ detectRatioClass,
4
+ useResponsiveContext,
5
+ } from '../context/ResponsiveContext';
6
+ import type {RatioClass, RatioId} from '../types';
7
+
8
+ type RatioCondition =
9
+ | RatioId
10
+ | RatioClass
11
+ | 'portrait'
12
+ | 'landscape'
13
+ | 'square'
14
+ | 'ultrawide';
15
+
16
+ export interface UseRatioElementOptions {
17
+ only?: RatioCondition[];
18
+ except?: RatioCondition[];
19
+ element: () => React.ReactElement | null;
20
+ }
21
+
22
+ export function useRatioElement(
23
+ options: UseRatioElementOptions,
24
+ ): React.ReactElement | null {
25
+ const state = useResponsiveContext();
26
+ const aspectRatio = state.width / state.height;
27
+ const ratioClass = detectRatioClass(aspectRatio);
28
+
29
+ return useMemo(() => {
30
+ const conditions = options.only ?? [];
31
+ const exceptions = options.except ?? [];
32
+
33
+ const matchesConditions =
34
+ conditions.length === 0 ||
35
+ conditions.some(cond => {
36
+ if (cond === state.ratio) return true;
37
+ if (cond === ratioClass) return true;
38
+ if (cond === 'portrait' && ratioClass === 'portrait') return true;
39
+ if (cond === 'landscape' && ratioClass === 'landscape') return true;
40
+ if (cond === 'square' && ratioClass === 'square') return true;
41
+ if (cond === 'ultrawide' && ratioClass === 'ultrawide') return true;
42
+ return false;
43
+ });
44
+
45
+ const matchesExceptions = exceptions.some(cond => {
46
+ if (cond === state.ratio) return true;
47
+ if (cond === ratioClass) return true;
48
+ if (cond === 'portrait' && ratioClass === 'portrait') return true;
49
+ if (cond === 'landscape' && ratioClass === 'landscape') return true;
50
+ if (cond === 'square' && ratioClass === 'square') return true;
51
+ if (cond === 'ultrawide' && ratioClass === 'ultrawide') return true;
52
+ return false;
53
+ });
54
+
55
+ if (matchesConditions && !matchesExceptions) {
56
+ return options.element();
57
+ }
58
+
59
+ return null;
60
+ }, [state.ratio, ratioClass, options]);
61
+ }
@@ -0,0 +1,36 @@
1
+ import {
2
+ detectRatioClass,
3
+ useResponsiveContext,
4
+ } from '../context/ResponsiveContext';
5
+ import type {ResponsiveConfig} from '../types';
6
+
7
+ export function useResponsive<T>(config: ResponsiveConfig<T>): T {
8
+ const state = useResponsiveContext();
9
+ const aspectRatio = state.width / state.height;
10
+ const ratioClass = detectRatioClass(aspectRatio);
11
+
12
+ let result: any = {...config.base};
13
+
14
+ const classOverrides: Partial<T>[] = [];
15
+
16
+ if (config.portrait && ratioClass === 'portrait') {
17
+ classOverrides.push(config.portrait);
18
+ } else if (config.landscape && ratioClass === 'landscape') {
19
+ classOverrides.push(config.landscape);
20
+ } else if (config.square && ratioClass === 'square') {
21
+ classOverrides.push(config.square);
22
+ } else if (config.ultrawide && ratioClass === 'ultrawide') {
23
+ classOverrides.push(config.ultrawide);
24
+ }
25
+
26
+ const specificOverride = config[state.ratio];
27
+ if (specificOverride) {
28
+ classOverrides.push(specificOverride);
29
+ }
30
+
31
+ for (const override of classOverrides) {
32
+ result = {...result, ...override};
33
+ }
34
+
35
+ return result as T;
36
+ }
@@ -0,0 +1,8 @@
1
+ import type {AnimationConfig, ResponsiveConfig} from '../types';
2
+ import {useResponsive} from './useResponsive';
3
+
4
+ export function useResponsiveAnimation(
5
+ config: ResponsiveConfig<AnimationConfig>,
6
+ ): AnimationConfig {
7
+ return useResponsive(config);
8
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ export * from './components';
2
+ export * from './context/ResponsiveContext';
3
+ export * from './helpers';
4
+ export * from './hooks/useRatio';
5
+ export * from './hooks/useRatioEffect';
6
+ export * from './hooks/useRatioElement';
7
+ export * from './hooks/useResponsive';
8
+ export * from './hooks/useResponsiveAnimation';
9
+ export * from './layout/ResponsiveLayout';
10
+ export * from './scene';
11
+ export * from './types';
12
+
13
+ declare module '@efxlab/motion-canvas-core' {
14
+ interface Scene {
15
+ setResponsiveSetup(callback: (size: {x: number; y: number}) => void): void;
16
+ }
17
+ }
@@ -0,0 +1,82 @@
1
+ import {describe, expect, it} from 'vitest';
2
+ import {createResponsiveLayout} from '../layout/ResponsiveLayout';
3
+
4
+ describe('ResponsiveLayout', () => {
5
+ describe('createResponsiveLayout', () => {
6
+ it('should create layout for 16:9 landscape', () => {
7
+ const layout = createResponsiveLayout(1920, 1080, '16x9');
8
+
9
+ expect(layout.ratio).toBe('16x9');
10
+ expect(layout.ratioClass).toBe('landscape');
11
+ expect(layout.width).toBe(1920);
12
+ expect(layout.height).toBe(1080);
13
+ expect(layout.ar).toBeCloseTo(1.778, 2);
14
+ expect(layout.isLandscape).toBe(true);
15
+ expect(layout.isPortrait).toBe(false);
16
+ });
17
+
18
+ it('should create layout for 9:16 portrait', () => {
19
+ const layout = createResponsiveLayout(1080, 1920, '9x16');
20
+
21
+ expect(layout.ratio).toBe('9x16');
22
+ expect(layout.ratioClass).toBe('portrait');
23
+ expect(layout.isPortrait).toBe(true);
24
+ expect(layout.isLandscape).toBe(false);
25
+ });
26
+
27
+ it('should create layout for 1:1 square', () => {
28
+ const layout = createResponsiveLayout(1080, 1080, '1x1');
29
+
30
+ expect(layout.ratioClass).toBe('square');
31
+ expect(layout.isSquare).toBe(true);
32
+ });
33
+
34
+ it('should create layout for 21:9 ultrawide', () => {
35
+ const layout = createResponsiveLayout(2520, 1080, '21x9');
36
+
37
+ expect(layout.ratioClass).toBe('ultrawide');
38
+ expect(layout.isUltrawide).toBe(true);
39
+ });
40
+
41
+ it('should calculate px correctly', () => {
42
+ const layout = createResponsiveLayout(1920, 1080, '16x9');
43
+
44
+ expect(layout.px(0)).toBe(0);
45
+ expect(layout.px(-0.5)).toBe(-960);
46
+ expect(layout.px(0.5)).toBe(960);
47
+ });
48
+
49
+ it('should calculate py correctly', () => {
50
+ const layout = createResponsiveLayout(1920, 1080, '16x9');
51
+
52
+ expect(layout.py(0)).toBe(0);
53
+ expect(layout.py(-0.5)).toBe(-540);
54
+ expect(layout.py(0.5)).toBe(540);
55
+ });
56
+
57
+ it('should calculate sz correctly', () => {
58
+ const layout = createResponsiveLayout(1920, 1080, '16x9');
59
+
60
+ expect(layout.sz(1080)).toBe(1080); // base 1080 at 1080 min dimension
61
+ expect(layout.sz(64)).toBeCloseTo(64);
62
+ });
63
+
64
+ it('should calculate half dimensions', () => {
65
+ const layout = createResponsiveLayout(1920, 1080, '16x9');
66
+
67
+ expect(layout.hw).toBe(960);
68
+ expect(layout.hh).toBe(540);
69
+ });
70
+
71
+ it('should calculate padding correctly', () => {
72
+ const layout = createResponsiveLayout(1920, 1080, '16x9');
73
+
74
+ // topPad: -h/2 + sz(padding)
75
+ // -540 + (100 * scale) where scale = 1080/1080 = 1
76
+ expect(layout.topPad(100)).toBe(-440);
77
+
78
+ // bottomPad: h/2 - sz(padding)
79
+ expect(layout.bottomPad(100)).toBe(440);
80
+ });
81
+ });
82
+ });
@@ -0,0 +1,89 @@
1
+ import {
2
+ detectRatioClass,
3
+ useResponsiveContext,
4
+ } from '../context/ResponsiveContext';
5
+ import type {RatioClass, RatioId} from '../types';
6
+
7
+ export interface ResponsiveLayout {
8
+ ratio: RatioId;
9
+ ratioClass: RatioClass;
10
+ width: number;
11
+ height: number;
12
+ w: number;
13
+ h: number;
14
+ hw: number;
15
+ hh: number;
16
+ ar: number;
17
+ isPortrait: boolean;
18
+ isLandscape: boolean;
19
+ isSquare: boolean;
20
+ isUltrawide: boolean;
21
+ px: (frac: number) => number;
22
+ py: (frac: number) => number;
23
+ sz: (base: number) => number;
24
+ sx: (frac: number) => number;
25
+ sy: (frac: number) => number;
26
+ pad: (frac: number) => number;
27
+ topPad: (basePadding: number) => number;
28
+ bottomPad: (basePadding: number) => number;
29
+ leftPad: (basePadding: number) => number;
30
+ rightPad: (basePadding: number) => number;
31
+ }
32
+
33
+ export function createResponsiveLayout(
34
+ width: number,
35
+ height: number,
36
+ ratio: RatioId = '16x9',
37
+ ): ResponsiveLayout {
38
+ const aspectRatio = width / height;
39
+ const ratioClass = detectRatioClass(aspectRatio);
40
+ const scale = Math.min(width, height) / 1080;
41
+
42
+ return {
43
+ ratio,
44
+ ratioClass,
45
+ width,
46
+ height,
47
+ w: width,
48
+ h: height,
49
+ hw: width / 2,
50
+ hh: height / 2,
51
+ ar: aspectRatio,
52
+ isPortrait: ratioClass === 'portrait',
53
+ isLandscape: ratioClass === 'landscape',
54
+ isSquare: ratioClass === 'square',
55
+ isUltrawide: ratioClass === 'ultrawide',
56
+ px: (frac: number) => frac * width,
57
+ py: (frac: number) => frac * height,
58
+ sz: (base: number) => base * scale,
59
+ sx: (frac: number) => frac * width,
60
+ sy: (frac: number) => frac * height,
61
+ pad: (frac: number) => frac * Math.min(width, height),
62
+ topPad: (basePadding: number) => -(height / 2) + basePadding * scale,
63
+ bottomPad: (basePadding: number) => height / 2 - basePadding * scale,
64
+ leftPad: (basePadding: number) => -(width / 2) + basePadding * scale,
65
+ rightPad: (basePadding: number) => width / 2 - basePadding * scale,
66
+ };
67
+ }
68
+
69
+ export function useRatio(): ResponsiveLayout {
70
+ const state = useResponsiveContext();
71
+ return createResponsiveLayout(state.width, state.height, state.ratio);
72
+ }
73
+
74
+ /**
75
+ * Create a responsive layout from width and height values.
76
+ * This is a standalone version that doesn't require the responsive context,
77
+ * useful for creating layouts from raw values.
78
+ *
79
+ * @param width - The width of the view
80
+ * @param height - The height of the view
81
+ * @param ratio - Optional ratio ID (default: '16x9')
82
+ */
83
+ export function viewLayout(
84
+ width: number,
85
+ height: number,
86
+ ratio: RatioId = '16x9',
87
+ ): ResponsiveLayout {
88
+ return createResponsiveLayout(width, height, ratio);
89
+ }
@@ -0,0 +1,2 @@
1
+ export * from './integration';
2
+ export * from './makeResponsiveScene';
@@ -0,0 +1,13 @@
1
+ import type {Vector2} from '@efxlab/motion-canvas-core';
2
+ import {initResponsiveFromSize} from '../context/ResponsiveContext';
3
+ import type {RatioId} from '../types';
4
+
5
+ export interface ResponsiveSetupOptions {
6
+ ratio?: RatioId;
7
+ }
8
+
9
+ export function createResponsiveSetup(options: ResponsiveSetupOptions = {}) {
10
+ return (size: Vector2) => {
11
+ initResponsiveFromSize(size, options.ratio ?? '16x9');
12
+ };
13
+ }
@@ -0,0 +1,49 @@
1
+ import type {Scene2D} from '@efxlab/motion-canvas-2d';
2
+ import type {
3
+ ThreadGenerator,
4
+ ThreadGeneratorFactory,
5
+ } from '@efxlab/motion-canvas-core';
6
+ import {useResponsiveContext} from '../context/ResponsiveContext';
7
+
8
+ type SceneFactory = () => ThreadGenerator;
9
+
10
+ export interface ResponsiveSceneConfig {
11
+ base: SceneFactory;
12
+ portrait?: SceneFactory;
13
+ landscape?: SceneFactory;
14
+ square?: SceneFactory;
15
+ ultrawide?: SceneFactory;
16
+ // eslint-disable-next-line @typescript-eslint/naming-convention
17
+ '16x9'?: SceneFactory;
18
+ // eslint-disable-next-line @typescript-eslint/naming-convention
19
+ '9x16'?: SceneFactory;
20
+ // eslint-disable-next-line @typescript-eslint/naming-convention
21
+ '4x3'?: SceneFactory;
22
+ // eslint-disable-next-line @typescript-eslint/naming-convention
23
+ '1x1'?: SceneFactory;
24
+ fullwindow?: SceneFactory;
25
+ [key: string]: SceneFactory | undefined;
26
+ }
27
+
28
+ export function makeResponsiveScene(
29
+ config: ResponsiveSceneConfig,
30
+ ): ThreadGeneratorFactory<Scene2D> {
31
+ return function* (): ThreadGenerator {
32
+ // Note: _view parameter is required by ThreadGeneratorFactory but scene is selected via config
33
+ const state = useResponsiveContext();
34
+
35
+ const specificFactory = config[state.ratio];
36
+ if (specificFactory) {
37
+ yield* specificFactory();
38
+ return;
39
+ }
40
+
41
+ const classFactory = config[state.ratioClass];
42
+ if (classFactory) {
43
+ yield* classFactory();
44
+ return;
45
+ }
46
+
47
+ yield* config.base();
48
+ };
49
+ }
package/src/types.ts ADDED
@@ -0,0 +1,44 @@
1
+ export type RatioId = '16x9' | '9x16' | '4x3' | '1x1' | 'fullwindow' | string;
2
+
3
+ export type RatioClass = 'landscape' | 'portrait' | 'square' | 'ultrawide';
4
+
5
+ export interface ResponsiveRatioConfig {
6
+ aspect: string;
7
+ ratioClass?: RatioClass;
8
+ }
9
+
10
+ export interface ResponsiveRatiosConfig {
11
+ [ratioId: string]: ResponsiveRatioConfig;
12
+ }
13
+
14
+ export interface ResponsiveConfig<T> {
15
+ base: T;
16
+ portrait?: Partial<T>;
17
+ landscape?: Partial<T>;
18
+ square?: Partial<T>;
19
+ ultrawide?: Partial<T>;
20
+ // eslint-disable-next-line @typescript-eslint/naming-convention
21
+ '16x9'?: Partial<T>;
22
+ // eslint-disable-next-line @typescript-eslint/naming-convention
23
+ '9x16'?: Partial<T>;
24
+ // eslint-disable-next-line @typescript-eslint/naming-convention
25
+ '4x3'?: Partial<T>;
26
+ // eslint-disable-next-line @typescript-eslint/naming-convention
27
+ '1x1'?: Partial<T>;
28
+ fullwindow?: Partial<T>;
29
+ [key: string]: Partial<T> | undefined;
30
+ }
31
+
32
+ export interface ResponsiveState {
33
+ ratio: RatioId;
34
+ ratioClass: RatioClass;
35
+ width: number;
36
+ height: number;
37
+ }
38
+
39
+ export interface AnimationConfig {
40
+ duration?: number;
41
+ easing?: any;
42
+ delay?: number;
43
+ [key: string]: any;
44
+ }