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

@artidev/odontogram-core

v0.2.1

Published

Low-level, framework-agnostic odontogram runtime for custom integrations. The runtime manages an interactive dental chart: per-tooth state, occlusal overlays, a built-in control panel, and SVG asset resolution.

Readme

@artidev/odontogram-core

Low-level, framework-agnostic odontogram runtime for custom integrations. The runtime manages an interactive dental chart: per-tooth state, occlusal overlays, a built-in control panel, and SVG asset resolution.

If you use Vue 3 and want a working chart in minutes, use @artidev/vue-odontogram instead. Use this package when you need direct runtime control, custom asset overrides, or a non-Vue wrapper.

Quick path

  1. Install the package.
  2. Provide a root element with the runtime hooks your wrapper needs.
  3. Create an instance, call init(), and listen to onChange.
npm install @artidev/odontogram-core
import { createOdontogramInstance } from '@artidev/odontogram-core'

const root = document.getElementById('odontogram-root')
if (!root) throw new Error('Missing #odontogram-root')

const instance = createOdontogramInstance(root, {
  onChange: (state) => console.log('chart changed', state),
})

await instance.init()

When to use this package

| Use it when | Don't use it when | |---|---| | You are building a custom wrapper around the runtime. | You want a drop-in component — use @artidev/vue-odontogram. | | You need to override the bundled SVG assets with your own. | You're integrating from Vue without unusual needs. | | You are integrating from a non-Vue framework (React, Svelte, vanilla). | You only need the public API — use the Vue wrapper. | | You need raw access to init() / importState() / exportState(). | |

Distribution model

This package ships ES module source code (src/) plus the bundled SVG assets (assets/). It does not ship a pre-built bundle.

Why no pre-built bundle

The runtime resolves bundled SVG files with new URL('../assets/...', import.meta.url). Bundlers like Vite, Rollup, and Webpack transform that pattern only when they process the source file — i.e. inside your app's source graph, not inside a pre-built dist/.

The previous 0.1.x line shipped a pre-built dist/index.js. Consumer bundlers skipped the URL transform and emitted no SVG files, causing silent 404 errors in production builds. 0.2.0 fixes this by removing dist/ and shipping the source directly.

Practical implications

| Scenario | Works? | |---|---| | import { ... } from '@artidev/odontogram-core' in a Vite / Rollup / Webpack / Parcel app | ✅ | | <script src="/node_modules/@artidev/odontogram-core/dist/index.js"> direct usage | ❌ No dist/ is published | | CDN with a bundler-built artifact of your app | ✅ (use your consumer-built output) | | Plain <script> in a static HTML page | ❌ Not supported |

package.json shape

The package now exposes:

  • main / module / types./src/...
  • exports["./assets/*"]./assets/* (consumers can resolve individual SVG files)
  • sideEffects: false (better tree-shaking)
  • files["src", "assets", "!**/*.test.ts"] (no tests, no scripts ship)

Verifying the build

After npm run build in your app, check the emitted URLs:

import { defaultAssets } from '@artidev/odontogram-core'

console.log(defaultAssets.teeth[11])
// Vite dev:    /node_modules/@artidev/odontogram-core/assets/teeth-svgs/11.svg
// Vite build:  /assets/teeth-svgs/11-[hash].svg

If the URL is still a relative ../assets/teeth-svgs/11.svg after build, your bundler is not processing the package source — see Troubleshooting.

A note on new URL() placement

new URL(..., import.meta.url) calls in defaultAssets are intentionally inlined at the call site rather than wrapped in a helper like assetUrl(path). Vite and Rollup only transform the pattern when both arguments are statically analyzable string literals at the call site. Wrapping the path in a helper hides it from the static analyzer and silently breaks asset emission. Do not refactor defaultAssets into a helper.

API

Exports

| Export | Kind | Description | |---|---|---| | createOdontogramInstance | function | Creates an isolated runtime instance bound to a root element | | defaultAssets | object | Bundled SVG asset URLs (teeth templates, occlusal overlays, icons) |

TypeScript types

import type {
  OdontogramState,
  SerializedToothState,
  OdontogramAssets,
  CreateOdontogramOptions,
  OdontogramInstance,
} from '@artidev/odontogram-core'

// Record<number, SerializedToothState> — keyed by FDI tooth number
type OdontogramState = Record<number, SerializedToothState>

