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

retend-utils

v0.0.28

Published

A collection of utility functions for Retend.

Readme

retend-utils

This package provides a collection of utility hooks and components for Retend applications.

Table of Contents

Installation

npm install retend retend-web retend-utils
# or
yarn add retend retend-web retend-utils
# or
bun add retend retend-web retend-utils

Hooks

useElementBounding

Tracks the bounding rectangle (size and position) of an HTML element reactively.

Provides a BoundingRect object where each property is a reactive Cell. These cells update automatically whenever the element's size or position changes.

Parameters:

  • elementRef: A Cell<HTMLElement | null> containing a reference to the HTML element to track.
  • options (optional): An object with the following properties:
    • reset (boolean, default: true): Reset the bounding rectangle when the element is removed from the DOM.
    • windowResize (boolean, default: true): Update when the window is resized.
    • windowScroll (boolean, default: true): Update when the window is scrolled.
    • updateTiming ('sync' | 'next-frame', default: 'sync'): Timing for updates.

Returns:

  • BoundingRect: An object containing reactive cells for each dimension (width, height, x, y, top, right, bottom, left).

Example:

import { Cell } from 'retend';
import { useElementBounding } from 'retend-utils/hooks';

function PositionTracker() {
  const trackedElement = Cell.source(null);
  const bounds = useElementBounding(trackedElement);

  return (
    <>
      <div
        ref={trackedElement}
        style="width: 100px; height: 50px; border: 1px solid black;"
      >
        Track me!
      </div>
      <p>
        Position: X={bounds.x}, Y={bounds.y}
      </p>
      <p>
        Size: Width={bounds.width}, Height={bounds.height}
      </p>
    </>
  );
}

useLiveDate

Tracks the current system date and time reactively.

Parameters:

  • interval (optional, number, default: 1000): How often to update the date and time, in milliseconds.

Returns:

  • Cell<Date>: A Cell containing the current date and time.

Example:

import { Cell } from 'retend';
import { useLiveDate } from 'retend-utils/hooks';

function CurrentDateDisplay() {
  // Update every 5 seconds
  const currentDate = useLiveDate(5000);
  const dateString = Cell.derived(() => currentDate.get().toDateString());

  return <p>Today's date: {dateString}</p>;
}

useWindowSize

Returns an object containing reactive cells that track the current window size.

Parameters:

  • None

Returns:

  • An object with two properties:
    • width: A Cell containing the current window width.
    • height: A Cell containing the current window height.

Example:

import { Cell } from 'retend';
import { useWindowSize } from 'retend-utils/hooks';

function AdaptiveLayout() {
  const { width } = useWindowSize();
  const isMobile = Cell.derived(() => width.get() < 768);

  return If(isMobile, {
    true: () => <div>Mobile layout</div>,
    false: () => <div>Desktop layout</div>,
  });
}

useOnlineStatus

Tracks the network connection status and provides a reactive cell indicating whether the user is currently online.

Parameters:

  • None

Returns:

  • Cell<boolean>: A cell that contains a boolean indicating whether the network connection is currently online.

Example:

import { useOnlineStatus } from 'retend-utils/hooks';
import { If } from 'retend';

function NetworkStatusDisplay() {
  const isOnline = useOnlineStatus();

  return (
    <p>
      {If(isOnline, {
        true: () => 'Online',
        false: () => 'Offline',
      })}
    </p>
  );
}

useLocalStorage

Creates a reactive cell synchronized with localStorage.

Parameters:

  • key (string): The key to use for storing the value in localStorage.
  • initialValue (JSONSerializable): The initial value of the cell. Must be a JSON-serializable value.

Returns:

  • Cell<JSONSerializable>: A cell that contains the current value stored in localStorage.

Example:

import { useLocalStorage } from 'retend-utils/hooks';

function ThemeSwitcher() {
  const theme = useLocalStorage('theme', 'light');

  const toggleTheme = () => {
    theme.set(theme.get() === 'light' ? 'dark' : 'light');
  };

  return (
    <>
      <button onClick={toggleTheme}>Toggle Theme</button>
      <p>Current theme: {theme}</p>
    </>
  );
}

useSessionStorage

Creates a reactive cell synchronized with sessionStorage.

Parameters:

  • key (string): The key to use for storing the value in sessionStorage.
  • initialValue (JSONSerializable): The initial value of the cell. Must be a JSON-serializable value.

