@kmanueldc/vue-odontogram 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +720 -0
- package/THIRD_PARTY_NOTICES.md +37 -0
- package/dist/catalogs/nts-peru.d.ts +277 -0
- package/dist/components/ConditionLabels.vue.d.ts +40 -0
- package/dist/components/FindingPrimitives.vue.d.ts +6 -0
- package/dist/components/Odontogram.vue.d.ts +131 -0
- package/dist/components/OdontogramTooltip.vue.d.ts +82 -0
- package/dist/components/SurfaceDiagrams.vue.d.ts +33 -0
- package/dist/components/SurfaceGuide.vue.d.ts +47 -0
- package/dist/components/Tooth.vue.d.ts +56 -0
- package/dist/components/ToothInspector.vue.d.ts +57 -0
- package/dist/composables/useOdontogram.d.ts +43 -0
- package/dist/composables/useToothSelection.d.ts +11 -0
- package/dist/data/index.d.ts +1 -0
- package/dist/data/permanent-horizontal.d.ts +8 -0
- package/dist/data/permanent.d.ts +8 -0
- package/dist/data/primary-horizontal.d.ts +19 -0
- package/dist/data/primary.d.ts +14 -0
- package/dist/index.d.ts +26 -0
- package/dist/types/findings.d.ts +154 -0
- package/dist/types/geometry.d.ts +18 -0
- package/dist/types/odontogram.d.ts +119 -0
- package/dist/types/surfaces.d.ts +21 -0
- package/dist/utils/anchors.d.ts +37 -0
- package/dist/utils/compositions.d.ts +41 -0
- package/dist/utils/dentition-layout.d.ts +27 -0
- package/dist/utils/finding-records.d.ts +82 -0
- package/dist/utils/findings.d.ts +74 -0
- package/dist/utils/labels.d.ts +4 -0
- package/dist/utils/layout.d.ts +19 -0
- package/dist/utils/mixed-layout.d.ts +20 -0
- package/dist/utils/navigation.d.ts +11 -0
- package/dist/utils/notation.d.ts +26 -0
- package/dist/utils/numbers.d.ts +28 -0
- package/dist/utils/primary-horizontal-layout.d.ts +13 -0
- package/dist/utils/primary-layout.d.ts +34 -0
- package/dist/utils/quadrants.d.ts +6 -0
- package/dist/utils/state-marks.d.ts +35 -0
- package/dist/utils/surfaces.d.ts +118 -0
- package/dist/utils/svg-geometry.d.ts +36 -0
- package/dist/utils/tooth-frames.d.ts +51 -0
- package/dist/vue-odontogram.css +1 -0
- package/dist/vue-odontogram.js +3202 -0
- package/dist/vue-odontogram.umd.cjs +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { OdontogramLayout } from './layout';
|
|
2
|
+
/** Dentitions that have an SVG composition available to the component. */
|
|
3
|
+
export type RenderableDentition = 'permanent' | 'primary' | 'mixed';
|
|
4
|
+
/**
|
|
5
|
+
* Only these combinations may select a dataset and a composition internally.
|
|
6
|
+
* Every listed combination has its own geometry and layout definition.
|
|
7
|
+
*/
|
|
8
|
+
export type SupportedDentitionLayout = {
|
|
9
|
+
dentition: RenderableDentition;
|
|
10
|
+
layout: OdontogramLayout;
|
|
11
|
+
};
|
|
12
|
+
export interface DentitionLayoutResolution {
|
|
13
|
+
requested: SupportedDentitionLayout;
|
|
14
|
+
resolved: SupportedDentitionLayout;
|
|
15
|
+
fellBack: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const supportedLayoutsByDentition: {
|
|
18
|
+
readonly permanent: readonly ["arch", "horizontal"];
|
|
19
|
+
readonly primary: readonly ["arch", "horizontal"];
|
|
20
|
+
readonly mixed: readonly ["arch", "horizontal"];
|
|
21
|
+
};
|
|
22
|
+
export declare function isLayoutSupported(dentition: RenderableDentition, layout: OdontogramLayout): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Resolves a public request into a combination that has its own dataset and
|
|
25
|
+
* composition. Primary horizontal became available with its independent data.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveDentitionLayout(dentition: RenderableDentition, layout: OdontogramLayout): DentitionLayoutResolution;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { FindingCatalog, FindingDefinition, FindingScope, FindingStatus, FindingTone, OdontogramFinding } from '../types/findings';
|
|
2
|
+
import { type ToothSurface } from '../types/surfaces';
|
|
3
|
+
/**
|
|
4
|
+
* Helpers to read and edit the `findings` list tooth by tooth, e.g. from a
|
|
5
|
+
* panel that shows what is recorded on each surface. They never mutate their
|
|
6
|
+
* input and hold no clinical rules: the app decides what may be recorded.
|
|
7
|
+
*/
|
|
8
|
+
/** A finding of the list, as it applies to one tooth. */
|
|
9
|
+
export interface ToothFindingEntry {
|
|
10
|
+
/** Position in the `findings` list; `removeSurfaceFinding` takes it back. */
|
|
11
|
+
index: number;
|
|
12
|
+
finding: OdontogramFinding;
|
|
13
|
+
/** Catalog entry; undefined for unknown codes. */
|
|
14
|
+
definition?: FindingDefinition;
|
|
15
|
+
scope: FindingScope | 'unknown';
|
|
16
|
+
}
|
|
17
|
+
/** Everything recorded on one tooth. */
|
|
18
|
+
export interface ToothRecord {
|
|
19
|
+
toothId: string;
|
|
20
|
+
/** Surface findings on each surface, in list order. */
|
|
21
|
+
surfaces: Record<ToothSurface, ToothFindingEntry[]>;
|
|
22
|
+
/**
|
|
23
|
+
* Every other finding that includes the tooth: whole-tooth symbols, findings
|
|
24
|
+
* between two teeth, spans whose range covers it and unknown codes.
|
|
25
|
+
*/
|
|
26
|
+
tooth: ToothFindingEntry[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Groups the findings of one tooth by surface. Surface findings (`fill`,
|
|
30
|
+
* `outline`) go to each surface they cover, or to all five when they list no
|
|
31
|
+
* surfaces; every other finding that includes the tooth goes to `tooth`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getToothRecord(toothId: string, findings: readonly OdontogramFinding[], catalog?: FindingCatalog): ToothRecord;
|
|
34
|
+
/** A surface finding to record on one tooth. */
|
|
35
|
+
export interface SurfaceFindingInput {
|
|
36
|
+
code: string;
|
|
37
|
+
toothId: string;
|
|
38
|
+
surfaces: ToothSurface[];
|
|
39
|
+
status?: FindingStatus;
|
|
40
|
+
tone?: FindingTone;
|
|
41
|
+
color?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Records a surface finding. When the tooth already has a finding with the
|
|
45
|
+
* same code, status, tone and color on it alone, its surfaces are merged into
|
|
46
|
+
* that one; otherwise a new finding is appended.
|
|
47
|
+
*/
|
|
48
|
+
export declare function addSurfaceFinding(findings: readonly OdontogramFinding[], input: SurfaceFindingInput): OdontogramFinding[];
|
|
49
|
+
/** A finding to record on one tooth: on some surfaces, or on the whole tooth. */
|
|
50
|
+
export interface ToothFindingInput {
|
|
51
|
+
code: string;
|
|
52
|
+
toothId: string;
|
|
53
|
+
/** Surfaces, for surface findings (`fill`, `outline`). */
|
|
54
|
+
surfaces?: ToothSurface[];
|
|
55
|
+
status?: FindingStatus;
|
|
56
|
+
tone?: FindingTone;
|
|
57
|
+
color?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Records a finding on one tooth. Surface findings go through
|
|
61
|
+
* `addSurfaceFinding` (merging surfaces into the same kind of finding); any
|
|
62
|
+
* other finding is appended for the whole tooth.
|
|
63
|
+
*/
|
|
64
|
+
export declare function addFinding(findings: readonly OdontogramFinding[], input: ToothFindingInput, catalog?: FindingCatalog): OdontogramFinding[];
|
|
65
|
+
/** Which finding to remove, from a `ToothRecord` entry. */
|
|
66
|
+
export interface SurfaceFindingRemoval {
|
|
67
|
+
/** `ToothFindingEntry.index` of the finding in the same list. */
|
|
68
|
+
index: number;
|
|
69
|
+
toothId: string;
|
|
70
|
+
/**
|
|
71
|
+
* Surface to clear, for surface findings only; without it the finding is
|
|
72
|
+
* removed from the whole tooth (the way to remove any other finding).
|
|
73
|
+
*/
|
|
74
|
+
surface?: ToothSurface;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Removes a finding from one surface of a tooth, or from the whole tooth.
|
|
78
|
+
* Other teeth of the same finding keep it unchanged: a finding on several
|
|
79
|
+
* teeth is split so only this tooth loses the surface. A finding without
|
|
80
|
+
* surfaces counts as covering all five.
|
|
81
|
+
*/
|
|
82
|
+
export declare function removeSurfaceFinding(findings: readonly OdontogramFinding[], { index, toothId, surface }: SurfaceFindingRemoval): OdontogramFinding[];
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { FindingCatalog, FindingDefinition, FindingScope, FindingStatus, FindingSymbol, FindingTone, OdontogramFinding } from '../types/findings';
|
|
2
|
+
import { type ToothSurface } from '../types/surfaces';
|
|
3
|
+
import { type SurfaceDiagram, type SurfaceDiagramLayout } from './surfaces';
|
|
4
|
+
import { type Box } from './svg-geometry';
|
|
5
|
+
import { type ToothFrames } from './tooth-frames';
|
|
6
|
+
/**
|
|
7
|
+
* A drawing instruction, in viewBox coordinates, in the finding color:
|
|
8
|
+
* paths and circles are stroked, areas and text are filled.
|
|
9
|
+
*/
|
|
10
|
+
export type FindingPrimitive = {
|
|
11
|
+
type: 'path';
|
|
12
|
+
d: string;
|
|
13
|
+
} | {
|
|
14
|
+
type: 'area';
|
|
15
|
+
d: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: 'circle';
|
|
18
|
+
cx: number;
|
|
19
|
+
cy: number;
|
|
20
|
+
r: number;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'text';
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
text: string;
|
|
26
|
+
fontSize: number;
|
|
27
|
+
};
|
|
28
|
+
/** A finding ready to draw. */
|
|
29
|
+
export interface RenderedFinding {
|
|
30
|
+
/** Stable key: finding index and, for single-tooth findings, the tooth. */
|
|
31
|
+
key: string;
|
|
32
|
+
code: string;
|
|
33
|
+
name: string;
|
|
34
|
+
status: FindingStatus;
|
|
35
|
+
/** Resolved default color; `color`, when set, overrides it. */
|
|
36
|
+
tone: FindingTone;
|
|
37
|
+
color?: string;
|
|
38
|
+
/** Every tooth the finding covers (a span includes its whole range). */
|
|
39
|
+
teeth: string[];
|
|
40
|
+
/** Surfaces drawn by a surface finding, in `toothSurfaces` order. */
|
|
41
|
+
surfaces?: ToothSurface[];
|
|
42
|
+
strokeWidth: number;
|
|
43
|
+
primitives: FindingPrimitive[];
|
|
44
|
+
/** Area covered by the drawing. */
|
|
45
|
+
box: Box;
|
|
46
|
+
}
|
|
47
|
+
export interface FindingLayout {
|
|
48
|
+
findings: RenderedFinding[];
|
|
49
|
+
/** Findings that could not be drawn, and why. */
|
|
50
|
+
issues: string[];
|
|
51
|
+
}
|
|
52
|
+
export declare function getFindingScope(symbol: FindingSymbol): FindingScope;
|
|
53
|
+
/**
|
|
54
|
+
* Default color of a finding: its own tone, else red while planned and blue
|
|
55
|
+
* once done, else the catalog tone.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveFindingTone(finding: Pick<OdontogramFinding, 'status' | 'tone'>, definition: Pick<FindingDefinition, 'tone'>): FindingTone;
|
|
58
|
+
/**
|
|
59
|
+
* Lays out findings on the visible teeth. Unknown codes, hidden teeth,
|
|
60
|
+
* invalid tooth groups and surface findings without surface diagrams are
|
|
61
|
+
* skipped and reported in `issues`.
|
|
62
|
+
*
|
|
63
|
+
* With `surfaceLayout`, surface findings are drawn on its diagrams and
|
|
64
|
+
* vestibular marks are placed past them.
|
|
65
|
+
*/
|
|
66
|
+
export declare function layoutFindings(findings: readonly OdontogramFinding[], catalog: FindingCatalog, { frames, order, fontSize, gap }: ToothFrames, surfaceLayout?: SurfaceDiagramLayout): FindingLayout;
|
|
67
|
+
/**
|
|
68
|
+
* Draws the surface findings of one tooth on a given diagram, e.g. the
|
|
69
|
+
* enlarged one of a surface guide. Strips and drawing order match the chart.
|
|
70
|
+
* Findings of other teeth and non-surface findings are ignored.
|
|
71
|
+
*/
|
|
72
|
+
export declare function layoutToothSurfaceFindings(findings: readonly OdontogramFinding[], catalog: FindingCatalog, diagram: SurfaceDiagram): RenderedFinding[];
|
|
73
|
+
/** Miniature of a finding symbol for legends, in a `0 0 16 16` viewBox. */
|
|
74
|
+
export declare function getFindingIcon(symbol: FindingSymbol): FindingPrimitive[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { OdontogramLabels, OdontogramLabelsInput } from '../types/odontogram';
|
|
2
|
+
export declare const defaultOdontogramLabels: Readonly<OdontogramLabels>;
|
|
3
|
+
/** Merges partial overrides, including nested maps, over the defaults. */
|
|
4
|
+
export declare function resolveOdontogramLabels(labels?: OdontogramLabelsInput): OdontogramLabels;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Presentation-only layouts for an odontogram. */
|
|
2
|
+
export type OdontogramLayout = 'arch' | 'horizontal';
|
|
3
|
+
export type PermanentQuadrant = 1 | 2 | 3 | 4;
|
|
4
|
+
export interface QuadrantTransform {
|
|
5
|
+
quadrant: PermanentQuadrant;
|
|
6
|
+
transform: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* SVG geometry used to place a complete permanent odontogram.
|
|
10
|
+
*
|
|
11
|
+
* This model does not assign FDI identifiers, infer dentition, or define tooth
|
|
12
|
+
* geometry. It only describes the presentation space and group transforms.
|
|
13
|
+
*/
|
|
14
|
+
export interface LayoutDefinition {
|
|
15
|
+
viewBox: string;
|
|
16
|
+
quadrants: readonly QuadrantTransform[];
|
|
17
|
+
}
|
|
18
|
+
export declare const layoutDefinitions: Readonly<Record<OdontogramLayout, LayoutDefinition>>;
|
|
19
|
+
export declare function getLayoutDefinition(layout: OdontogramLayout): LayoutDefinition;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mixed dentition compositions, as in the NTS odontogram form: the permanent
|
|
3
|
+
* teeth keep their own composition, and the primary teeth are drawn as an
|
|
4
|
+
* inner set of rows or arches between them. No geometry is new; each primary
|
|
5
|
+
* quadrant reuses its dataset and quadrant transform, wrapped in a scale and
|
|
6
|
+
* translation that places it.
|
|
7
|
+
*/
|
|
8
|
+
export interface MixedQuadrantTransform {
|
|
9
|
+
quadrant: number;
|
|
10
|
+
transform: string;
|
|
11
|
+
}
|
|
12
|
+
export interface MixedLayoutDefinition {
|
|
13
|
+
viewBox: string;
|
|
14
|
+
permanent: readonly MixedQuadrantTransform[];
|
|
15
|
+
primary: readonly MixedQuadrantTransform[];
|
|
16
|
+
}
|
|
17
|
+
export declare const mixedLayoutDefinitions: {
|
|
18
|
+
readonly arch: MixedLayoutDefinition;
|
|
19
|
+
readonly horizontal: MixedLayoutDefinition;
|
|
20
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ToothAnchor } from './anchors';
|
|
2
|
+
export type ToothNavigationKey = 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown' | 'Home' | 'End';
|
|
3
|
+
export declare const toothNavigationKeys: ReadonlySet<string>;
|
|
4
|
+
/**
|
|
5
|
+
* Finds the tooth to focus after a navigation key, following what is on
|
|
6
|
+
* screen: left/right move along the same row, up/down jump to the closest
|
|
7
|
+
* tooth of the nearest row drawn above or below, and Home/End go to the first
|
|
8
|
+
* and last tooth of the row. A row is an arch of one dentition, so mixed
|
|
9
|
+
* charts have two per arch. Returns `undefined` when there is nowhere to go.
|
|
10
|
+
*/
|
|
11
|
+
export declare function findNavigationTarget(anchors: readonly ToothAnchor[], fromId: string, key: ToothNavigationKey): string | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ToothNotation, ToothType } from '../types/odontogram';
|
|
2
|
+
import { type NumberedDentition } from './quadrants';
|
|
3
|
+
/**
|
|
4
|
+
* Builds a tooth identifier from its dental position.
|
|
5
|
+
*
|
|
6
|
+
* Tooth IDs are always FDI, whatever notation a chart displays; use
|
|
7
|
+
* `formatToothNumber` to show them in another notation.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildToothId(dentition: NumberedDentition, quadrant: number, position: number, notation?: ToothNotation): string;
|
|
10
|
+
/** Quadrant of the patient's mouth, as used by the Palmer notation. */
|
|
11
|
+
export type PalmerQuadrant = 'upper-right' | 'upper-left' | 'lower-left' | 'lower-right';
|
|
12
|
+
/** Type of an FDI tooth, e.g. `First Molar` for 16 or `Primary Canine` for 53. */
|
|
13
|
+
export declare function getToothType(toothId: string): ToothType;
|
|
14
|
+
/** Palmer quadrant of an FDI tooth. */
|
|
15
|
+
export declare function getPalmerQuadrant(toothId: string): PalmerQuadrant;
|
|
16
|
+
/**
|
|
17
|
+
* How a notation writes an FDI tooth:
|
|
18
|
+
* - `FDI`: the ID itself (`11`, `85`).
|
|
19
|
+
* - `Universal`: `1`–`32` from the upper right third molar clockwise to the
|
|
20
|
+
* lower right third molar; primary teeth `A`–`T` in the same order.
|
|
21
|
+
* - `Palmer`: the position in its quadrant (`1`–`8`, primary `A`–`E`) with the
|
|
22
|
+
* quadrant corner. `style: 'symbol'` uses box-drawing corners (`1┘`,
|
|
23
|
+
* `└A`); `style: 'text'` writes the quadrant (`UR1`, `ULA`), which reads
|
|
24
|
+
* well aloud.
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatToothNumber(toothId: string, notation: ToothNotation, style?: 'symbol' | 'text'): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ToothNotation } from '../types/odontogram';
|
|
2
|
+
import type { ToothAnchor } from './anchors';
|
|
3
|
+
import type { OdontogramLayout } from './layout';
|
|
4
|
+
import type { Box } from './svg-geometry';
|
|
5
|
+
/** A tooth number placed next to its tooth, in viewBox coordinates. */
|
|
6
|
+
export interface ToothNumberLabel {
|
|
7
|
+
toothId: string;
|
|
8
|
+
/** The number in the chart's notation, without the Palmer corner. */
|
|
9
|
+
text: string;
|
|
10
|
+
/** Center of the text; render with `text-anchor="middle"`. */
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
fontSize: number;
|
|
14
|
+
/** Palmer quadrant corner drawn around the text, as a stroked path. */
|
|
15
|
+
corner?: string;
|
|
16
|
+
/** Approximate area covered by the text and corner. */
|
|
17
|
+
box: Box;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Places each tooth number on the occlusal side of its tooth: perpendicular
|
|
21
|
+
* to the arch on its inner side in `arch`, and between both rows in
|
|
22
|
+
* `horizontal`. The
|
|
23
|
+
* font size follows the teeth, so every layout keeps the same proportion.
|
|
24
|
+
*
|
|
25
|
+
* `chart` is the full composition viewBox, so positions stay stable when a
|
|
26
|
+
* half of the chart is hidden.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getToothNumberLabels(anchors: readonly ToothAnchor[], chart: Box, layout: OdontogramLayout, notation?: ToothNotation): ToothNumberLabel[];
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PrimaryQuadrantTransform } from './primary-layout';
|
|
2
|
+
/**
|
|
3
|
+
* Presentation-only composition for the provisional horizontal primary
|
|
4
|
+
* dataset. Its transforms are independent from permanent and primary-arch
|
|
5
|
+
* layouts.
|
|
6
|
+
*/
|
|
7
|
+
export interface PrimaryHorizontalLayoutDefinition {
|
|
8
|
+
layout: 'horizontal';
|
|
9
|
+
viewBox: string;
|
|
10
|
+
quadrants: readonly PrimaryQuadrantTransform[];
|
|
11
|
+
provisional: true;
|
|
12
|
+
}
|
|
13
|
+
export declare const primaryHorizontalLayoutDefinition: PrimaryHorizontalLayoutDefinition;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PrimaryToothType } from '../types/geometry';
|
|
2
|
+
/** FDI quadrants used by primary (deciduous) dentition. */
|
|
3
|
+
export type PrimaryQuadrant = 5 | 6 | 7 | 8;
|
|
4
|
+
export interface PrimaryQuadrantTransform {
|
|
5
|
+
quadrant: PrimaryQuadrant;
|
|
6
|
+
transform: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Provisional arch composition for the five pediatric SVG approximations.
|
|
10
|
+
*
|
|
11
|
+
* The 461-unit height is intentionally independent from the permanent arch's
|
|
12
|
+
* 694-unit viewBox. It keeps every lower-arch spacing intact while leaving
|
|
13
|
+
* more than 30 units between the upper and lower second molars.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Mirrors an upper primary quadrant across the horizontal axis of its SVG
|
|
17
|
+
* workspace. It preserves every path's size and curvature, yielding the
|
|
18
|
+
* corresponding mandibular quadrant.
|
|
19
|
+
*/
|
|
20
|
+
export declare function mirrorPrimaryQuadrant(width: number, height: number, mirrorHorizontally?: boolean): string;
|
|
21
|
+
/**
|
|
22
|
+
* Presentation offsets applied to each provisional primary shape inside its
|
|
23
|
+
* quadrant. They never modify the SVG paths and, because quadrant groups are
|
|
24
|
+
* mirrored, the same local offset is valid for Q5, Q6, Q8 and Q7.
|
|
25
|
+
*/
|
|
26
|
+
export declare const primaryArchToothTransforms: Readonly<Partial<Record<PrimaryToothType, string>>>;
|
|
27
|
+
export interface PrimaryArchLayoutDefinition {
|
|
28
|
+
layout: 'arch';
|
|
29
|
+
viewBox: string;
|
|
30
|
+
quadrants: readonly PrimaryQuadrantTransform[];
|
|
31
|
+
toothTransforms: Readonly<Partial<Record<PrimaryToothType, string>>>;
|
|
32
|
+
provisional: true;
|
|
33
|
+
}
|
|
34
|
+
export declare const primaryArchLayoutDefinition: PrimaryArchLayoutDefinition;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DentalArch, DentalSide, Dentition } from '../types/odontogram';
|
|
2
|
+
/** Dentitions whose FDI quadrant is unambiguous for a single tooth. */
|
|
3
|
+
export type NumberedDentition = Exclude<Dentition, 'mixed'>;
|
|
4
|
+
/** Returns the FDI quadrant for a dentition, arch and patient side. */
|
|
5
|
+
export declare function getQuadrant(dentition: NumberedDentition, arch: DentalArch, side: DentalSide): number;
|
|
6
|
+
export declare function isQuadrantForDentition(dentition: NumberedDentition, quadrant: number): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { OdontogramToothStates, ToothState } from '../types/odontogram';
|
|
2
|
+
import type { ToothAnchor } from './anchors';
|
|
3
|
+
import type { Box } from './svg-geometry';
|
|
4
|
+
export interface Line {
|
|
5
|
+
x1: number;
|
|
6
|
+
y1: number;
|
|
7
|
+
x2: number;
|
|
8
|
+
y2: number;
|
|
9
|
+
}
|
|
10
|
+
/** An "X" across an extracted tooth. */
|
|
11
|
+
export interface CrossMark {
|
|
12
|
+
kind: 'cross';
|
|
13
|
+
toothId: string;
|
|
14
|
+
lines: [Line, Line];
|
|
15
|
+
strokeWidth: number;
|
|
16
|
+
}
|
|
17
|
+
/** A threaded implant fixture over an implant tooth. */
|
|
18
|
+
export interface ImplantMark {
|
|
19
|
+
kind: 'implant';
|
|
20
|
+
toothId: string;
|
|
21
|
+
/** Fixture body. */
|
|
22
|
+
body: Box;
|
|
23
|
+
/** Threads across the body. */
|
|
24
|
+
threads: Line[];
|
|
25
|
+
strokeWidth: number;
|
|
26
|
+
}
|
|
27
|
+
export type ToothStateMark = CrossMark | ImplantMark;
|
|
28
|
+
/** States drawn without the crown: only a dashed outline remains. */
|
|
29
|
+
export declare const absentToothStates: ReadonlySet<ToothState>;
|
|
30
|
+
export declare function resolveToothState(states: OdontogramToothStates | undefined, toothId: string): ToothState;
|
|
31
|
+
/**
|
|
32
|
+
* Marks drawn over teeth whose state needs a symbol, in the layout's viewBox
|
|
33
|
+
* coordinates. Their size follows each tooth, so they fit every layout.
|
|
34
|
+
*/
|
|
35
|
+
export declare function getToothStateMarks(anchors: readonly ToothAnchor[], states: OdontogramToothStates | undefined): ToothStateMark[];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { OdontogramSurfaces, SurfaceShapeKind, ToothSurface, ToothSurfaceName } from '../types/surfaces';
|
|
2
|
+
import { type Box, type Point } from './svg-geometry';
|
|
3
|
+
import { type ToothFrames } from './tooth-frames';
|
|
4
|
+
/** One clickable surface of a diagram. */
|
|
5
|
+
export interface SurfaceShape {
|
|
6
|
+
surface: ToothSurface;
|
|
7
|
+
/** Clinical name on this tooth, e.g. `incisal` or `palatal`. */
|
|
8
|
+
name: ToothSurfaceName;
|
|
9
|
+
/** Closed polygon, in viewBox coordinates. */
|
|
10
|
+
d: string;
|
|
11
|
+
/** Corners of the polygon, in viewBox coordinates. */
|
|
12
|
+
points: Point[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A five-surface diagram drawn next to a tooth: a square split into a central
|
|
16
|
+
* occlusal square and four trapezoids. It is rotated with the tooth frame, so
|
|
17
|
+
* the vestibular side faces out of the arch and the mesial side faces the
|
|
18
|
+
* midline in every layout.
|
|
19
|
+
*/
|
|
20
|
+
export interface SurfaceDiagram {
|
|
21
|
+
toothId: string;
|
|
22
|
+
center: Point;
|
|
23
|
+
/** Side of the square. */
|
|
24
|
+
size: number;
|
|
25
|
+
/** Unit directions of the diagram: out of the arch, and toward the midline. */
|
|
26
|
+
axes: {
|
|
27
|
+
vestibular: Point;
|
|
28
|
+
mesial: Point;
|
|
29
|
+
};
|
|
30
|
+
strokeWidth: number;
|
|
31
|
+
/** In `toothSurfaces` order. */
|
|
32
|
+
surfaces: SurfaceShape[];
|
|
33
|
+
/** Area covered by the diagram, stroke included. */
|
|
34
|
+
box: Box;
|
|
35
|
+
}
|
|
36
|
+
export interface SurfaceDiagramLayout {
|
|
37
|
+
diagrams: SurfaceDiagram[];
|
|
38
|
+
/**
|
|
39
|
+
* Distance the diagrams use beyond the vestibular edge of each tooth, so
|
|
40
|
+
* vestibular marks such as abbreviations can be placed past them.
|
|
41
|
+
*/
|
|
42
|
+
reserved: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Extra space, in viewBox units, between the symbols drawn over a tooth and
|
|
46
|
+
* its diagram, and between the diagram and the marks placed past it.
|
|
47
|
+
*/
|
|
48
|
+
export declare const surfaceMargin = 3;
|
|
49
|
+
/**
|
|
50
|
+
* Clinical name of a surface on an FDI tooth: incisal instead of occlusal on
|
|
51
|
+
* incisors and canines, palatal instead of lingual on upper teeth.
|
|
52
|
+
*/
|
|
53
|
+
export declare function getSurfaceName(surface: ToothSurface, toothId: string): ToothSurfaceName;
|
|
54
|
+
/** Closed SVG path through some points. */
|
|
55
|
+
export declare function polygonPath(points: readonly Point[]): string;
|
|
56
|
+
/**
|
|
57
|
+
* Part of a convex polygon between two lines perpendicular to `axis`: the
|
|
58
|
+
* points whose projection on `axis` lies in `[from, to]`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function clipPolygon(points: readonly Point[], axis: Point, from: number, to: number): Point[];
|
|
61
|
+
/**
|
|
62
|
+
* Splits a surface into `count` equal strips and returns strip `index`. The
|
|
63
|
+
* strips run across the surface: mesial–distal on the vestibular, lingual and
|
|
64
|
+
* occlusal surfaces, vestibular–lingual on the mesial and distal ones.
|
|
65
|
+
*/
|
|
66
|
+
export declare function surfaceStrip(diagram: SurfaceDiagram, shape: SurfaceShape, index: number, count: number): Point[];
|
|
67
|
+
/**
|
|
68
|
+
* Builds the diagram of a tooth: a square (or circle) of side (diameter)
|
|
69
|
+
* `size` centered at `center`, with its vestibular side along `vestibular` and its mesial side
|
|
70
|
+
* along `mesial` (both unit vectors).
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildSurfaceDiagram(toothId: string, center: Point, axes: {
|
|
73
|
+
vestibular: Point;
|
|
74
|
+
mesial: Point;
|
|
75
|
+
}, size: number, strokeWidth?: number, shape?: SurfaceShapeKind): SurfaceDiagram;
|
|
76
|
+
/**
|
|
77
|
+
* Screen orientation of a tooth's diagram in the horizontal layout, which
|
|
78
|
+
* surface guides also use: vestibular up on upper teeth and down on lower
|
|
79
|
+
* ones, mesial toward the midline (right on the patient's right side).
|
|
80
|
+
*/
|
|
81
|
+
export declare function getSurfaceAxes(toothId: string): {
|
|
82
|
+
vestibular: Point;
|
|
83
|
+
mesial: Point;
|
|
84
|
+
};
|
|
85
|
+
/** Middle of a surface polygon, where its letter goes. */
|
|
86
|
+
export declare function surfaceCenter({ points }: Pick<SurfaceShape, 'points'>): Point;
|
|
87
|
+
/**
|
|
88
|
+
* Places one surface diagram on the vestibular side of every tooth. All
|
|
89
|
+
* diagrams share one size, which follows the teeth like the tooth numbers.
|
|
90
|
+
*
|
|
91
|
+
* Each diagram keeps clear of its tooth and of the widest symbol drawn over
|
|
92
|
+
* it (the circle of a crown). Along each row, diagrams that stick out of the
|
|
93
|
+
* curve are brought back onto it (they may then give up that extra margin,
|
|
94
|
+
* never the tooth outline), and the distances are smoothed so the diagrams
|
|
95
|
+
* follow the arch instead of jumping out next to larger teeth.
|
|
96
|
+
*/
|
|
97
|
+
export declare function getSurfaceDiagrams({ frames, order, fontSize, gap }: ToothFrames, options?: {
|
|
98
|
+
/**
|
|
99
|
+
* Brings diagrams that stick out of a curved row back onto it (see
|
|
100
|
+
* `fairDistances`). Meant for the `arch` layout; rows of `horizontal` are
|
|
101
|
+
* straight and keep every margin.
|
|
102
|
+
*/
|
|
103
|
+
fairCurve?: boolean;
|
|
104
|
+
/** Square (default) or circular diagrams. */
|
|
105
|
+
shape?: SurfaceShapeKind;
|
|
106
|
+
/**
|
|
107
|
+
* Places every diagram of a row on one line, at the level of the tooth
|
|
108
|
+
* that needs the most room; the others end up farther from their tooth.
|
|
109
|
+
* Meant for the `horizontal` layout, whose rows share one vestibular
|
|
110
|
+
* direction.
|
|
111
|
+
*/
|
|
112
|
+
alignRows?: boolean;
|
|
113
|
+
}): SurfaceDiagramLayout;
|
|
114
|
+
/**
|
|
115
|
+
* Returns a new surfaces model with one surface of a tooth toggled. Surfaces
|
|
116
|
+
* keep the `toothSurfaces` order and teeth without surfaces are removed.
|
|
117
|
+
*/
|
|
118
|
+
export declare function toggleSurface(surfaces: OdontogramSurfaces | undefined, toothId: string, surface: ToothSurface): OdontogramSurfaces;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-independent SVG geometry: path bounding boxes and transform
|
|
3
|
+
* matrices. It needs no DOM, so it also works in SSR and tests.
|
|
4
|
+
*/
|
|
5
|
+
export interface Point {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
}
|
|
9
|
+
export interface Box {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
}
|
|
15
|
+
/** Affine matrix `[a, b, c, d, e, f]`, as in SVG `matrix(a b c d e f)`. */
|
|
16
|
+
export type Matrix = readonly [number, number, number, number, number, number];
|
|
17
|
+
export declare const identityMatrix: Matrix;
|
|
18
|
+
/** Returns `left · right`: `right` is applied to points first. */
|
|
19
|
+
export declare function multiplyMatrices(left: Matrix, right: Matrix): Matrix;
|
|
20
|
+
export declare function applyMatrix([a, b, c, d, e, f]: Matrix, { x, y }: Point): Point;
|
|
21
|
+
/** Parses an SVG `transform` attribute into a single matrix. */
|
|
22
|
+
export declare function parseTransform(transform: string | undefined): Matrix;
|
|
23
|
+
/** Axis-aligned box that contains every point. */
|
|
24
|
+
export declare function boxFromPoints(points: readonly Point[]): Box;
|
|
25
|
+
/**
|
|
26
|
+
* Transforms a box and returns the box that contains the result. It is exact
|
|
27
|
+
* for translations, scales and reflections, the transforms used by layouts.
|
|
28
|
+
*/
|
|
29
|
+
export declare function transformBox(matrix: Matrix, box: Box): Box;
|
|
30
|
+
/** Exact bounding box of an SVG path `d` attribute. */
|
|
31
|
+
export declare function getPathBox(path: string): Box;
|
|
32
|
+
/**
|
|
33
|
+
* Endpoints plus curve and arc extrema of a path. The box of these points is
|
|
34
|
+
* the path's exact bounding box.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getPathExtremePoints(path: string): Point[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { DentalArch } from '../types/odontogram';
|
|
2
|
+
import type { ToothAnchor, ToothRow } from './anchors';
|
|
3
|
+
import type { OdontogramLayout } from './layout';
|
|
4
|
+
import type { Box, Point } from './svg-geometry';
|
|
5
|
+
/**
|
|
6
|
+
* Local orientation of a tooth on screen, derived from the anchors. Numbers,
|
|
7
|
+
* symbols and multi-tooth marks are placed with it, so they follow the arch
|
|
8
|
+
* in every layout.
|
|
9
|
+
*/
|
|
10
|
+
export interface ToothFrame {
|
|
11
|
+
toothId: string;
|
|
12
|
+
arch: DentalArch;
|
|
13
|
+
row: ToothRow;
|
|
14
|
+
box: Box;
|
|
15
|
+
center: Point;
|
|
16
|
+
/** Smallest side of the box; symbols scale with it. */
|
|
17
|
+
size: number;
|
|
18
|
+
/** Unit direction to the inner side: the arch's lingual side, or the other row. */
|
|
19
|
+
inner: Point;
|
|
20
|
+
/** Opposite of `inner`: the arch's vestibular side, or away from the other row. */
|
|
21
|
+
outer: Point;
|
|
22
|
+
/** Unit direction toward the other arch, on the vertical axis. */
|
|
23
|
+
occlusal: Point;
|
|
24
|
+
/** Unit direction along the arch toward the midline. */
|
|
25
|
+
mesial: Point;
|
|
26
|
+
}
|
|
27
|
+
export interface ToothFrames {
|
|
28
|
+
frames: Map<string, ToothFrame>;
|
|
29
|
+
/** Teeth of each row in arch order: right back to midline to left back. */
|
|
30
|
+
order: Map<ToothRow, string[]>;
|
|
31
|
+
/** Text size that keeps the same proportion to the teeth in every layout. */
|
|
32
|
+
fontSize: number;
|
|
33
|
+
/** Standard spacing between a tooth and what is drawn around it. */
|
|
34
|
+
gap: number;
|
|
35
|
+
}
|
|
36
|
+
export declare function normalize({ x, y }: Point): Point;
|
|
37
|
+
/** Distance from a box center to its edge along a unit direction. */
|
|
38
|
+
export declare function halfExtent(width: number, height: number, direction: Point): number;
|
|
39
|
+
/**
|
|
40
|
+
* Radius of the circle drawn around a whole tooth (e.g. a crown). It is the
|
|
41
|
+
* widest symbol drawn over a tooth, so marks placed next to the tooth keep
|
|
42
|
+
* clear of it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function toothCircleRadius(box: Box): number;
|
|
45
|
+
/** Stroke width of the symbols drawn over a tooth of a given size. */
|
|
46
|
+
export declare function toothSymbolStrokeWidth(size: number): number;
|
|
47
|
+
/**
|
|
48
|
+
* Computes the frame of every tooth. `chart` is the full composition viewBox,
|
|
49
|
+
* so frames stay stable when a half of the chart is hidden.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getToothFrames(anchors: readonly ToothAnchor[], chart: Box, layout: OdontogramLayout): ToothFrames;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.odontogram-condition-labels{display:flex;flex-wrap:wrap;gap:8px;margin:0;padding:0;font-family:sans-serif;font-size:14px;list-style:none}.odontogram-condition-labels__item{display:flex;align-items:center;gap:8px;text-transform:capitalize}.odontogram-condition-labels__item[data-state],.odontogram-condition-labels__item[data-finding]{text-transform:none}.odontogram-condition-labels__swatch{display:inline-block;width:14px;height:14px;border:2px solid;border-radius:4px}.odontogram-condition-labels__icon{width:16px;height:16px;overflow:visible}.odontogram-condition-labels__icon-tooth{stroke:var(--odontogram-stroke-color, #8a98be);stroke-width:1.5}.odontogram-condition-labels__icon line,.odontogram-condition-labels__icon .odontogram-state-mark--implant rect{stroke-width:1.5}.odontogram-condition-labels__icon-tooth--missing,.odontogram-condition-labels__icon-tooth--extracted{opacity:var(--odontogram-absent-opacity, .55)}.odontogram-condition-labels__icon-tooth--unerupted{opacity:var(--odontogram-unerupted-opacity, .45)}.odontogram-tooltip{z-index:1000;pointer-events:none;padding:6px 10px;border-radius:6px;background:var(--odontogram-tooltip-bg, rgba(0, 0, 0, .85));color:var(--odontogram-tooltip-fg, #fff);font-size:12px;line-height:1.3;white-space:nowrap;box-shadow:0 2px 8px #00000026;transition:opacity .15s ease}.odontogram-tooltip__summary{padding-left:.5rem}.odontogram-tooltip__letter{display:inline-block;min-width:1.1em;font-weight:700}.odontogram-tooltip__description{max-width:16rem;margin-bottom:2px;opacity:.8;white-space:normal}.odontogram-tooltip__arrow{position:absolute;bottom:-6px;left:50%;width:0;height:0;border-top:6px solid var(--odontogram-tooltip-bg, rgba(0, 0, 0, .85));border-right:6px solid transparent;border-left:6px solid transparent;transform:translate(-50%)}.odontogram-tooltip__arrow--below{top:-6px;bottom:auto;border-top:0;border-bottom:6px solid var(--odontogram-tooltip-bg, rgba(0, 0, 0, .85))}.odontogram{--odontogram-stroke-color: #8a98be;--odontogram-selected-color: #c6ccf8;--odontogram-selected-stroke-color: #b8c0cc;--odontogram-focus-outline-color: rgb(184 167 232 / 70%);width:100%;color:var(--odontogram-stroke-color)}.odontogram__svg{display:block;width:100%;height:auto;margin-top:.5rem;overflow:visible;-webkit-user-select:none;user-select:none;touch-action:manipulation}.odontogram__numbers,.odontogram__surface-letters,.odontogram__state-marks,.odontogram__findings{pointer-events:none}.odontogram-finding--good{color:var(--odontogram-finding-good-color, #1d4ed8)}.odontogram-finding--bad{color:var(--odontogram-finding-bad-color, #dc2626)}.odontogram-finding__stroke{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round}.odontogram-finding__area{fill:currentColor;stroke:var(--odontogram-surface-stroke-color, var(--odontogram-stroke-color));stroke-linejoin:round}.odontogram-finding--planned .odontogram-finding__stroke{stroke-dasharray:var(--odontogram-finding-planned-dasharray)}.odontogram-finding--planned .odontogram-finding__area{stroke-dasharray:none;fill-opacity:var(--odontogram-finding-planned-fill-opacity, .45)}.odontogram-condition-labels .odontogram-finding--planned .odontogram-finding__stroke{stroke-dasharray:var(--odontogram-finding-planned-dasharray, 2 1.5)}.odontogram-finding__text{fill:currentColor;font-family:inherit;font-weight:700}.odontogram-tooth--absent{opacity:var(--odontogram-absent-opacity, .55)}.odontogram-tooth--unerupted{opacity:var(--odontogram-unerupted-opacity, .45)}.odontogram-state-mark--cross line{stroke:var(--odontogram-extracted-color, #dc2626);stroke-linecap:round}.odontogram-state-mark--implant rect,.odontogram-state-mark--implant line{stroke:var(--odontogram-implant-color, #0f766e);stroke-linecap:round}.odontogram-state-mark--implant rect{fill:var(--odontogram-implant-fill, #ccfbf1)}.odontogram__number{fill:var(--odontogram-number-color, #64748b);font-family:inherit;font-variant-numeric:tabular-nums}.odontogram__number-corner{fill:none;stroke:var(--odontogram-number-color, #64748b);stroke-linecap:round;stroke-linejoin:round}.odontogram__number-corner--selected{stroke:var(--odontogram-number-selected-color, #4338ca)}.odontogram__number--selected{fill:var(--odontogram-number-selected-color, #4338ca);font-weight:700}.odontogram-surface{fill:var(--odontogram-surface-fill, #fff);stroke:var(--odontogram-surface-stroke-color, var(--odontogram-stroke-color));stroke-linejoin:round;cursor:pointer;transition:fill .15s ease-in}.odontogram:not([aria-disabled=true]) .odontogram-surfaces:not(.odontogram-surfaces--inactive) .odontogram-surface:hover{fill:var(--odontogram-surface-hover-color, var(--odontogram-selected-color))}.odontogram-surface--selected,.odontogram-surface--selected:hover{fill:var(--odontogram-surface-selected-color, #6366f1)}.odontogram-surface:focus{outline:none}.odontogram-surface:focus-visible{stroke:var(--odontogram-surface-focus-color, #4338ca);stroke-width:2px}.odontogram-surfaces--inactive{opacity:var(--odontogram-absent-opacity, .55)}.odontogram-surfaces--inactive .odontogram-surface,.odontogram[aria-disabled=true] .odontogram-surface{cursor:default}.odontogram__surface-letter{fill:var(--odontogram-surface-letter-color, #334155);font-family:inherit;font-weight:700;paint-order:stroke;stroke:#fff;stroke-width:.12em}.odontogram-surface-menu{z-index:1000;min-width:12rem;max-width:min(22rem,calc(100vw - 16px));padding:.75rem;border:1px solid var(--odontogram-surface-menu-border, #cbd5e1);border-radius:.5rem;color:var(--odontogram-surface-menu-fg, #1e293b);background:var(--odontogram-surface-menu-bg, #fff);box-shadow:0 8px 24px #0f172a2e}.odontogram__overlay{pointer-events:none}.odontogram-tooth path:nth-of-type(2){opacity:0;transition:opacity .2s ease-in}.odontogram-tooth path[data-colored=true],.odontogram-tooth--selected path:nth-of-type(2),.odontogram-tooth:not(.odontogram-tooth--disabled):hover path:nth-of-type(2){opacity:1}.odontogram-tooth--selected path:nth-of-type(2),.odontogram-tooth:not(.odontogram-tooth--disabled):hover path:nth-of-type(2){fill:var(--odontogram-selected-color)}.odontogram-tooth:focus,.odontogram-tooth:focus-visible{outline:.5px solid var(--odontogram-focus-outline-color);filter:drop-shadow(0 0 .5px rgb(221 214 254 / 60%)) drop-shadow(0 0 1.5px rgb(167 139 250 / 21%))}.odontogram-surface-guide{--odontogram-stroke-color: #8a98be;--odontogram-selected-color: #c6ccf8;display:grid;grid-template-columns:minmax(7rem,10rem) minmax(0,1fr);gap:.5rem 1rem;align-items:start;color:#1e293b;font-family:inherit}.odontogram-surface-guide__title{grid-column:1 / -1;margin:0;font-weight:700}.odontogram-surface-guide__diagram{width:100%;height:auto;overflow:visible}.odontogram-surface-guide__diagram .odontogram-surface:hover{fill:var(--odontogram-surface-hover-color, var(--odontogram-selected-color))}.odontogram-surface-guide__highlight{fill:none;stroke:var(--odontogram-surface-focus-color, #4338ca);stroke-width:2.4;stroke-linejoin:round;pointer-events:none}.odontogram-surface-guide__findings{pointer-events:none}.odontogram-surface-guide__letter{fill:var(--odontogram-surface-letter-color, #334155);font-size:10px;font-weight:700;paint-order:stroke;stroke:#fff;stroke-width:2.5px;pointer-events:none}.odontogram-surface-guide__letter--active{fill:var(--odontogram-surface-focus-color, #4338ca)}.odontogram-surface-guide__list{display:grid;grid-template-columns:repeat(auto-fill,minmax(13rem,1fr));gap:.25rem .75rem;align-content:start;margin:0;padding:0;list-style:none}.odontogram-surface-guide .odontogram-surface-guide__item{display:flex;gap:.5rem;width:100%;padding:.35rem .5rem;border:1px solid transparent;border-radius:.375rem;color:inherit;font:inherit;font-size:.875rem;text-align:left;background:none;cursor:pointer}.odontogram-surface-guide .odontogram-surface-guide__item:disabled{cursor:default;opacity:1}.odontogram-surface-guide .odontogram-surface-guide__item--active{border-color:var(--odontogram-surface-focus-color, #4338ca);background:var(--odontogram-surface-guide-active-bg, #eef2ff)}.odontogram-surface-guide__item[aria-pressed=true] .odontogram-surface-guide__badge{color:#fff;background:var(--odontogram-surface-selected-color, #6366f1)}.odontogram-surface-guide__badge{flex:none;display:inline-grid;place-items:center;width:1.5rem;height:1.5rem;border-radius:50%;font-weight:700;background:#e2e8f0}.odontogram-surface-guide__text{display:grid;gap:.1rem}.odontogram-surface-guide__findings-text{color:#475569;font-size:.8125rem}.odontogram-inspector{display:grid;gap:.75rem;color:#1e293b;font-family:inherit;font-size:.875rem}.odontogram-inspector__header{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem}.odontogram-inspector__title{font-size:1rem}.odontogram-inspector__type{color:#64748b}.odontogram-inspector__state{padding:0 .4rem;border-radius:999px;color:#b91c1c;background:#fee2e2}.odontogram-inspector__body{display:flex;flex-wrap:wrap;gap:1rem;align-items:flex-start}.odontogram-inspector__guide{flex:0 0 9rem;grid-template-columns:1fr}.odontogram-inspector__details{flex:1 1 14rem}.odontogram-inspector__guide .odontogram-surface-guide__title{display:none}.odontogram-inspector__details{display:grid;gap:.75rem}.odontogram-inspector__section{display:grid;gap:.35rem;margin:0}.odontogram-inspector__heading{margin:0;font-weight:700}.odontogram-inspector__list{display:grid;gap:.35rem;margin:0;padding:0;list-style:none}.odontogram-inspector__list li{display:flex;flex-wrap:wrap;align-items:center;gap:.35rem}.odontogram-inspector__badge{display:inline-grid;place-items:center;width:1.4rem;height:1.4rem;border-radius:50%;font-weight:700;background:#e2e8f0}.odontogram-inspector__surface-name{font-weight:600}.odontogram-inspector__entries{display:flex;flex-wrap:wrap;gap:.25rem}.odontogram-inspector__entry{display:inline-flex;align-items:center;gap:.25rem;padding:.1rem .25rem .1rem .5rem;border-radius:999px;background:#f1f5f9}.odontogram-inspector .odontogram-inspector__remove{width:1.25rem;height:1.25rem;padding:0;border:0;border-radius:50%;color:#475569;font:inherit;line-height:1;background:transparent;cursor:pointer}.odontogram-inspector .odontogram-inspector__remove:hover,.odontogram-inspector .odontogram-inspector__remove:focus-visible{color:#fff;background:#dc2626}.odontogram-inspector__empty,.odontogram-inspector__hint{margin:0;color:#64748b}.odontogram-inspector__field{display:grid;gap:.25rem}.odontogram-inspector__status{display:flex;flex-wrap:wrap;gap:.25rem .75rem;margin:0;padding:0;border:0}.odontogram-inspector__status legend{padding:0;margin-bottom:.25rem}.odontogram-inspector .odontogram-inspector__add{justify-self:start;padding:.35rem .9rem;border:1px solid var(--odontogram-surface-selected-color, #6366f1);border-radius:.375rem;color:#fff;font:inherit;background:var(--odontogram-surface-selected-color, #6366f1);cursor:pointer}.odontogram-inspector .odontogram-inspector__add:disabled{opacity:.5;cursor:not-allowed}
|