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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@solid-primitives/history

v0.1.5

Published

Primitives for managing undo/redo history in Solid.

Downloads

512

Readme

@solid-primitives/history

turborepo size version stage

Primitives for managing undo/redo history in Solid.

Installation

npm install @solid-primitives/history
# or
yarn add @solid-primitives/history
# or
pnpm add @solid-primitives/history

createUndoHistory

Creates an undo history from a reactive source for going back and forth between state snapshots.

How to use it

createUndoHistory takes two arguments:

  • source - A function or an array thereof that tracks the state to be restored, and returns a callback to restore it.
  • options - Configuration object. Optional.
    • limit - The maximum number of history states. Defaults to 100.
import { createUndoHistory } from "@solid-primitives/history";

const [count, setCount] = createSignal(0);

const history = createUndoHistory(() => {
  // track the changes to the state (and clone if you need to)
  const v = count();
  // return a callback to set the state back to the tracked value
  return () => setCount(v);
});

// undo the last change
history.undo();

// redo the last change
history.redo();

// check if there are any changes to undo/redo with .canUndo() and .canRedo()
return (
  <>
    <button disabled={!history.canUndo()} onClick={history.undo}>
      Undo
    </button>
    <button disabled={!history.canRedo()} onClick={history.redo}>
      Redo
    </button>
  </>
);

Observing stores

Stores have many independent points of updates, so you can choose how and what to track.

But for the most part, you may want to track and copy the whole store value.

Copying is important so that the history points are not affected by future mutations to the store.

const [state, setState] = createStore({ a: 0, b: 0 });

const history = createUndoHistory(() => {
  // track and clone the whole state
  const copy = JSON.parse(JSON.stringify(state));
  // reconcile the state back to the tracked value
  return () => setState(reconcile(copy));
});

To use stricturedClone instead of JSON.parse(JSON.stringify()), you can use the trackStore utility from @solid-primitives/deep:

import { trackStore } from "@solid-primitives/deep";

const history = createUndoHistory(() => {
  // track any update to the store
  trackStore(state);
  // clone the object underneath the store
  const copy = stricturedClone(unwrap(state));
  // reconcile the state back to the tracked value
  return () => setState(reconcile(copy));
});

To clone only the parts of the store that changed, you can use the captureStoreUpdates utility from @solid-primitives/deep. This is useful for large stores where you want to avoid unnecessary cloning and reconciliation.

The code for this example you'll find in the source code of the DEMO.

Observing multiple sources

You can track as many signals in the source callback as you want. Then any updates will create a point in history for all of them.

const [a, setA] = createSignal(0);
const [b, setB] = createSignal(0);

const history = createUndoHistory(() => {
  const aVal = a();
  const bVal = b();
  return () => {
    setA(aVal);
    setB(bVal);
  };
});

// set them both at the same time, to only create one point in history
batch(() => {
  setA(1);
  setB(1);
});

Observing multiple independent sources

If you want to track multiple independent sources, you can pass an array of source functions to createUndoHistory.

This way the undo history will still be shared, but the individual source and setter functions will be called when needed, instead of all at once. This is useful for tracking multiple stores where you want to avoid unnecessary cloning and reconciliation.

const [a, setA] = createSignal(0);
const [b, setB] = createSignal(0);

const history = createUndoHistory([
  () => {
    const aVal = a();
    return () => setA(aVal);
  },
  () => {
    const bVal = b();
    return () => setB(bVal);
  },
]);

// e.g.
setA(1);
history.undo(); // will only call setA(0)

Changing souces or clearing history

If you want to change what sources get tracked, or clear the history, you can wrap the createUndoHistory call in a createMemo:

Example of clearing the history:

const [trackClear, clear] = createSignal(undefined, { equals: false });

const history = createMemo(() => {
  // track what should rerun the memo
  trackClear();
  return createUndoHistory(/* ... */);
});

// history is now a signal
history().undo();

// clear the history
clear();

Example of changing the source:

const [a, setA] = createSignal(0);
const [b, setB] = createSignal(0);
const [useA, setUseA] = createSignal(true);

const history = createMemo(() =>
  createUndoHistory(
    useA()
      ? () => {
          const aVal = a();
          return () => setA(aVal);
        }
      : () => {
          const bVal = b();
          return () => setB(bVal);
        },
  ),
);

Pause and resume

To pause listening to changes, or batch multiple changes into one history point, you can create additional signal indicating whether to track changes or not. And then decide when to capture the state.

const [count, setCount] = createSignal(0);
const [tracking, setTracking] = createSignal(true);

const history = createUndoHistory(() => {
  if (tracking()) {
    const v = count();
    return () => setCount(v);
  }
});

setCount(1); // will create a point in history

setTracking(false); // disable tracking

setCount(2); // will NOT create a point in history
setCount(3); // will NOT create a point in history

setTracking(true); // enable tracking, and create a point in history for the last change

history.undo(); // will set count to 1
history.undo(); // will set count to 0

history.redo(); // will set count to 1
history.redo(); // will set count to 3

Changelog

See CHANGELOG.md