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

@dimacrossbow/target-zone-core

v0.1.0

Published

PIXI.js-based target visualization with MVVM split, layers, zones, shots, and decorations.

Readme

@dimacrossbow/target-zone-core

A flexible 2D target / scoring-zone renderer built on PIXI.js v8. The generic visual primitive behind shooting-sport apps (archery, biathlon, pneumatics, …): a configurable target image, arbitrary scoring zones, shots placed at hit coordinates, and a decoration slot for your own overlays.

Generic by design. The package renders and reports — it holds no domain logic. Scoring rules, data shapes and gameplay live in your app. Every data field is an opaque Record<string, unknown>.

Features

  • 🎯 Configurable target — any backdrop image, sized in target-local space.
  • Arbitrary zones — circles, donuts, or custom SVG-path geometry with cut-out holes. Hit-testing respects the holes (a true ring, not a disc).
  • 🔴 Shots — projectile sprites placed at hit coordinates, with per-shot color tinting via a colorable mask layer (or plain geometry).
  • 👆 Input — tap / click on the target surface (mouse + touch), with the zone under the tap reported back to you.
  • 🖼 Decoration layers — plug in MPI / group / annotation overlays through addLayer() + built-in CircleAsset / ImageAsset / TextAsset / PathAsset primitives.
  • 🎥 Camera — pan, multi-touch pinch-zoom, wheel-zoom, and object-fit: contain auto-fit with clamps.
  • ↩️ History — a generic History<ICommand> undo/redo stack plus ready-made AddShotCommand / RemoveShotCommand.
  • 💾 Serializationtarget.toJSON()Target.create(json) round-trip.
  • 🧩 PIXI-free domain subpath@dimacrossbow/target-zone-core/domain exposes the entity + event layer with zero PIXI runtime dependency, for non-PIXI renderers (see @dimacrossbow/target-zone-react-native).

Install

npm install @dimacrossbow/target-zone-core pixi.js

pixi.js is a peer dependency (^8.6.6) — install it alongside.

Quick start

import {
  SceneViewModel,
  LayerViewModel,
  Target,
  TargetViewModel,
} from '@dimacrossbow/target-zone-core';

// 1. A scene owns the PIXI Application + camera. Mount it into a DOM element.
const scene = new SceneViewModel({
  width: window.innerWidth,
  height: window.innerHeight,
  background: 0x12141a,
});
await scene.init(document.querySelector('#app')!);

window.addEventListener('resize', () =>
  scene.resize(window.innerWidth, window.innerHeight),
);

// 2. A layer groups assets by zIndex.
const layer = new LayerViewModel({ name: 'targets', zIndex: 0 });
scene.addLayer(layer);

// 3. Build a target from plain data and add it to the layer.
const circle = (cx: number, cy: number, r: number) =>
  `M ${cx},${cy} m -${r},0 a ${r},${r} 0 1,0 ${r * 2},0 a ${r},${r} 0 1,0 -${r * 2},0`;

const target = new TargetViewModel(
  Target.create({
    x: 0,
    y: 0,
    width: 320,
    height: 320,
    image: '/target.png',
    zones: [
      { name: 'ring-9', geometry: circle(160, 160, 64), holes: [circle(160, 160, 38)], data: { score: 9 } },
      { name: 'bullseye', geometry: circle(160, 160, 38), data: { score: 10 } },
    ],
    shots: [],
  }),
);
layer.addAsset(target);

// Keep the target rect fitted on resize (object-fit: contain).
scene.setContentBounds({ x: target.x, y: target.y, width: target.width, height: target.height });

// 4. Register new shots on tap — you get the local point and the zone under it.
target.events.on('surface:click', ({ x, y, zone }) => {
  target.addShot({ x, y, color: '#ff3366', image: '/arrow.png', mask: '/arrow-mask.png' });
  if (zone) console.log('scored', zone.data.score);
});

Concepts

MVVM entities

The library is split Model / View / ViewModel, with an Base → Asset entity hierarchy:

  • Model — pure state (SceneModel, LayerModel, TargetModel).
  • View — PIXI display logic (SceneView, TargetView, ZoneView, …).
  • ViewModel — orchestrates Model ↔ View, owns the event emitters and pointer wiring (SceneViewModel, TargetViewModel, …).

Compound components (Scene, Layer, Target) expose a flat ViewModel facade. Leaf entities (Zone, Shot, Projectile, and the asset primitives) are single classes that own their container and draw() directly.

Projectile vs Shot

A Projectile is a reusable template (image / mask / geometry / anchorOffset). A Shot is an instance (x / y / size / color) that references a projectile to render its visual. Many shots can share one projectile.

Render stack

Inside target.container, bottom → top:

  1. Backdrop sprite (image)
  2. Zones (hover-highlight + donut-accurate hit-testing)
  3. Shots (tapping a shot stops propagation — it inspects, doesn't add)
  4. Decoration layers (your overlays)

Events

| Emitter | Event | Fires on | | --- | --- | --- | | target.events | surface:click | Tap on empty surface or a zone — payload { x, y, zone \| null } | | target.events | zone:click | Tap on a zone (also bubbles → surface:click) | | target.events | shot:click | Tap on a shot (stops propagation — no surface:click) | | target.events | shot:added / shot:removed | Shot list mutations | | scene.events | layer:added, camera:changed | Scene / camera changes |

History (undo/redo)

import { History, AddShotCommand } from '@dimacrossbow/target-zone-core';

const history = new History({ capacity: 100 });
history.execute(new AddShotCommand(target, { x, y, color: '#ff3366' }));
history.undo();
history.redo();
history.events.on('changed', () => update(history.canUndo, history.canRedo));

ICommand is generic — implement your own commands for any app-level mutation.

Public API

Import everything from the package root — internal file paths are not part of the contract:

  • EntitiesBase, Asset, CircleAsset, ImageAsset, TextAsset, PathAsset, Projectile, Shot, Zone, Target
  • ModelsSceneModel, LayerModel, TargetModel
  • ViewsSceneView, LayerView, TargetView, ZoneView, ShotView, and the asset views
  • ViewModelsSceneViewModel, LayerViewModel, TargetViewModel, ZoneViewModel, ShotViewModel, and the asset view-models
  • HistoryHistory, AddShotCommand, RemoveShotCommand
  • UtilitiesEventEmitter, Units, UnknownUnitError

All types (ITargetData, IShotOptions, IZoneJSON, TargetEventMap, …) are exported alongside their classes.

@dimacrossbow/target-zone-core/domain

A PIXI-free subset — entities, models, EventEmitter, Units — with zero PIXI runtime dependency. Use it in environments where PIXI can't run (native, server-side data building). A few .d.ts files carry type-only PIXI references that are erased from the emitted JS, so pixi.js is only needed for type resolution.

Conventions

  • Colors are numbers (0xRRGGBB) everywhere except Shot.color, which is a string (PIXI accepts both).
  • Coordinates are target-local (0..width × 0..height). Projectile geometry (when used instead of an image) is normalized 0..1 and scaled by Shot.size.
  • data fields are always opaque Record<string, unknown>.
  • Decorations are not serialized (runtime/consumer-derived state).

Related packages

| Package | For | | --- | --- | | @dimacrossbow/target-zone-vue | Declarative Vue 3 components | | @dimacrossbow/target-zone-react-native | Expo / RN (Skia) renderer |

License

MIT © dimacrossbow