npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@propagande/utils

v0.0.45

Published

PROPAGANDE utils.

Readme

Propagande Utils Documentation

This documentation covers the core utilities and Vue composables provided by the @propagande/utils repository.

Installation

On npm, the public package is published under the propagande organization:

npm install @propagande/utils vue gsap
import { P } from "@propagande/utils";
import { useFrame } from "@propagande/utils/voir";
import { gsap } from "@propagande/utils/gsap";

GitHub Packages remains available as @propagande-studio/utils for existing projects.

Table of Contents


Shared Package

Accessible via import { P } from "@propagande/utils".

Math Utilities

| Function | Description | Usage | | --------------------------------- | --------------------------------------------------- | -------------------------------- | | Lerp(xi, xf, t) | Linear interpolation between two values. | P.Lerp(0, 100, 0.5) // 50 | | rLerp(start, end, alpha) | Rotational interpolation (useful for angles). | P.rLerp(0, Math.PI, 0.1) | | Damp(x, y, lambda, delta) | Smooth damping effect (framerate independent). | P.Damp(current, target, 8, dt) | | Clamp(x, min, max) | Constrains a number between a min and max value. | P.Clamp(150, 0, 100) // 100 | | map(x, s1, e1, s2, e2, options) | Maps a value from one range to another. | P.map(0.5, 0, 1, 0, 100) // 50 | | Round(x, decimal) | Rounds a number to a specific decimal precision. | P.Round(1.234, 2) // 1.23 | | mod(n, m) | Correct modulo operator (handles negative numbers). | P.mod(-1, 5) // 4 |

Array Utilities

| Function | Description | Usage | | ----------------------- | ------------------------------------------------ | ---------------------------------------- | | Arr.create(length) | Creates an array of N elements (range 0 to N-1). | P.Arr.create(5) // [0, 1, 2, 3, 4] | | Arr.shuffle(arr) | Shuffles an array in place. | P.Arr.shuffle([1, 2, 3]) | | Arr.rand(arr) | Returns a random element from an array. | P.Arr.rand([1, 2, 3]) | | Arr.shift(arr, idx) | Rotates array so it starts at idx. | P.Arr.shift([1, 2, 3], 1) // [2, 3, 1] | | Arr.spliceNth(arr, n) | Removes and returns every Nth element. | P.Arr.spliceNth([1, 2, 3, 4], 2) |

Random Utilities

| Function | Description | Usage | | ---------------------- | ----------------------------------------- | ------------------------ | | Rand.int(max) | Random integer between 0 and max-1. | P.Rand.int(10) | | Rand.int(min, max) | Random integer between min and max-1. | P.Rand.int(5, 10) | | Rand.range(min, max) | Random float between min and max. | P.Rand.range(0.5, 1.5) |

Class Utilities

| Function | Description | Usage | | ------------------------ | -------------------- | ------------------------------- | | Class.add(el, name) | Adds a CSS class. | P.Class.add(div, 'active') | | Class.remove(el, name) | Removes a CSS class. | P.Class.remove(div, 'active') | | Class.toggle(el, name) | Toggles a CSS class. | P.Class.toggle(div, 'active') |

Ticker

The Ticker provides a centralized requestAnimationFrame loop.

  • getTicker(): Returns the ticker instance.
  • ticker.add(fn, priority, once): Add a callback.
  • ticker.start() / ticker.stop(): Manage the loop.

Viewport

A singleton to track window size, device info, and breakpoints.

  • getViewport(): Returns the viewport instance.
  • Properties: viewport.width, height, isMobile, iOS.
  • viewport.add(fn, priority, noThrottle, immediate): Listen for resizes.

Throttle & Debounce

Useful for limiting executions on high-frequency events.

  • throttle(delay, callback, options)
  • debounce(delay, callback, options)

Voir Package

Composables designed for Vue 3 projects, handling everything from physical animations to client-side lifecycle safety.

Animation & Motion

useDampedValue

  • What it does: Smoothly interpolates a value towards a target using framerate-independent damping.
  • When to use: Smooth transitions for numbers, like cursor tracking or progress bars.
  • Example:
<script setup>
import { ref } from "vue";
import { useDampedValue } from "@propagande/utils/voir";

const target = ref(0);
const { damped } = useDampedValue({ target, lambda: 8 });

// When you change target.value, damped.value will smoothly follow.
</script>

useSpring

  • What it does: Simulates physical spring motion with stiffness and damping.
  • When to use: Playful, bouncy UI elements or realistic physics.
  • Example:
<script setup>
const target = ref(0);
const { damped: position, velocity } = useSpring({
    target,
    stiffness: 100,
    damping: 10,
});
</script>

useFrame

  • What it does: A hook that executes a callback on every ticker update (usually 60fps).
  • When to use: Custom Canvas/WebGL rendering or manual DOM animations.
  • Example:
<script setup>
import { useFrame } from "@propagande/utils/voir";

useFrame(({ et, dt }) => {
    console.log(`Elapsed: ${et}, Delta: ${dt}`);
});
</script>

[!TIP] You can pass observedElement to useFrame to only run the animation when the element is visible on screen.

GSAP Integration

useGSAPContext

  • What it does: Wraps GSAP animations in a context that is automatically cleaned up on unmount.
  • When to use: Every time you create a GSAP animation in a component.
  • Example:
<script setup>
import { useGSAPContext } from "@propagande/utils/voir";
import { gsap } from "@propagande/utils/gsap";

useGSAPContext((ctx) => {
    gsap.to(".box", { x: 100 });
});
</script>

