@artidev/vue-odontogram
v0.1.4
Published
Ready-to-use Vue 3 odontogram component with built-in layout, controls, styles, and `v-model` support.
Maintainers
Readme
@artidev/vue-odontogram
Ready-to-use Vue 3 odontogram component with built-in layout, controls, styles, and v-model support.
This package wraps @artidev/odontogram-core with a fully styled UI. If you want a working chart in minutes, start here.
Quick path
- Install the package and its core dependency (npm installs the core automatically).
- Import the stylesheet once in your app entry.
- Bind the component with
v-model.
npm install @artidev/vue-odontogram// app entry or any module that mounts the component
import '@artidev/vue-odontogram/style.css'<script setup lang="ts">
import { ref } from 'vue'
import Odontogram from '@artidev/vue-odontogram'
const chartState = ref({})
</script>
<template>
<Odontogram v-model="chartState" />
</template>When to use this package
| Use it when | Don't use it when |
|---|---|
| You are in a Vue 3 app (>= 3.5). | You need a non-Vue wrapper. Use @artidev/odontogram-core. |
| You want a plug-and-play chart with built-in controls. | You need raw runtime methods exposed in templates. |
| You want to ship less custom CSS. | You have very specific UI requirements — use the core and roll your own. |
| You want theming through CSS variables. | |
How it works
The component instantiates @artidev/odontogram-core against a .odontogram-root element on mount, wires the engine's onChange to update:modelValue, and tears it down on unmount. Internal markup is owned by the engine; the wrapper only provides layout, theme variables, and prop plumbing.
If the engine API changes (new init option, additional events, etc.) you only need to upgrade @artidev/odontogram-core. This wrapper follows the core's lifecycle but exposes a stable Vue surface.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| modelValue | object | {} | Full chart state controlled by the parent. Pass {} to reset |
| readonly | boolean | false | Blocks editing interactions in the chart and the control panel |
| hideControls | boolean | false | Hides the top toolbar and right-side control panel — chart only |
Emits
| Event | Payload | When |
|---|---|---|
| update:modelValue | OdontogramState | After any chart change. Full state object, not deltas |
The component does not expose per-tooth-click or toolbar-toggle events. If you need them, drop down to @artidev/odontogram-core and bind a custom wrapper.
Slots
This component does not expose slots. The toolbar and control panel are part of the engine's UI surface; customization happens through:
- CSS variables for theming (see Styling)
- The
readonlyandhideControlsprops - Asset overrides via the core
v-model flow
parent <Odontogram> core engine
│ │ │
│ setProps({ modelValue }) │ │
│ ─────────────────────────────► │ importState(value) │
│ │ ───────────────────────────► │
│ │ │
│ │ onChange(fullState) │
│ │ ◄─────────────────────────── │
│ update:modelValue(fullState) │ │
│ ◄───────────────────────────── │ │Guarantees
- The component suppresses re-import of the same object it just emitted upward, so
v-modeldoesn't loop. - Pass
{}tomodelValueto reset the chart. - Internal state changes (driven by
importState) never emitupdate:modelValueredundantly — the engine'sonChangeis gated by a microtask-based "ignored depth" counter. - When
readonlychanges, the engine is updated throughsetReadOnly().
TypeScript
<script setup lang="ts">
import { ref } from 'vue'
import type { OdontogramState } from '@artidev/odontogram-core'
import Odontogram from '@artidev/vue-odontogram'
import '@artidev/vue-odontogram/style.css'
const chartState = ref<OdontogramState>({})
</script>
<template>
<Odontogram v-model="chartState" />
</template>For the full per-tooth schema and accepted enum values, see the State schema section of the core package.
Styling
Import the stylesheet once
import '@artidev/vue-odontogram/style.css'If you skip this, the component will render but the chart grid, tooth tiles, icons, and control panel will be unstyled. The stylesheet is also what makes the engine's dynamically injected nodes (.tooth-grid, .tooth-tile, .odon-ctx-menu) render correctly.
Theme variables
Override these CSS custom properties on a parent element to theme the chart:
| Variable | Default | Description |
|---|---|---|
| --odon-line | #d7e0ec | Border color for cards, tiles, and the panel |
| --odon-text | #1e2a3a | Primary text color |
| --odon-muted | #5b6b7d | Secondary text color (hints, subtitles) |
| --odon-accent | #3b7bff | Primary accent (selected tile border, focus ring) |
| --odon-accent2 | #12b981 | Success accent (toggles in aria-pressed="true" state) |
<style scoped>
.dark-chart {
--odon-line: #2a3651;
--odon-text: #e8eef9;
--odon-muted: #98a4ba;
--odon-accent: #5a9bff;
--odon-accent2: #34d399;
}
</style>
<template>
<div class="dark-chart">
<Odontogram v-model="chartState" />
</div>
</template>Scoped vs global CSS
The component ships two style blocks internally:
<style scoped>— applies to the wrapper's own elements (toolbar, panel, card structure).<style>(global) — applies to classes the engine injects dynamically at runtime (.tooth-grid,.tooth-tile,.odon-ctx-menu, etc.).
The global block is required because the engine creates DOM nodes outside Vue's scoped-style attribute mechanism. The @artidev/vue-odontogram/style.css bundle includes both.
Examples
Basic usage
<script setup lang="ts">
import { ref } from 'vue'
import Odontogram from '@artidev/vue-odontogram'
import '@artidev/vue-odontogram/style.css'
const chartState = ref({})
</script>
<template>
<Odontogram v-model="chartState" />
</template>Read-only review screen
<script setup lang="ts">
import { ref } from 'vue'
import Odontogram from '@artidev/vue-odontogram'
import '@artidev/vue-odontogram/style.css'
const chartState = ref({
11: { toothSelection: 'implant', crownMaterial: 'zircon' },
21: { toothSelection: 'tooth-base', crownMaterial: 'broken' },
})
</script>
<template>
<Odontogram v-model="chartState" readonly />
</template>Embed chart only (no toolbar, no panel)
<template>
<Odontogram v-model="chartState" hide-controls />
</template>Reset from the parent
<script setup lang="ts">
import { ref } from 'vue'
import Odontogram from '@artidev/vue-odontogram'
import '@artidev/vue-odontogram/style.css'
const chartState = ref<Record<number, object>>({})
function clearChart() {
chartState.value = {}
}
</script>
<template>
<button type="button" @click="clearChart">Clear chart</button>
<Odontogram v-model="chartState" />
</template>Persist with localStorage
<script setup lang="ts">
import { ref, watch } from 'vue'
import Odontogram from '@artidev/vue-odontogram'
import '@artidev/vue-odontogram/style.css'
import type { OdontogramState } from '@artidev/odontogram-core'
const STORAGE_KEY = 'odontogram-state'
const chartState = ref<OdontogramState>(
JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}'),
)
watch(chartState, (state) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
}, { deep: true })
</script>
<template>
<Odontogram v-model="chartState" />
</template>Inspect the serialized state
<script setup lang="ts">
import { computed, ref } from 'vue'
import Odontogram from '@artidev/vue-odontogram'
import '@artidev/vue-odontogram/style.css'
const chartState = ref({})
const serialized = computed(() => JSON.stringify(chartState.value, null, 2))
</script>
<template>
<Odontogram v-model="chartState" />
<pre>{{ serialized }}</pre>
</template>Dark theme
<script setup lang="ts">
import { ref } from 'vue'
import Odontogram from '@artidev/vue-odontogram'
import '@artidev/vue-odontogram/style.css'
const chartState = ref({})
</script>
<style scoped>
.dark-chart {
--odon-line: #2a3651;
--odon-text: #e8eef9;
--odon-muted: #98a4ba;
--odon-accent: #5a9bff;
--odon-accent2: #34d399;
}
</style>
<template>
<div class="dark-chart">
<Odontogram v-model="chartState" />
</div>
</template>Browser support
Modern browsers with ES2020 + import.meta.url support. No IE11. Tested on the latest Chrome, Firefox, Safari, and Edge.
Peer dependencies
| Package | Version |
|---|---|
| vue | ^3.5.0 |
The runtime depends on @artidev/odontogram-core as a normal dependency. You do not need to install it explicitly.
Migration from 0.1.x
There are no API changes in 0.1.3. The only change is bumping the @artidev/odontogram-core dependency to ^0.2.0 so the consumer build emits the bundled SVG assets. If you saw 404 errors on tooth SVGs in production before, they should be gone after upgrading.
If you were on vue@^3.4.0, bump to ^3.5.0 to match this package's peer dependency.
Related packages
@artidev/odontogram-core— the underlying runtime. Read this if you need asset overrides, custom wrappers, or the full state schema.
License
MIT