// Free-form per-tooth data; the runtime normalizes invalid values
type SerializedToothState = Record<string, unknown>

// Asset overrides; each slot merges on top of defaultAssets
interface OdontogramAssets {
  teeth?: Partial<Record<ToothTemplateKey, string>>            // 11 | 13 | 14 | 16
  teethOcclusal?: Partial<Record<OcclusalTemplateKey, string>> // 14 | 16
  icons?: Partial<{
    occlusal: string
    wisdom: string
    bone: string
    pulp: string
    clearSelection: string
  }>
}

// Instance options
interface CreateOdontogramOptions {
  onChange?: (state: OdontogramState) => void
  readOnly?: boolean
  assets?: OdontogramAssets
}

createOdontogramInstance(rootEl, options?)

Creates one isolated runtime instance bound to rootEl.

| Argument | Type | Description | |---|---|---| | rootEl | HTMLElement | The container the runtime mounts into | | options | CreateOdontogramOptions \| (state) => void | Configuration object, or just an onChange callback shorthand |

Returns an OdontogramInstance.

Instance methods

| Method | Returns | Description | |---|---|---| | init() | Promise<void> | Builds the chart UI inside rootEl. Await before calling importState() | | destroy() | void | Removes listeners, clears grid state, detaches the DOM. Safe to call once | | importState(data) | void | Replaces the full chart state. Missing teeth fall back to defaults. Invalid enum values are normalized, not rejected | | exportState() | OdontogramState | Returns the current full serialized chart state | | setReadOnly(value) | void | Toggles readonly mode after mount. true blocks editing interactions |

State schema

OdontogramState is Record<number, SerializedToothState> keyed by FDI tooth number — the World Health Organization notation for permanent dentition: 32 teeth numbered 11..18, 21..28, 31..38, 41..48. Quadrant 1 is upper right, quadrant 4 is lower left.

Per-tooth shape

Each SerializedToothState is a free-form record the runtime normalizes on import. The runtime recognizes the following keys:

| Key | Type | Accepted values | Notes | |---|---|---|---| | toothSelection | string | none, tooth-base, milktooth, implant, tooth-crownprep, tooth-under-gum, no-tooth-after-extraction | milktooth is rejected on molars (16..18, 26..28, 36..38, 46..48) | | crownMaterial | string | natural, broken, radix, emax, zircon, metal, temporary, telescope, healing-abutment, locator, locator-prosthesis, bar, bar-prosthesis | Constrained per toothSelection (implants use a subset) | | caries | string[] | caries-subcrown, caries-buccal, caries-lingual, caries-mesial, caries-distal, caries-occlusal | Affected surfaces | | fillingMaterial | string | none, amalgam, composite, gic, temporary | | | fillingSurfaces | string[] | buccal, lingual, mesial, distal, occlusal | | | fissureSealing | boolean | | Only valid on permanent molars (16, 17, 26, 27, 36, 37, 46, 47) | | endo | string | none, endo-medical-filling, endo-filling, endo-filling-incomplete, endo-glass-pin, endo-metal-pin | | | pulpInflam | boolean | | | | endoResection | boolean | | | | parapulpalPin | boolean | | | | mobility | string | none, m1, m2, m3 | | | mods | string[] | inflammation, parodontal, mobility | | | contactMesial | boolean | | | | contactDistal | boolean | | | | bruxismWear | boolean | | Incisal wear | | bruxismNeckWear | boolean | | Cervical wear | | brokenMesial | boolean | | Broken-crown facet flags | | brokenIncisal | boolean | | | | brokenDistal | boolean | | | | extractionWound | boolean | | | | extractionPlan | boolean | | | | missingClosed | boolean | | Space closed after extraction | | bridgePillar | boolean | | | | bridgeUnit | string | none, removable, zircon, metal, temporary, bar, bar-prosthesis | Pontic material | | crownReplace | boolean | | | | crownNeeded | boolean | | | | customStates | object | | Free-form escape hatch for app-specific data | | note | string | | |

Import semantics

  • importState(data) does a full replacement, not a patch.
  • Missing teeth in data fall back to defaultState() (tooth-base + natural crown).
  • Invalid enum values are silently normalized to the closest valid value. No errors are thrown.
  • The runtime coerces boolean-like strings ("true", "yes", "si", "sí", "on") for legacy data.

Asset catalog

defaultAssets exposes:

