@rokkit/actions 1.0.0-next.128 → 1.0.0-next.129

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.
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Scroll-triggered reveal action using IntersectionObserver.
3
3
  * Applies CSS transitions (opacity + translate) when element enters viewport.
4
+ * When stagger > 0, applies reveal to each child element independently.
4
5
  *
5
6
  * @param {HTMLElement} node
6
7
  * @param {RevealOptions} [options]
@@ -10,6 +11,7 @@
10
11
  * @property {string} [distance='1.5rem'] Slide distance (CSS unit)
11
12
  * @property {number} [duration=600] Animation duration (ms)
12
13
  * @property {number} [delay=0] Delay before animation starts (ms)
14
+ * @property {number} [stagger=0] Delay increment per child in ms (0 = disabled)
13
15
  * @property {boolean} [once=true] Only animate once
14
16
  * @property {number} [threshold=0.1] IntersectionObserver threshold (0–1)
15
17
  * @property {string} [easing='cubic-bezier(0.4, 0, 0.2, 1)'] CSS easing function
@@ -18,6 +20,7 @@ export function reveal(node: HTMLElement, options?: RevealOptions): void;
18
20
  /**
19
21
  * Scroll-triggered reveal action using IntersectionObserver.
20
22
  * Applies CSS transitions (opacity + translate) when element enters viewport.
23
+ * When stagger > 0, applies reveal to each child element independently.
21
24
  */