Returns:

  • Cell<JSONSerializable>: A cell that contains the current value stored in sessionStorage.

Example:

import { useSessionStorage } from 'retend-utils/hooks';

function SessionCounter() {
  const count = useSessionStorage('count', 0);

  const increment = () => {
    count.set(count.get() + 1);
  };

  return (
    <>
      <button onClick={increment}>Increment</button>
      <p>Count: {count}</p>
    </>
  );
}

useDerivedValue

Creates a derived cell from either a static value or another cell. The returned cell will automatically update when the input cell changes, or remain constant if the input is a static value.

Parameters:

  • property (Cell | T): The input value or cell to derive from

Returns:

  • Cell<T>: A derived cell that reflects the current value of the input

Example:

import { Cell } from 'retend';
import { useDerivedValue } from 'retend-utils/hooks';

function ExampleComponent(props) {
  const { valueOrCell } = props;
  const derivedValue = useDerivedValue(valueOrCell);

  return <p>Current value: {derivedValue}</p>;
}

useMatchMedia

Creates a reactive cell that tracks the result of a media query.

Parameters:

  • query (string): The media query to match (e.g., (min-width: 768px)).

Returns:

  • Cell<boolean>: A cell that contains a boolean indicating whether the media query matches.

Example:

import { Cell } from 'retend';
import { useMatchMedia } from 'retend-utils/hooks';

function MyComponent() {
  const isDarkMode = useMatchMedia('(prefers-color-scheme: dark)');
  return If(isDarkMode, {
    true: () => <div>Dark mode</div>,
    false: () => <div>Light mode</div>,
  });
}

useCursorPosition

Tracks the cursor position within the window.

Parameters:

  • None

Returns:

  • CursorPosition: An object containing reactive cells for the x and y coordinates of the cursor (x, y).

Example:

import { useCursorPosition } from 'retend-utils/hooks';

function MyComponent() {
  const { x, y } = useCursorPosition();

  return (
    <div>
      Cursor Position: X: {x}, Y: {y}
    </div>
  );
}

Components

Input

A reactive input component with two-way data binding support for various HTML input types.

Props:

  • type (string): The HTML input type (e.g., "text", "number", "password", "checkbox", "date").
  • model (Cell<string | number | boolean | Date | File[]>): A reactive cell for two-way data binding. The type of the cell should match the input type.
  • ref (Cell<HTMLInputElement | null>): Optional reference to the input element.
  • ...rest: Other standard HTML input attributes.

Example:

import { Cell } from 'retend';
import { Input } from 'retend-utils/components';

function InputExample() {
  const textModel = Cell.source('');

  return (
    <div>
      <Input type="text" model={textModel} placeholder="Enter text" />
      <p>Current value: {textModel}</p>
    </div>
  );
}

FluidList

Renders a list with dynamic sizing, staggered animations, and flexible layouts, handling transitions automatically.

Props:

  • items: Required. Reactive cell containing the array of items to render.
  • Template: Required. Function returning JSX for each item. Receives { item, index, previousIndex, list }.
  • itemKey: Required for object items. Unique key for each item.
  • ref: Optional. Cell for the <ul> element reference.
  • style: Optional. Custom styles for the <ul> container.
  • direction: Optional. Item flow direction ('block'=horizontal, 'inline'=vertical). Default: 'block'.
  • staggeredDelay: Optional. Staggered animation delay per item (e.g., '50ms'). Default: '0ms'.
  • itemHeight: Optional. Fixed item height (e.g., '50px').
  • itemWidth: Optional. Fixed item width (e.g., '100px').
  • speed: Optional. Transition duration (e.g., '0.2s'). Default: '0.2s'.
  • easing: Optional. Transition easing function (e.g., 'ease-in-out'). Default: 'ease'.
  • gap: Optional. Gap between items (e.g., '10px'). Default: '0px'.
  • animateSizing: Optional. Animate item size changes. Default: false.
  • maxColumns: Optional. Max columns before wrapping (for direction: 'inline').
  • maxRows: Optional. Max rows before wrapping (for direction: 'block').
  • ...rest: Other standard <ul> attributes.

Example:

import { Cell } from 'retend';
import { FluidList, type ListTemplateProps } from 'retend-utils/components';

