@ssgoi/core 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 (74) hide show
  1. package/README.md +34 -0
  2. package/dist/animator.d.ts +41 -0
  3. package/dist/animator.d.ts.map +1 -0
  4. package/dist/create-ssgoi-transition-context.d.ts +15 -0
  5. package/dist/create-ssgoi-transition-context.d.ts.map +1 -0
  6. package/dist/create-transition-callback.d.ts +36 -0
  7. package/dist/create-transition-callback.d.ts.map +1 -0
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/ssgoi.cjs +1 -0
  11. package/dist/ssgoi.js +1 -0
  12. package/dist/ssgoi2.cjs +1 -0
  13. package/dist/ssgoi2.js +305 -0
  14. package/dist/ssgoi3.cjs +1 -0
  15. package/dist/ssgoi3.js +173 -0
  16. package/dist/ssgoi4.cjs +1 -0
  17. package/dist/ssgoi4.js +764 -0
  18. package/dist/transition.d.ts +16 -0
  19. package/dist/transition.d.ts.map +1 -0
  20. package/dist/transitions/blur.d.ts +27 -0
  21. package/dist/transitions/blur.d.ts.map +1 -0
  22. package/dist/transitions/bounce.d.ts +27 -0
  23. package/dist/transitions/bounce.d.ts.map +1 -0
  24. package/dist/transitions/fade.d.ts +26 -0
  25. package/dist/transitions/fade.d.ts.map +1 -0
  26. package/dist/transitions/index.d.ts +8 -0
  27. package/dist/transitions/index.d.ts.map +1 -0
  28. package/dist/transitions/none.d.ts +24 -0
  29. package/dist/transitions/none.d.ts.map +1 -0
  30. package/dist/transitions/rotate.d.ts +28 -0
  31. package/dist/transitions/rotate.d.ts.map +1 -0
  32. package/dist/transitions/scale.d.ts +27 -0
  33. package/dist/transitions/scale.d.ts.map +1 -0
  34. package/dist/transitions/slide.d.ts +27 -0
  35. package/dist/transitions/slide.d.ts.map +1 -0
  36. package/dist/transitions.d.ts +2 -0
  37. package/dist/types.d.ts +38 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/dist/utils-BSgyev81.cjs +1 -0
  40. package/dist/utils-DR41YsAh.js +16 -0
  41. package/dist/utils.d.ts +11 -0
  42. package/dist/utils.d.ts.map +1 -0
  43. package/dist/view-transitions/fade.d.ts +7 -0
  44. package/dist/view-transitions/fade.d.ts.map +1 -0
  45. package/dist/view-transitions/hero.d.ts +8 -0
  46. package/dist/view-transitions/hero.d.ts.map +1 -0
  47. package/dist/view-transitions/index.d.ts +5 -0
  48. package/dist/view-transitions/index.d.ts.map +1 -0
  49. package/dist/view-transitions/pinterest.d.ts +8 -0
  50. package/dist/view-transitions/pinterest.d.ts.map +1 -0
  51. package/dist/view-transitions/ripple.d.ts +7 -0
  52. package/dist/view-transitions/ripple.d.ts.map +1 -0
  53. package/dist/view-transitions.d.ts +2 -0
  54. package/package.json +58 -0
  55. package/src/lib/animator.ts +172 -0
  56. package/src/lib/create-ssgoi-transition-context.ts +265 -0
  57. package/src/lib/create-transition-callback.ts +246 -0
  58. package/src/lib/index.ts +3 -0
  59. package/src/lib/transition.ts +76 -0
  60. package/src/lib/transitions/blur.ts +55 -0
  61. package/src/lib/transitions/bounce.ts +61 -0
  62. package/src/lib/transitions/fade.ts +33 -0
  63. package/src/lib/transitions/index.ts +7 -0
  64. package/src/lib/transitions/none.ts +27 -0
  65. package/src/lib/transitions/rotate.ts +57 -0
  66. package/src/lib/transitions/scale.ts +34 -0
  67. package/src/lib/transitions/slide.ts +53 -0
  68. package/src/lib/types.ts +60 -0
  69. package/src/lib/utils.ts +39 -0
  70. package/src/lib/view-transitions/fade.ts +29 -0
  71. package/src/lib/view-transitions/hero.ts +179 -0
  72. package/src/lib/view-transitions/index.ts +4 -0
  73. package/src/lib/view-transitions/pinterest.ts +403 -0
  74. package/src/lib/view-transitions/ripple.ts +45 -0