22
25
  export type RevealOptions = {
23
26
  /**
@@ -36,6 +39,10 @@ export type RevealOptions = {
36
39
  * Delay before animation starts (ms)
37
40
  */
38
41
  delay?: number | undefined;
42
+ /**
43
+ * Delay increment per child in ms (0 = disabled)
44
+ */
45
+ stagger?: number | undefined;
39
46
  /**
40
47
  * Only animate once
41
48
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/actions",
3
- "version": "1.0.0-next.128",
3
+ "version": "1.0.0-next.129",
4
4
  "description": "Contains generic actions that can be used in various components.",
5
5
  "author": "Jerry Thomas <me@jerrythomas.name>",
6
6
  "license": "MIT",
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Scroll-triggered reveal action using IntersectionObserver.
3
3
  * Applies CSS transitions (opacity + translate) when element enters viewport.
4
+ * When stagger > 0, applies reveal to each child element independently.
4
5
  *
5
6
  * @param {HTMLElement} node
6
7
  * @param {RevealOptions} [options]
@@ -10,6 +11,7 @@
10
11
  * @property {string} [distance='1.5rem'] Slide distance (CSS unit)
11
12
  * @property {number} [duration=600] Animation duration (ms)
12
13
  * @property {number} [delay=0] Delay before animation starts (ms)
14
+ * @property {number} [stagger=0] Delay increment per child in ms (0 = disabled)
13
15
  * @property {boolean} [once=true] Only animate once
14
16
  * @property {number} [threshold=0.1] IntersectionObserver threshold (0–1)
15
17
  * @property {string} [easing='cubic-bezier(0.4, 0, 0.2, 1)'] CSS easing function
@@ -21,6 +23,7 @@ export function reveal(node, options = {}) {
21
23
  distance: '1.5rem',
22
24
  duration: 600,
23
25
  delay: 0,
26
+ stagger: 0,
24
27
  once: true,
25
28
  threshold: 0.1,
26
29
  easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
@@ -31,39 +34,80 @@ export function reveal(node, options = {}) {
31
34
  typeof window !== 'undefined' &&
32
35
  window.matchMedia('(prefers-reduced-motion: reduce)').matches
33
36
 
34
- // Set CSS custom properties for the transition
35
- node.style.setProperty('--reveal-duration', `${opts.duration}ms`)
36
- node.style.setProperty('--reveal-distance', opts.distance)
37
- node.style.setProperty('--reveal-easing', opts.easing)
37
+ const isStagger = opts.stagger > 0
38
38
 
39
- // Apply direction attribute (CSS uses this for initial translate)
40
- node.setAttribute('data-reveal', opts.direction)
39
+ function applyReveal(el) {
40
+ el.style.setProperty('--reveal-duration', `${opts.duration}ms`)
41
+ el.style.setProperty('--reveal-distance', opts.distance)
42
+ el.style.setProperty('--reveal-easing', opts.easing)
43
+ el.setAttribute('data-reveal', opts.direction)
44
+ }
45
+
46
+ function cleanReveal(el) {
47
+ el.removeAttribute('data-reveal')
48
+ el.removeAttribute('data-reveal-visible')
49
+ el.style.removeProperty('--reveal-duration')
50
+ el.style.removeProperty('--reveal-distance')
51
+ el.style.removeProperty('--reveal-easing')
52
+ el.style.removeProperty('transition-delay')
53
+ }
41
54
 
42
- if (opts.delay > 0) {
43
- node.style.transitionDelay = `${opts.delay}ms`
55
+ if (isStagger) {
56
+ Array.from(node.children).forEach((child) => applyReveal(child))
57
+ } else {
58
+ applyReveal(node)
59
+ if (opts.delay > 0) {
60
+ node.style.transitionDelay = `${opts.delay}ms`
61
+ }
44
62
  }
45
63
 
46
64
  if (reducedMotion) {
47
- node.setAttribute('data-reveal-visible', '')
65
+ if (isStagger) {
66
+ Array.from(node.children).forEach((child) => child.setAttribute('data-reveal-visible', ''))
67
+ } else {
68
+ node.setAttribute('data-reveal-visible', '')
69
+ }
48
70
  node.dispatchEvent(new CustomEvent('reveal', { detail: { visible: true } }))
49
71
  return () => {
50
- node.removeAttribute('data-reveal')
51
- node.removeAttribute('data-reveal-visible')
52
- node.style.removeProperty('--reveal-duration')
53
- node.style.removeProperty('--reveal-distance')
54
- node.style.removeProperty('--reveal-easing')
72
+ if (isStagger) {
73
+ Array.from(node.children).forEach((child) => cleanReveal(child))
74
+ } else {
75
+ cleanReveal(node)
76
+ }
55
77
  }
56
78
  }
57
79
 
80
+ let timers = []
81
+
58
82
  const observer = new IntersectionObserver(
59
83
  (entries) => {
60
84
  for (const entry of entries) {
61
85
  if (entry.isIntersecting) {
62
- node.setAttribute('data-reveal-visible', '')
86
+ if (isStagger) {
87
+ timers.forEach((t) => clearTimeout(t))
88
+ const kids = Array.from(node.children)
89
+ timers = kids.map((child, i) => {
90
+ if (!child.hasAttribute('data-reveal')) applyReveal(child)
91
+ return setTimeout(
92
+ () => child.setAttribute('data-reveal-visible', ''),
93
+ opts.delay + i * opts.stagger
94
+ )
95
+ })
96
+ } else {
97
+ node.setAttribute('data-reveal-visible', '')
98
+ }
63
99
  node.dispatchEvent(new CustomEvent('reveal', { detail: { visible: true } }))
64
100
  if (opts.once) observer.unobserve(node)
65
101
  } else if (!opts.once) {
66
- node.removeAttribute('data-reveal-visible')
102
+ if (isStagger) {
103
+ timers.forEach((t) => clearTimeout(t))
104
+ timers = []
105
+ Array.from(node.children).forEach((child) =>
106
+ child.removeAttribute('data-reveal-visible')
107
+ )
108
+ } else {
109
+ node.removeAttribute('data-reveal-visible')
110
+ }
67
111
  node.dispatchEvent(new CustomEvent('reveal', { detail: { visible: false } }))
68
112
  }
69
113
  }
@@ -74,13 +118,13 @@ export function reveal(node, options = {}) {
74
118
  observer.observe(node)
75
119
 
76
120
  return () => {
121
+ timers.forEach((t) => clearTimeout(t))
77
122
  observer.disconnect()
78
- node.removeAttribute('data-reveal')
79
- node.removeAttribute('data-reveal-visible')
80
- node.style.removeProperty('--reveal-duration')
81
- node.style.removeProperty('--reveal-distance')
82
- node.style.removeProperty('--reveal-easing')
83
- if (opts.delay > 0) node.style.removeProperty('transition-delay')
123
+ if (isStagger) {
124
+ Array.from(node.children).forEach((child) => cleanReveal(child))
125
+ } else {
126
+ cleanReveal(node)
127
+ }
84
128
  }
85
129
  })
86
130
  }