interface MyItem {
  id: number;
  name: string;
}

const myItems = Cell.source<MyItem[]>([
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' },
  { id: 3, name: 'Item 3' },
]);

function MyItemTemplate({ item, index }: ListTemplateProps<MyItem>) {
  // 'previousIndex' and 'list' are also available if needed
  return (
    <div style="border: 1px solid #ccc; padding: 10px;">
      <h2>{item.name}</h2>
      <p>Current Index: {index}</p>
    </div>
  );
}

function MyComponent() {
  return (
    <FluidList
      items={myItems}
      itemKey="id"
      itemHeight="100px"
      gap="10px"
      direction="inline" // Items flow horizontally, wrap vertically
      maxColumns={2} // Max 2 columns before wrapping
      speed="0.3s"
      easing="ease-in-out"
      staggeredDelay="30ms"
      Template={MyItemTemplate}
      class="my-custom-list"
    />
  );
}

UniqueTransition

A component that animates the children of a createUnique(...) component with smooth FLIP transitions. When the surrounding unique instance moves between different positions in the DOM tree, UniqueTransition animates its children from their previous position and size to their new position and size using CSS transforms.

Parameters:

  • children: Required. The children to animate when the surrounding unique instance moves.
  • Transition props:
    • transitionDuration: The duration of the transition (e.g., '300ms', '0.5s').
    • transitionTimingFunction: Optional. The easing function for the transition (e.g., 'ease-in-out'). Default: 'ease'.
    • maintainWidthDuringTransition: Optional. If true, disables horizontal scaling during transitions.
    • maintainHeightDuringTransition: Optional. If true, disables vertical scaling during transitions.

Returns:

  • The wrapped children.

Example: Persistent Video Player

A video player that smoothly animates when moving between a sidebar and a main content area.

import { createUnique } from 'retend';
import { UniqueTransition } from 'retend-utils/components';

const PersistentVideo = createUnique((props) => {
  const src = Cell.derived(() => props.get().src);
  return (
    <UniqueTransition transitionDuration="300ms">
      <VideoPlayer src={src} />
    </UniqueTransition>
  );
});

function App() {
  return (
    <div>
      <PersistentVideo id="main-video" src="/video.mp4" />
    </div>
  );
}

Example: Picture-in-Picture Transition

A video that animates between a main view and a picture-in-picture corner.

import { Cell, If, createUnique } from 'retend';
import { UniqueTransition } from 'retend-utils/components';

const styles = {
  main: { width: '640px', height: '360px' },
  pip: {
    position: 'fixed',
    bottom: '20px',
    right: '20px',
    width: '200px',
    height: '112px',
  },
};

const VideoPlayer = createUnique(() => (
  <UniqueTransition
    transitionDuration="300ms"
    transitionTimingFunction="cubic-bezier(0.4, 0, 0.2, 1)"
  >
    <video src="video.mp4" controls />
  </UniqueTransition>
));

function App() {
  const isPip = Cell.source(false);
  const isMain = Cell.derived(() => !isPip.get());
  const toggle = () => isPip.set(!isPip.get());

  return (
    <div>
      {If(isMain, () => (
        <div style={styles.main}>
          <VideoPlayer />
        </div>
      ))}
      {If(isPip, () => (
        <div style={styles.pip}>
          <VideoPlayer />
        </div>
      ))}
      <button type="button" onClick={toggle}>
        Toggle PiP
      </button>
    </div>
  );
}

Example: Returning Your Own Root Element

If you need shared sizing or layout styles for the moving content, return your own root element from renderFn.

import { Cell, createUnique } from 'retend';
import { UniqueTransition } from 'retend-utils/components';

const AnimatedCard = createUnique((props) => {
  const title = Cell.derived(() => props.get().title);
  return (
    <UniqueTransition transitionDuration="300ms">
      <div class="card">
        <h3>{title}</h3>
        <div class="content" style="overflow-y: auto; height: 100px;">
          {/* ... long content ... */}
        </div>
      </div>
    </UniqueTransition>
  );
});

function ProductGrid() {
  const items = Cell.source([
    { id: 1, title: 'Product A' },
    { id: 2, title: 'Product B' },
  ]);

  return (
    <div class="grid">
      {For(items, (item) => (
        <AnimatedCard id={`card-${item.id}`} title={item.title} />
      ))}
    </div>
  );
}