@@ -0,0 +1,33 @@
1
+ interface FadeOptions {
2
+ from?: number;
3
+ to?: number;
4
+ spring?: {
5
+ stiffness?: number;
6
+ damping?: number;
7
+ };
8
+ }
9
+
10
+ export const fade = (options: FadeOptions = {}) => {
11
+ const {
12
+ from = 0,
13
+ to = 1,
14
+ spring = { stiffness: 300, damping: 30 }
15
+ } = options;
16
+
17
+ return {
18
+ in: (element: HTMLElement) => ({
19
+ spring,
20
+ tick: (progress: number) => {
21
+ const opacity = from + (to - from) * progress;
22
+ element.style.opacity = opacity.toString();
23
+ }
24
+ }),
25
+ out: (element: HTMLElement) => ({
26
+ spring,
27
+ tick: (progress: number) => {
28
+ const opacity = from + (to - from) * progress;
29
+ element.style.opacity = opacity.toString();
30
+ }
31
+ })
32
+ };
33
+ };
@@ -0,0 +1,7 @@
1
+ export * from './fade';
2
+ export * from './scale';
3
+ export * from './slide';
4
+ export * from './rotate';
5
+ export * from './bounce';
6
+ export * from './blur';
7
+ export * from './none';
@@ -0,0 +1,27 @@
1
+ interface NoneOptions {
2
+ spring?: {
3
+ stiffness?: number;
4
+ damping?: number;
5
+ };
6
+ }
7
+
8
+ export const none = (options: NoneOptions = {}) => {
9
+ const {
10
+ spring = { stiffness: 1000, damping: 100 }
11
+ } = options;
12
+
13
+ return {
14
+ in: (_element: HTMLElement) => ({
15
+ spring,
16
+ tick: (_progress: number) => {
17
+ // No animation, just instantly show
18
+ }
19
+ }),
20
+ out: (_element: HTMLElement) => ({
21
+ spring,
22
+ tick: (_progress: number) => {
23
+ // No animation, just instantly hide
24
+ }
25
+ })
26
+ };
27
+ };
@@ -0,0 +1,57 @@
1
+ interface RotateOptions {
2
+ degrees?: number;
3
+ clockwise?: boolean;
4
+ scale?: boolean;
5
+ fade?: boolean;
6
+ spring?: {
7
+ stiffness?: number;
8
+ damping?: number;
9
+ };
10
+ }
11
+
12
+ export const rotate = (options: RotateOptions = {}) => {
13
+ const {
14
+ degrees = 360,
15
+ clockwise = true,
16
+ scale = false,
17
+ fade = false,
18
+ spring = { stiffness: 500, damping: 25 }
19
+ } = options;
20
+
21
+ const rotation = clockwise ? degrees : -degrees;
22
+
23
+ return {
24
+ in: (element: HTMLElement) => ({
25
+ spring,
26
+ tick: (progress: number) => {
27
+ const transforms = [`rotate(${progress * rotation}deg)`];
28
+
29
+ if (scale) {
30
+ transforms.push(`scale(${progress})`);
31
+ }
32
+
33
+ element.style.transform = transforms.join(' ');
34
+
35
+ if (fade) {
36
+ element.style.opacity = progress.toString();
37
+ }
38
+ }
39
+ }),
40
+ out: (element: HTMLElement) => ({
41
+ spring,
42
+ tick: (progress: number) => {
43
+ const transforms = [`rotate(${progress * rotation}deg)`];
44
+
45
+ if (scale) {
46
+ transforms.push(`scale(${progress})`);
47
+ }
48
+
49
+ element.style.transform = transforms.join(' ');
50
+
51
+ if (fade) {
52
+ element.style.opacity = progress.toString();
53
+ }
54
+ }
55
+ })
56
+ };
57
+ };
@@ -0,0 +1,34 @@
1
+ interface ScaleOptions {
2
+ duration?: number;
3
+ from?: number;
4
+ to?: number;
5
+ spring?: {
6
+ stiffness?: number;
7
+ damping?: number;
8
+ };
9
+ }
10
+
11
+ export const scale = (options: ScaleOptions = {}) => {
12
+ const {
13
+ from = 0,
14
+ to = 1,
15
+ spring = { stiffness: 300, damping: 30 }
16
+ } = options;
17
+
18
+ return {
19
+ in: (element: HTMLElement) => ({
20
+ spring,
21
+ tick: (progress: number) => {
22
+ const value = from + (to - from) * progress;
23
+ element.style.transform = `scale(${value})`;
24
+ }
25
+ }),
26
+ out: (element: HTMLElement) => ({
27
+ spring,
28
+ tick: (progress: number) => {
29
+ const value = from + (to - from) * progress;
30
+ element.style.transform = `scale(${value})`;
31
+ }
32
+ })
33
+ };
34
+ };
@@ -0,0 +1,53 @@
1
+ interface SlideOptions {
2
+ direction?: 'left' | 'right' | 'up' | 'down';
3
+ distance?: number;
4
+ fade?: boolean;
5
+ spring?: {
6
+ stiffness?: number;
7
+ damping?: number;
8
+ };
9
+ }
10
+
11
+ export const slide = (options: SlideOptions = {}) => {
12
+ const {
13
+ direction = 'left',
14
+ distance = 100,
15
+ fade = true,
16
+ spring = { stiffness: 400, damping: 35 }
17
+ } = options;
18
+
19
+ const getTransform = (progress: number) => {
20
+ const offset = (1 - progress) * distance;
21
+ switch (direction) {
22
+ case 'left':
23
+ return `translateX(${-offset}px)`;
24
+ case 'right':
25
+ return `translateX(${offset}px)`;
26
+ case 'up':
27
+ return `translateY(${-offset}px)`;
28
+ case 'down':
29
+ return `translateY(${offset}px)`;
30
+ }
31
+ };
32
+
33
+ return {
34
+ in: (element: HTMLElement) => ({
35
+ spring,
36
+ tick: (progress: number) => {
37
+ element.style.transform = getTransform(progress);
38
+ if (fade) {
39
+ element.style.opacity = progress.toString();
40
+ }
41
+ }
42
+ }),
43
+ out: (element: HTMLElement) => ({
44
+ spring,
45
+ tick: (progress: number) => {
46
+ element.style.transform = getTransform(progress);
47
+ if (fade) {
48
+ element.style.opacity = progress.toString();
49
+ }
50
+ }
51
+ })
52
+ };
53
+ };
@@ -0,0 +1,60 @@
1
+ export type TransitionKey = string | symbol;
2
+
3
+ export type SpringConfig = {
4
+ stiffness?: number;
5
+ damping?: number;
6
+ };
7
+
8
+ export type TransitionConfig = {
9
+ // Spring physics configuration
10
+ spring?: SpringConfig; // Default: { stiffness: 300, damping: 30 }
11
+
12
+ // Callback for each frame with progress value (0-1)
13
+ tick?: (progress: number) => void;
14
+
15
+ // Prepare element before animation (typically for out transitions)
16
+ prepare?: (element: HTMLElement) => void;
17
+
18
+ // Called when animation starts
19
+ onStart?: () => void;
20
+
21
+ // Called when animation ends
22
+ onEnd?: () => void;
23
+ };
24
+
25
+ export type GetTransitionConfig<TContext = undefined> =
26
+ TContext extends undefined
27
+ ? (node: HTMLElement) => TransitionConfig | Promise<TransitionConfig>
28
+ : (
29
+ node: HTMLElement,
30
+ context: TContext
31
+ ) => TransitionConfig | Promise<TransitionConfig>;
32
+
33
+ export type Transition<TContext = undefined> = {
34
+ in?: GetTransitionConfig<TContext>;
35
+ out?: GetTransitionConfig<TContext>;
36
+ };
37
+
38
+ export type TransitionCallback = (
39
+ element: HTMLElement | null
40
+ ) => void | (() => void);
41
+
42
+ export type SggoiTransitionContext = {
43
+ scrollOffset: { x: number; y: number };
44
+ };
45
+
46
+ export type SggoiTransition = Transition<SggoiTransitionContext>;
47
+
48
+ export type SsgoiConfig = {
49
+ transitions: {
50
+ from: string;
51
+ to: string;
52
+ transition: SggoiTransition;
53
+ symmetric?: boolean;
54
+ }[];
55
+ defaultTransition?: SggoiTransition;
56
+ };
57
+
58
+ export type SsgoiContext = (
59
+ path: string
60
+ ) => Transition & { key: TransitionKey };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Applies common styles for outgoing page elements
3
+ * Makes the element absolute positioned to allow the incoming page to take its place
4
+ */
5
+ export const prepareOutgoing = (element: HTMLElement): void => {
6
+ element.style.position = "absolute";
7
+ element.style.width = "100%";
8
+ element.style.top = "0";
9
+ element.style.left = "0";
10
+ };
11
+
12
+ /**
13
+ * Gets the scrolling element that contains the given element
14
+ * Returns the first scrollable parent element or document.documentElement
15
+ */
16
+ export const getScrollingElement = (element: HTMLElement): HTMLElement => {
17
+ let current = element.parentElement;
18
+
19
+ while (current && current !== document.body) {
20
+ const style = window.getComputedStyle(current);
21
+ const overflow = style.overflow + style.overflowY + style.overflowX;
22
+
23
+ // Check if element is scrollable
24
+ if (overflow.includes('auto') || overflow.includes('scroll')) {
25
+ return current;
26
+ }
27
+
28
+ // Also check if element has scroll even without explicit overflow
29
+ if (current.scrollHeight > current.clientHeight ||
30
+ current.scrollWidth > current.clientWidth) {
31
+ return current;
32
+ }
33
+
34
+ current = current.parentElement;
35
+ }
36
+
37
+ // Return document element as fallback (handles body/html scrolling)
38
+ return document.documentElement;
39
+ };
@@ -0,0 +1,29 @@
1
+ import type { SpringConfig, SggoiTransition } from "../types";
2
+ import { prepareOutgoing } from "../utils";
3
+
4
+ interface FadeOptions {
5
+ spring?: Partial<SpringConfig>;
6
+ }
7
+
8
+ export const fade = (options: FadeOptions = {}): SggoiTransition => {
9
+ const spring: SpringConfig = {
10
+ stiffness: options.spring?.stiffness ?? 300,
11
+ damping: options.spring?.damping ?? 30,
12
+ };
13
+
14
+ return {
15
+ in: (element) => ({
16
+ spring,
17
+ tick: (progress) => {
18
+ element.style.opacity = progress.toString();
19
+ },
20
+ }),
21
+ out: (element) => ({
22
+ spring,
23
+ tick: (progress) => {
24
+ element.style.opacity = progress.toString();
25
+ },
26
+ prepare: prepareOutgoing,
27
+ }),
28
+ };
29
+ };
@@ -0,0 +1,179 @@
1
+ import type { SpringConfig, SggoiTransition } from "../types";
2
+ import { prepareOutgoing } from "../utils";
3
+
4
+ interface HeroOptions {
5
+ spring?: Partial<SpringConfig>;
6
+ timeout?: number;
7
+ }
8
+
9
+ function getHeroEl(page: HTMLElement, key: string): HTMLElement | null {
10
+ return page.querySelector(`[data-hero-key="${key}"]`);
11
+ }
12
+
13
+ function getRect(root: HTMLElement, el: HTMLElement): DOMRect {
14
+ const rootRect = root.getBoundingClientRect();
15
+ const elRect = el.getBoundingClientRect();
16
+
17
+ return new DOMRect(
18
+ elRect.left - rootRect.left,
19
+ elRect.top - rootRect.top,
20
+ elRect.width,
21
+ elRect.height
22
+ );
23
+ }
24
+
25
+ export const hero = (options: HeroOptions = {}): SggoiTransition => {
26
+ const spring: SpringConfig = {
27
+ stiffness: options.spring?.stiffness ?? 300,
28
+ damping: options.spring?.damping ?? 30,
29
+ };
30
+ const timeout = options.timeout ?? 300;
31
+
32
+ // Closure variables to share state between in/out
33
+ let fromNode: HTMLElement | null = null;
34
+ let resolver: ((value: boolean) => void) | null = null;
35
+
36
+ return {
37
+ in: async (element, context) => {
38
+ const toNode = element;
39
+
40
+ // Find all hero elements in the incoming page
41
+ const heroEls = Array.from(toNode.querySelectorAll("[data-hero-key]"));
42
+ if (heroEls.length === 0) {
43
+ return {
44
+ spring,
45
+ tick: () => {}, // No hero elements, skip animation
46
+ };
47
+ }
48
+
49
+ // Wait for fromNode to be set by out transition
50
+ const hasFromNode = await new Promise<boolean>((resolve) => {
51
+ if (fromNode) {
52
+ // fromNode already set by out transition
53
+ resolve(true);
54
+ } else {
55
+ // Store resolver for out transition to call
56
+ resolver = resolve;
57
+ // Timeout fallback
58
+ setTimeout(() => {
59
+ resolver = null;
60
+ resolve(false);
61
+ }, timeout);
62
+ }
63
+ });
64
+
65
+ if (!hasFromNode || !fromNode) {
66
+ fromNode = null;
67
+ return {
68
+ spring,
69
+ tick: () => {}, // No fromNode, skip animation
70
+ };
71
+ }
72
+
73
+ // Calculate animations for matching hero elements
74
+ const heroAnimations = heroEls
75
+ .map((heroEl) => {
76
+ const key = heroEl.getAttribute("data-hero-key");
77
+ if (!key) return null;
78
+
79
+ const fromEl = getHeroEl(fromNode!, key);
80
+ if (!fromEl) return null;
81
+
82
+ const toEl = heroEl as HTMLElement;
83
+
84
+ // Calculate animation parameters
85
+ const fromRect = getRect(fromNode!, fromEl);
86
+ const toRect = getRect(toNode, toEl);
87
+ const dx = fromRect.left - toRect.left - context.scrollOffset.x;
88
+ const dy = fromRect.top - toRect.top - context.scrollOffset.y;
89
+ const dw = fromRect.width / toRect.width;
90
+ const dh = fromRect.height / toRect.height;
91
+
92
+ // Store original styles
93
+ const originalTransform = toEl.style.transform;
94
+ const originalPosition = toEl.style.position;
95
+ const originalTransformOrigin = toEl.style.transformOrigin;
96
+
97
+ return {
98
+ toEl,
99
+ dx,
100
+ dy,
101
+ dw,
102
+ dh,
103
+ originalTransform,
104
+ originalPosition,
105
+ originalTransformOrigin,
106
+ };
107
+ })
108
+ .filter(Boolean) as Array<{
109
+ toEl: HTMLElement;
110
+ dx: number;
111
+ dy: number;
112
+ dw: number;
113
+ dh: number;
114
+ originalTransform: string;
115
+ originalPosition: string;
116
+ originalTransformOrigin: string;
117
+ }>;
118
+
119
+ // Reset fromNode for next transition
120
+ fromNode = null;
121
+
122
+ if (heroAnimations.length === 0) {
123
+ return {
124
+ spring,
125
+ tick: () => {}, // No matching hero elements
126
+ };
127
+ }
128
+
129
+ return {
130
+ spring,
131
+ prepare: () => {
132
+ heroAnimations.forEach(({ toEl }) => {
133
+ toEl.style.position = "relative";
134
+ toEl.style.transformOrigin = "top left";
135
+ });
136
+ },
137
+ tick: (progress) => {
138
+ // Animate all hero elements
139
+ heroAnimations.forEach(({ toEl, dx, dy, dw, dh }) => {
140
+ toEl.style.transform = `translate(${(1 - progress) * dx}px,${(1 - progress) * dy}px) scale(${progress + (1 - progress) * dw}, ${progress + (1 - progress) * dh})`;
141
+ });
142
+ },
143
+ onEnd: () => {
144
+ // Reset all hero elements
145
+ heroAnimations.forEach(
146
+ ({
147
+ toEl,
148
+ originalTransform,
149
+ originalPosition,
150
+ originalTransformOrigin,
151
+ }) => {
152
+ toEl.style.transform = originalTransform;
153
+ toEl.style.position = originalPosition;
154
+ toEl.style.transformOrigin = originalTransformOrigin;
155
+ }
156
+ );
157
+ },
158
+ };
159
+ },
160
+ out: async (element) => {
161
+ return {
162
+ onStart: () => {
163
+ // Store fromNode
164
+ fromNode = element;
165
+
166
+ // If there's a waiting resolver, resolve it
167
+ if (resolver) {
168
+ resolver(true);
169
+ resolver = null;
170
+ }
171
+ },
172
+ prepare: (element) => {
173
+ prepareOutgoing(element);
174
+ element.style.opacity = "0";
175
+ },
176
+ };
177
+ },
178
+ };
179
+ };
@@ -0,0 +1,4 @@
1
+ export * from "./fade";
2
+ export * from "./hero";
3
+ export * from "./pinterest";
4
+ export * from "./ripple";