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

@sfgrp/pinpoint

v0.1.0

Published

Pinpoint is an engine for displaying hierarchical keys that identify the Earth's species.

Readme

pinpoint

Pinpoint is an engine for displaying hierarchical keys that identify the Earth's species.

Install

npm install @sfgrp/pinpoint

Usage

<script setup>
import { VuePinpoint } from '@sfgrp/pinpoint'
import '@sfgrp/pinpoint/style.css'
</script>

<template>
  <VuePinpoint
    :lead-id="103"
    base-url="https://sfg.taxonworks.org/api/v1"
    project-token="YOUR_TOKEN"
  />
</template>

Props

| Prop | Type | Required | Description | | -------------- | -------------------------------------- | -------- | ------------------------------------------------------------------------------------ | | leadId | number \| string | yes | Id of the lead the key starts on. | | baseUrl | string | yes | TaxonWorks API base url. | | projectToken | string | yes | TaxonWorks project token. | | fetcher | (id: number) => Promise<KeyResponse> | no | Load the key payload from somewhere other than a TaxonWorks server. | | allowHtml | boolean | no | Render lead text as HTML. TaxonWorks marks up taxon names, so this is on by default. Defaults to true. |

Nothing is requested until leadId, baseUrl and projectToken are all set, so binding them to values that arrive asynchronously is safe.

Events

| Event | Payload | Fired when | | --------- | ------------- | --------------------------------- | | start | — | A load begins. | | loading | — | A load begins. | | end | KeyResponse | A load resolves. | | error | Error | A load rejects. |

Exposed methods

const pinpoint = ref()

pinpoint.value.getState()        // reactive store state
pinpoint.value.setCurrentNode(9) // navigate to a node by id
pinpoint.value.isLoading         // computed<boolean>

Slots

Every slot is declared on <VuePinpoint>, no matter how deep it renders.

Figures

Three levels of control, from most to least of the built-in UI replaced.

#figures — replace the whole gallery of a lead

<VuePinpoint v-bind="config">
  <template #figures="{ figures, node, open }">
    <MyGallery
      :images="figures"
      @select="(index) => open(index)"
    />
  </template>
</VuePinpoint>

| Slot prop | Type | Description | | --------- | ------------------------- | ---------------------------------------- | | figures | Figure[] | Figures attached to this lead. | | node | Node \| null | The lead the figures belong to. | | open | (index: number) => void | Opens the viewer on the given figure. |

#figure — replace a single thumbnail

<template #figure="{ figure, index, open }">
  <img
    :src="figure.thumb"
    :alt="figure.caption ?? ''"
    @click="open"
  />
</template>

| Slot prop | Type | Description | | --------- | ----------------- | --------------------------------- | | figure | Figure | The figure to render. | | index | number | Its position in figures. | | figures | Figure[] | The full list, for context. | | node | Node \| null | The lead the figure belongs to. | | open | () => void | Opens the viewer on this figure. |

#figure-viewer — keep the thumbnails, bring your own image viewer

This is usually what you want: pinpoint renders the thumbnails and tracks which figure is active, your component does the actual viewing.

<template #figure-viewer="{ figure, figures, index, close, next, previous }">
  <MyLightbox
    :items="figures"
    :start-index="index"
    @close="close"
  />
</template>

| Slot prop | Type | Description | | ---------- | -------------- | ---------------------------------------------- | | figure | Figure | The active figure. | | figures | Figure[] | The full list, so you can offer navigation. | | index | number | Index of the active figure. | | node | Node \| null | The lead the figures belong to. | | close | () => void | Closes the viewer. | | next | () => void | Advances to the next figure (wraps around). | | previous | () => void | Goes back one figure (wraps around). |

The slot only renders once a thumbnail has been activated, so there is no need to guard on visibility yourself.

Other slots

| Slot | Slot props | Replaces | | -------------------------- | --------------------------------- | ------------------------------------- | | title | { title, metadata } | The key title. | | loading | { leadId } | Shown while a key loads. See below. | | error | { error } | The error message. | | text | { text, node } | Lead text, wherever it renders. | | target | { label, id, node } | The target label of a lead. | | couplet-title | { node, coupletNumber } | "Couplet N". | | previous-couplets-title | — | "Previous couplets". | | button-up-label | { node } | "Up". | | button-go-label | { node } | "Go". | | button-next-label | { node } | "Next". | | figure-next-label | — | The viewer's next arrow. | | figure-previous-label | — | The viewer's previous arrow. |

#loading

Rendered while a key is in flight, and removed once it resolves or fails. Nothing at all is rendered when the slot is not provided, so this is opt-in.

<VuePinpoint v-bind="config">
  <template #loading>
    <MySpinner />
  </template>
</VuePinpoint>

The rest of the key stays mounted underneath while loading, so an overlay works without the content flickering out. If you would rather drive the state from outside, the loading/end/error events still fire, and isLoading is available on the component instance.

The Figure type

type Figure = {
  id: number | null
  caption: string | null
  label: string | null
  position: number
  contentType: string | null
  thumb: string     // small preview
  image: string     // medium size, used by the built-in thumbnails
  original: string  // full size, project token appended
  source: unknown   // the untouched API payload
}

source is the escape hatch: anything the adapters do not normalize is still reachable from your own viewer.

Using a data source other than TaxonWorks

fetcher replaces the request for the key payload. It receives the lead id and must resolve with the raw payload, in the same shape the TaxonWorks endpoint returns.

<VuePinpoint
  :lead-id="1"
  base-url="https://sfg.taxonworks.org/api/v1"
  project-token="YOUR_TOKEN"
  :fetcher="(id) => fetch(`/keys/${id}.json`).then((r) => r.json())"
/>

baseUrl and projectToken are still required with a fetcher: the API returns figure paths relative to the server (/api/v1/images/…), and the adapters need both to turn them into fetchable urls.

Development

npm run dev        # demo app
npm test           # unit + component tests
npm run typecheck  # vue-tsc
npm run build:lib  # library bundle + type declarations