| Key | Available values | Description | |---|---|---| | defaultAssets.teeth | 11, 13, 14, 16 | Tooth template SVG URLs (4 templates; the engine mirrors and rotates them to render the other 28 teeth) | | defaultAssets.teethOcclusal | 14, 16 | Occlusal surface overlay URLs for the two occlusal templates | | defaultAssets.icons.occlusal | string | Occlusal view toggle icon | | defaultAssets.icons.wisdom | string | Wisdom teeth visibility toggle icon | | defaultAssets.icons.bone | string | Bone/gum visibility toggle icon | | defaultAssets.icons.pulp | string | Pulp visibility toggle icon | | defaultAssets.icons.clearSelection | string | Clear selection icon |

Overriding assets

Pass a partial OdontogramAssets to createOdontogramInstance. Your override merges on top of defaultAssets, slot by slot:

import { createOdontogramInstance } from '@artidev/odontogram-core'

const instance = createOdontogramInstance(root, {
  assets: {
    teeth: {
      16: '/my-assets/custom-molar.svg',
    },
    icons: {
      wisdom: '/brand/icons/wisdom.svg',
      bone: '/brand/icons/bone.svg',
    },
  },
})

For full replacement of a slot, supply every key the runtime expects (see the table above).

You can also resolve individual SVG files directly via the package's exports map:

import tooth16 from '@artidev/odontogram-core/assets/teeth-svgs/16.svg'

This is the recommended way to ship asset overrides through a CDN or a static asset folder.

Examples

Create an instance

import { createOdontogramInstance } from '@artidev/odontogram-core'

const root = document.getElementById('odontogram-root')
if (!root) throw new Error('Missing #odontogram-root')

const instance = createOdontogramInstance(root, {
  readOnly: false,
  onChange(state) {
    console.log('chart changed', state)
  },
})

await instance.init()

Persist and restore

// Save
localStorage.setItem('odontogram', JSON.stringify(instance.exportState()))

// Restore
const saved = JSON.parse(localStorage.getItem('odontogram') ?? '{}')
instance.importState(saved)

Read-only review mode

const review = createOdontogramInstance(root, {
  readOnly: true,
  onChange(state) {
    // read-only — onChange still fires when state is imported programmatically
    auditTrail.log(state)
  },
})

await review.init()
review.importState({
  11: { toothSelection: 'implant', crownMaterial: 'zircon' },
  21: { toothSelection: 'tooth-base', crownMaterial: 'broken' },
})

Asset override

const themed = createOdontogramInstance(root, {
  assets: {
    icons: {
      wisdom: '/brand/icons/wisdom.svg',
    },
  },
})

Load a saved chart asynchronously

async function loadChart(instance: OdontogramInstance, id: string) {
  const res = await fetch(`/api/charts/${id}`)
  const state: OdontogramState = await res.json()
  instance.importState(state)
}

Troubleshooting

SVGs return 404 in production

defaultAssets.teeth[11] resolves to a relative ../assets/... URL after build. Your bundler is not processing the package source.

Fix:

  • Confirm you are not pinning dist/index.js anywhere (the file no longer ships).
  • If you use Vite, make sure @artidev/odontogram-core is not in optimizeDeps.exclude.
  • Check that the asset URLs appear in your build manifest with hashes (assets/teeth-svgs/11-[hash].svg).
  • If you are using a custom build pipeline, make sure the package is part of the source graph and not treated as pre-built.

Tree-shaking strips runtime functions

Make sure your bundler respects sideEffects: false. Webpack 5 does this by default when reading package.json#sideEffects.

TypeScript types missing

If tsc cannot find the types, ensure moduleResolution is node16, nodenext, or bundler. The package exposes .d.ts next to the .js source.

Refactor broke SVG emission

If you wrap the new URL() calls in default-assets.js in a helper function, asset emission will break silently. The path argument must be a string literal at the call site. See Distribution model.

Migration from 0.1.x

| Before (0.1.x) | After (0.2.0) | |---|---| | import { ... } from '@artidev/odontogram-core/dist/index.js' | import { ... } from '@artidev/odontogram-core' | | <script src=".../dist/index.js"> (UMD-ish direct usage) | Not supported. Build with a bundler. | | dist/ present in the published tarball | Gone — only src/ and assets/ ship. |

There are no API changes. If your bundler processed node_modules correctly in 0.1.x, you may have had broken SVG URLs without knowing it — the fix is automatic with 0.2.0.

Related packages

License

MIT