useGSAPMatchMedia

  • What it does: Responsive GSAP animations using global breakpoints.
  • Example:
<script setup>
useGSAPMatchMedia(
    (ctx) => {
        const { isLg } = ctx.conditions;
        gsap.to(".box", { x: isLg ? 500 : 100 });
    },
    { conditions: ["isLg"] },
);
</script>

Pass only the conditions the animation uses. The callback then reruns when one of those conditions changes, without rebuilding at unrelated breakpoints. isPointerFine can be included for hover-dependent animations. Omitting conditions keeps the existing behavior of watching every configured breakpoint and pointer capability. The second argument can still be a boolean to control context revert on unmount, or an options object with revert and conditions.

Viewport & Resize

| Composable | Description | Simple Example | | -------------------- | ------------------------------------- | ------------------------------------- | | useSize() | Reactive window dimensions. | const { width, height } = useSize() | | useScroll() | Reactive window scroll position. | const scroll = useScroll() | | useIsLg() | Boolean for "Large" breakpoint. | const isMobile = !useIsLg().value | | useIsPointerFine() | Check if user has a mouse (vs touch). | const canHover = useIsPointerFine() |

Lifecycle & Utilities

useSafeClient

  • What it does: Ensures a callback only runs when the browser/DOM is available.
  • When to use: SSR apps (Nuxt) to prevent "window is not defined" errors.
const result = useSafeClient(() => {
    return new SomeBrowserPlugin();
});

createSharedComposable

  • What it does: Shares composable state in the browser until the last subscriber's scope is disposed. On the server, each call creates its own state so separate SSR requests cannot share cached values.
  • When to use: Tracking browser data like window size or scroll position while avoiding redundant event listeners. Use request-scoped state for authentication and user sessions.
  • Example:
// sharedComposable.js
import { onScopeDispose, ref } from "vue";
import { createSharedComposable, useSafeClient } from "@propagande/utils/voir";

export const useSharedScroll = createSharedComposable(() => {
    const scrollY = ref(0);
    useSafeClient(() => {
        const onScroll = () => {
            scrollY.value = window.scrollY;
        };
        window.addEventListener("scroll", onScroll, { passive: true });
        onScopeDispose(() => window.removeEventListener("scroll", onScroll));
    });
    return scrollY;
});

// ComponentA.vue & ComponentB.vue
// In the browser, both receive the same scrollY ref.
const scrollY = useSharedScroll();

During SSR, calls receive independent refs. This helper does not provide per-request session storage.

useWatchOnce

  • What it does: A watch that automatically stops itself after the first trigger.
  • Example:
useWatchOnce(someRef, (val) => {
    console.log("Triggered only once!");
});

GSAP Package

Accessible via @propagande/utils/gsap, which exports GSAP, its plugins, and animation helpers.

SplitTextAnimator

SplitTextAnimator(target, config) creates one GSAP SplitText instance and adds animate() and clearAnimations() methods. Use it when the same text needs several animations, such as an entrance and an exit in a timeline, while keeping those animations attached to the current split elements.

When SplitText splits text again, it replaces the generated line, word, or character elements. Animations created against the previous elements would keep targeting those old elements. SplitTextAnimator kills and rebuilds each registered animation with the new elements, reinserts it at its original position in its parent timeline, and preserves its paused state. It always enables autoSplit; with type including "lines", SplitText automatically re-splits when the element's width changes or fonts finish loading.

Create animations and add them to a timeline

Run this in the browser after the target element exists. Register the SplitText plugin before creating the helper.

import { gsap, SplitText, SplitTextAnimator } from "@propagande/utils/gsap";

gsap.registerPlugin(SplitText);

const split = SplitTextAnimator(".headline", {
    type: "words,lines",
    mask: "lines",
});

const timeline = gsap.timeline({ paused: true });

timeline.add(
    split.animate((self) => {
        return gsap.from(self.words, {
            yPercent: 100,
            opacity: 0,
            duration: 0.6,
            stagger: 0.05,
            ease: "power2.out",
        });
    }),
);

timeline.add(
    split.animate((self) => {
        return gsap.to(self.words, {
            yPercent: -100,
            opacity: 0,
            duration: 0.4,
            stagger: 0.03,
            ease: "power2.in",
        });
    }),
    "+=1",
);

timeline.play();

Each animate(create) callback must return a GSAP tween or timeline. It runs immediately and again after each split, so always read self.words, self.lines, or self.chars inside the callback. Create the animation there instead of caching split elements outside it. animate() returns the initial animation so it can be added to a timeline; the helper manages its replacements after subsequent splits.

Other SplitText options can be passed through config. A supplied onSplit(self) callback runs before the registered animations are rebuilt, which is useful for preparing styles on the new elements. For TypeScript consumers, the return type is also exported as SplitTextAnimatorInstance.

Replace animations or clean up

clearAnimations() kills all registered animations and removes their rebuild callbacks while keeping the split text alive. Use it before registering a new transition on the same instance:

split.clearAnimations();
split.animate((self) => gsap.to(self.words, { opacity: 1, duration: 0.3 }));

When disposing the text animation, stop its parent timeline, clear the registered animations, and restore the original markup:

timeline.kill();
split.clearAnimations();
split.revert();

License

Copyright (c) 2026 PROPAGANDE. All rights reserved. This package is proprietary; see LICENSE.

Development

Use the Bun version declared in packageManager and install with bun install --frozen-lockfile --ignore-scripts. Run bun run test and bun run build to verify changes. Use bun run audit to check dependencies when needed.