gclass-anims 1.0.0-beta.12 → 1.0.0-beta.13

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.
package/Animations.js CHANGED
@@ -222,14 +222,43 @@ export function countUp (target , delay , dur, ease){
222
222
  return gsap.timeline({ delay }).fromTo(obj , { n: start } , { n: end , duration:dur , ease:e , onUpdate: () => { target.textContent = obj.n.toFixed(decimals) } } , 0)
223
223
  }
224
224
 
225
+ // Helpers for the `fill-svg` draw-modifier. When an element carries both
226
+ // `.draw`/`.draw-split` and `.fill-svg`, the stroke is drawn first and then
227
+ // the interior fills. `fill-time-N` / `fill-ease-NAME` override the fill
228
+ // phase; otherwise the fill takes half of `dur` and reuses the draw ease.
229
+ const fillTimeOf = (el , fallback) => {
230
+ const m = [...el.classList].find(c => c.startsWith("fill-time-"))
231
+ return m ? Number(m.slice("fill-time-".length)) : fallback * 0.5
232
+ }
233
+ const fillEaseOf = (el , fallbackEase) => {
234
+ const m = [...el.classList].find(c => c.startsWith("fill-ease-"))
235
+ return m ? m.slice("fill-ease-".length) : fallbackEase
236
+ }
237
+
225
238
  // Stroke-draw reveal (strokes only — filled SVGs are deliberately out of
226
239
  // scope for now). Explicit fromTo endpoints so the animation's hidden state
227
240
  // matches this class's Config `from` metadata exactly: `.scroll-progress`
228
241
  // scrubs between those two values and `.leave`/`.scroll` reversal tweens back
229
- // into them.
242
+ // into them. When the target also carries `.fill-svg`, the interior is filled
243
+ // after the stroke finishes (draw → fill sequential timeline).
230
244
  export function drawsvg (target , delay , dur , ease){
231
245
  const e = easeOf(ease)
232
- return gsap.fromTo(target , {drawSVG:"0%"} , {ease:e , duration:dur , delay:delay , drawSVG:"100%"})
246
+ const first = gsap.utils.toArray(target)[0]
247
+ const hasFill = !!first?.classList?.contains("fill-svg")
248
+ if (!hasFill) {
249
+ return gsap.fromTo(target , {drawSVG:"0%"} , {ease:e , duration:dur , delay:delay , drawSVG:"100%"})
250
+ }
251
+ const fillDur = fillTimeOf(first , dur)
252
+ const fillEase = fillEaseOf(first , ease)
253
+ const fillTargets = gsap.utils.toArray(target).filter(el => el.classList.contains("fill-svg"))
254
+ const tl = gsap.timeline({ delay })
255
+ // Keep fill invisible while the stroke draws
256
+ if (fillTargets.length) gsap.set(fillTargets , { fillOpacity: 0 })
257
+ tl.fromTo(target , {drawSVG:"0%"} , {ease:e , duration:dur , drawSVG:"100%"})
258
+ if (fillTargets.length) {
259
+ tl.fromTo(fillTargets , {fillOpacity:0} , {ease:easeOf(fillEase) , duration:fillDur , fillOpacity:1})
260
+ }
261
+ return tl
233
262
  }
234
263
 
235
264
  // Busts a multi-segment <path> (one containing multiple "M" commands) apart
@@ -293,9 +322,15 @@ export function splitPaths (paths){
293
322
  // of `dur` proportional to its own stroke length so the pen travels at a
294
323
  // constant speed across the whole drawing. Returns a timeline, so leave /
295
324
  // scroll reversal un-draws the segments back-to-front and the engine's
296
- // onComplete hooks fire only after the final segment lands.
325
+ // onComplete hooks fire only after the final segment lands. When the source
326
+ // also carries `.fill-svg`, every resulting segment is filled together after
327
+ // the last draw segment lands (draw → fill).
297
328
  export function drawsvgSplit (target , delay , dur , ease){
298
329
  const e = easeOf(ease)
330
+ const first = gsap.utils.toArray(target)[0]
331
+ const hasFill = !!first?.classList?.contains("fill-svg")
332
+ const fillDur = hasFill ? fillTimeOf(first , dur) : 0
333
+ const fillEase = hasFill ? fillEaseOf(first , ease) : ease
299
334
  const tl = gsap.timeline({ delay })
300
335
  const paths = splitPaths(target)
301
336
  let distance = 0
@@ -303,11 +338,15 @@ export function drawsvgSplit (target , delay , dur , ease){
303
338
  // Nothing drawable (empty selection / zero-length strokes): hand back the
304
339
  // inert timeline rather than divide by zero below.
305
340
  if (!distance) return tl
341
+ if (hasFill && paths.length) gsap.set(paths , { fillOpacity: 0 })
306
342
  paths.forEach(segment => {
307
343
  tl.fromTo(segment ,
308
344
  {drawSVG:"0%"} ,
309
345
  {ease:e , duration:dur * (segment.getTotalLength() / distance) , drawSVG:"100%"})
310
346
  })
347
+ if (hasFill && paths.length) {
348
+ tl.fromTo(paths , {fillOpacity:0} , {ease:easeOf(fillEase) , duration:fillDur , fillOpacity:1})
349
+ }
311
350
  return tl
312
351
  }
313
352
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  All notable changes to `gclass-anims` will be documented in this file.
4
4
 
5
+ ## [1.0.0-beta.13] - 2026-08-27
6
+ - Added a new `.fill-svg` modifier for the `.draw` and `.draw-split` classes that fills the SVG after it has been drawn.
7
+
5
8
  ## [1.0.0-beta.12] - 2026-08-26
6
9
 
7
10
  - Fixed `scramble` with `scroll-progress` throwing `can't convert undefined to object` — `computeTo` (`Listeners.js:424`) and scrub `to` builder (`Listeners.js:867`) now guard `from` (`scramble` has no `from`).
package/Config.js CHANGED
@@ -142,6 +142,11 @@ export const animations = [
142
142
  // to. `.draw` animates its target(s) as one stroke; `.draw-split` first
143
143
  // splits multi-segment paths (paths with multiple "M" commands) into one
144
144
  // <path> per segment and draws them sequentially at constant pen speed.
145
+ // `.fill-svg` is a MODIFIER for both: add it alongside `.draw`/`.draw-split`
146
+ // to fill the interior after the stroke finishes (draw → fill). Tunables:
147
+ // `fill-time-N` (fill duration, default = 0.5× draw time) and
148
+ // `fill-ease-NAME` (default reuses draw ease). Requires a fill color on
149
+ // the element (`fill` attribute or CSS); the modifier animates `fillOpacity`.
145
150
  { sel: ".draw", text: false, from: { drawSVG: "0%" }, play: (el, delay, dur, ease) => drawsvg(el, delay, dur, ease) },
146
151
  { sel: ".draw-split", text: false, from: { drawSVG: "0%" }, play: (el, delay, dur, ease) => drawsvgSplit(el, delay, dur, ease) },
147
152
 
package/Listeners.js CHANGED
@@ -925,6 +925,24 @@ export default function initListeners() {
925
925
  } else if (typewriterSplit) {
926
926
  const parts = getParts(el, getGranularity(el))
927
927
  if (parts.length) tl.fromTo(parts, { opacity: reverse ? 1 : 0 }, { opacity: reverse ? 0 : 1, ease })
928
+ } else if (el.classList.contains("fill-svg") && (el.classList.contains("draw") || el.classList.contains("draw-split"))) {
929
+ // fill-svg modifier for draw: stroke first, then fill, sequential scrub.
930
+ // For scrub the two phases are sequential tweens so scroll maps
931
+ // draw → fill. Reverse swaps order so unfill happens before undraw.
932
+ const fillEaseCls = [...el.classList].find(c => c.startsWith("fill-ease-"))
933
+ const fillEase = fillEaseCls ? fillEaseCls.slice("fill-ease-".length) : ease
934
+ const fillTimeCls = [...el.classList].find(c => c.startsWith("fill-time-"))
935
+ const fillDur = fillTimeCls ? Number(fillTimeCls.slice("fill-time-".length)) : 0.5
936
+ const drawDur = 1
937
+ // Ensure the hidden / revealed fill state is correct before scrub starts
938
+ gsap.set(el, { fillOpacity: reverse ? 1 : 0 })
939
+ if (!reverse) {
940
+ tl.fromTo(el, { drawSVG: "0%" , ...rnd }, { drawSVG: "100%" , ...rndEnds, ease, duration: drawDur })
941
+ tl.fromTo(el, { fillOpacity: 0 }, { fillOpacity: 1, ease: fillEase, duration: fillDur })
942
+ } else {
943
+ tl.fromTo(el, { fillOpacity: 1 }, { fillOpacity: 0, ease: fillEase, duration: fillDur })
944
+ tl.fromTo(el, { drawSVG: "100%" , ...rndEnds }, { drawSVG: "0%" , ...rnd, ease, duration: drawDur })
945
+ }
928
946
  } else {
929
947
  tl.fromTo(el, { ...(reverse ? to : from), ...rnd }, { ...rndEnds, ...(reverse ? from : to), ease })
930
948
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gclass-anims",
3
- "version": "1.0.0-beta.12",
3
+ "version": "1.0.0-beta.13",
4
4
  "description": "A Tailwind-style utility layer on top of GSAP. Framework-agnostic — works in vanilla JS, React, Vue, Svelte, or any bundler.",
5
5
  "type": "module",
6
6
  "main": "index.js",