ngx-odontogram 0.1.0
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/README.md +64 -0
- package/fesm2022/ngx-odontogram.mjs +675 -0
- package/fesm2022/ngx-odontogram.mjs.map +1 -0
- package/package.json +41 -0
- package/types/ngx-odontogram.d.ts +140 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# NgxOdontogram
|
|
2
|
+
|
|
3
|
+
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0.
|
|
4
|
+
|
|
5
|
+
## Code scaffolding
|
|
6
|
+
|
|
7
|
+
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
ng generate component component-name
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
ng generate --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Building
|
|
20
|
+
|
|
21
|
+
To build the library, run:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
ng build ngx-odontogram
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
|
|
28
|
+
|
|
29
|
+
### Publishing the Library
|
|
30
|
+
|
|
31
|
+
Once the project is built, you can publish your library by following these steps:
|
|
32
|
+
|
|
33
|
+
1. Navigate to the `dist` directory:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd dist/ngx-odontogram
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
2. Run the `npm publish` command to publish your library to the npm registry:
|
|
40
|
+
```bash
|
|
41
|
+
npm publish
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Running unit tests
|
|
45
|
+
|
|
46
|
+
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
ng test
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Running end-to-end tests
|
|
53
|
+
|
|
54
|
+
For end-to-end (e2e) testing, run:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
ng e2e
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
|
61
|
+
|
|
62
|
+
## Additional Resources
|
|
63
|
+
|
|
64
|
+
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, signal, computed, HostListener, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
// Adult teeth — FDI quadrants: 1=upper-right, 2=upper-left, 3=lower-left, 4=lower-right
|
|
5
|
+
// Universal: upper-right 1-8, upper-left 9-16, lower-left 17-24, lower-right 25-32
|
|
6
|
+
const ADULT_TEETH = [
|
|
7
|
+
// Upper right (FDI 11-18, Universal 8-1)
|
|
8
|
+
{ fdi: 11, universal: 8, type: 'adult', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 8 },
|
|
9
|
+
{ fdi: 12, universal: 7, type: 'adult', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 7 },
|
|
10
|
+
{ fdi: 13, universal: 6, type: 'adult', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 6 },
|
|
11
|
+
{ fdi: 14, universal: 5, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 1, order: 5 },
|
|
12
|
+
{ fdi: 15, universal: 4, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 1, order: 4 },
|
|
13
|
+
{ fdi: 16, universal: 3, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 3, order: 3 },
|
|
14
|
+
{ fdi: 17, universal: 2, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 3, order: 2 },
|
|
15
|
+
{ fdi: 18, universal: 1, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 3, order: 1 },
|
|
16
|
+
// Upper left (FDI 21-28, Universal 9-16)
|
|
17
|
+
{ fdi: 21, universal: 9, type: 'adult', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 1 },
|
|
18
|
+
{ fdi: 22, universal: 10, type: 'adult', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 2 },
|
|
19
|
+
{ fdi: 23, universal: 11, type: 'adult', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 3 },
|
|
20
|
+
{ fdi: 24, universal: 12, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 1, order: 4 },
|
|
21
|
+
{ fdi: 25, universal: 13, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 1, order: 5 },
|
|
22
|
+
{ fdi: 26, universal: 14, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 3, order: 6 },
|
|
23
|
+
{ fdi: 27, universal: 15, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 3, order: 7 },
|
|
24
|
+
{ fdi: 28, universal: 16, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 3, order: 8 },
|
|
25
|
+
// Lower left (FDI 31-38, Universal 17-24)
|
|
26
|
+
{ fdi: 31, universal: 24, type: 'adult', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 1 },
|
|
27
|
+
{ fdi: 32, universal: 23, type: 'adult', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 2 },
|
|
28
|
+
{ fdi: 33, universal: 22, type: 'adult', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 3 },
|
|
29
|
+
{ fdi: 34, universal: 21, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 1, order: 4 },
|
|
30
|
+
{ fdi: 35, universal: 20, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 1, order: 5 },
|
|
31
|
+
{ fdi: 36, universal: 19, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 2, order: 6 },
|
|
32
|
+
{ fdi: 37, universal: 18, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 2, order: 7 },
|
|
33
|
+
{ fdi: 38, universal: 17, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 2, order: 8 },
|
|
34
|
+
// Lower right (FDI 41-48, Universal 25-32)
|
|
35
|
+
{ fdi: 41, universal: 25, type: 'adult', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 8 },
|
|
36
|
+
{ fdi: 42, universal: 26, type: 'adult', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 7 },
|
|
37
|
+
{ fdi: 43, universal: 27, type: 'adult', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 6 },
|
|
38
|
+
{ fdi: 44, universal: 28, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 1, order: 5 },
|
|
39
|
+
{ fdi: 45, universal: 29, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 1, order: 4 },
|
|
40
|
+
{ fdi: 46, universal: 30, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 2, order: 3 },
|
|
41
|
+
{ fdi: 47, universal: 31, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 2, order: 2 },
|
|
42
|
+
{ fdi: 48, universal: 32, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 2, order: 1 },
|
|
43
|
+
];
|
|
44
|
+
// Primary teeth — FDI quadrants: 5=upper-right, 6=upper-left, 7=lower-left, 8=lower-right
|
|
45
|
+
// Universal primary: A-T (letters)
|
|
46
|
+
const PRIMARY_TEETH = [
|
|
47
|
+
// Upper right (FDI 51-55, Universal E-A)
|
|
48
|
+
{ fdi: 51, universal: 'E', type: 'primary', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 5 },
|
|
49
|
+
{ fdi: 52, universal: 'D', type: 'primary', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 4 },
|
|
50
|
+
{ fdi: 53, universal: 'C', type: 'primary', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 3 },
|
|
51
|
+
{ fdi: 54, universal: 'B', type: 'primary', quadrant: 'upper-right', isAnterior: false, roots: 1, order: 2 },
|
|
52
|
+
{ fdi: 55, universal: 'A', type: 'primary', quadrant: 'upper-right', isAnterior: false, roots: 2, order: 1 },
|
|
53
|
+
// Upper left (FDI 61-65, Universal F-J)
|
|
54
|
+
{ fdi: 61, universal: 'F', type: 'primary', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 1 },
|
|
55
|
+
{ fdi: 62, universal: 'G', type: 'primary', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 2 },
|
|
56
|
+
{ fdi: 63, universal: 'H', type: 'primary', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 3 },
|
|
57
|
+
{ fdi: 64, universal: 'I', type: 'primary', quadrant: 'upper-left', isAnterior: false, roots: 1, order: 4 },
|
|
58
|
+
{ fdi: 65, universal: 'J', type: 'primary', quadrant: 'upper-left', isAnterior: false, roots: 2, order: 5 },
|
|
59
|
+
// Lower left (FDI 71-75, Universal O-K)
|
|
60
|
+
{ fdi: 71, universal: 'O', type: 'primary', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 1 },
|
|
61
|
+
{ fdi: 72, universal: 'N', type: 'primary', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 2 },
|
|
62
|
+
{ fdi: 73, universal: 'M', type: 'primary', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 3 },
|
|
63
|
+
{ fdi: 74, universal: 'L', type: 'primary', quadrant: 'lower-left', isAnterior: false, roots: 1, order: 4 },
|
|
64
|
+
{ fdi: 75, universal: 'K', type: 'primary', quadrant: 'lower-left', isAnterior: false, roots: 2, order: 5 },
|
|
65
|
+
// Lower right (FDI 81-85, Universal T-P)
|
|
66
|
+
{ fdi: 81, universal: 'T', type: 'primary', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 5 },
|
|
67
|
+
{ fdi: 82, universal: 'S', type: 'primary', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 4 },
|
|
68
|
+
{ fdi: 83, universal: 'R', type: 'primary', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 3 },
|
|
69
|
+
{ fdi: 84, universal: 'Q', type: 'primary', quadrant: 'lower-right', isAnterior: false, roots: 1, order: 2 },
|
|
70
|
+
{ fdi: 85, universal: 'P', type: 'primary', quadrant: 'lower-right', isAnterior: false, roots: 2, order: 1 },
|
|
71
|
+
];
|
|
72
|
+
const ALL_TEETH = [...ADULT_TEETH, ...PRIMARY_TEETH];
|
|
73
|
+
function getToothByFdi(fdi) {
|
|
74
|
+
return ALL_TEETH.find(t => t.fdi === fdi);
|
|
75
|
+
}
|
|
76
|
+
function getToothByUniversal(id) {
|
|
77
|
+
return ALL_TEETH.find(t => t.universal === id);
|
|
78
|
+
}
|
|
79
|
+
function getTeethByQuadrant(quadrant, type = 'all') {
|
|
80
|
+
return ALL_TEETH
|
|
81
|
+
.filter(t => t.quadrant === quadrant && (type === 'all' || t.type === type))
|
|
82
|
+
.sort((a, b) => a.order - b.order);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const TOOLTIP_W = 52;
|
|
86
|
+
const TOOLTIP_H = 16;
|
|
87
|
+
const TOOLTIP_R = 4;
|
|
88
|
+
class ToothComponent {
|
|
89
|
+
definition = input.required(...(ngDevMode ? [{ debugName: "definition" }] : /* istanbul ignore next */ []));
|
|
90
|
+
conditions = input([], ...(ngDevMode ? [{ debugName: "conditions" }] : /* istanbul ignore next */ []));
|
|
91
|
+
notation = input('fdi', ...(ngDevMode ? [{ debugName: "notation" }] : /* istanbul ignore next */ []));
|
|
92
|
+
interactive = input(true, ...(ngDevMode ? [{ debugName: "interactive" }] : /* istanbul ignore next */ []));
|
|
93
|
+
showTooltip = input(true, ...(ngDevMode ? [{ debugName: "showTooltip" }] : /* istanbul ignore next */ []));
|
|
94
|
+
x = input(0, ...(ngDevMode ? [{ debugName: "x" }] : /* istanbul ignore next */ []));
|
|
95
|
+
y = input(0, ...(ngDevMode ? [{ debugName: "y" }] : /* istanbul ignore next */ []));
|
|
96
|
+
size = input(36, ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
97
|
+
toothClick = output();
|
|
98
|
+
surfaceClick = output();
|
|
99
|
+
toothHover = output();
|
|
100
|
+
surfaceHover = output();
|
|
101
|
+
hoveredSurface = signal(null, ...(ngDevMode ? [{ debugName: "hoveredSurface" }] : /* istanbul ignore next */ []));
|
|
102
|
+
tooltipPos = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "tooltipPos" }] : /* istanbul ignore next */ []));
|
|
103
|
+
TOOLTIP_W = TOOLTIP_W;
|
|
104
|
+
TOOLTIP_H = TOOLTIP_H;
|
|
105
|
+
TOOLTIP_R = TOOLTIP_R;
|
|
106
|
+
onHostLeave() {
|
|
107
|
+
this.hoveredSurface.set(null);
|
|
108
|
+
}
|
|
109
|
+
isAnterior = computed(() => this.definition().isAnterior, ...(ngDevMode ? [{ debugName: "isAnterior" }] : /* istanbul ignore next */ []));
|
|
110
|
+
isUpperArch = computed(() => this.definition().quadrant.startsWith('upper'), ...(ngDevMode ? [{ debugName: "isUpperArch" }] : /* istanbul ignore next */ []));
|
|
111
|
+
label = computed(() => {
|
|
112
|
+
const d = this.definition();
|
|
113
|
+
return this.notation() === 'fdi' ? String(d.fdi) : String(d.universal);
|
|
114
|
+
}, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
115
|
+
crownRect = computed(() => ({ x: this.x(), y: this.y(), w: this.size(), h: this.size() }), ...(ngDevMode ? [{ debugName: "crownRect" }] : /* istanbul ignore next */ []));
|
|
116
|
+
surfaces = computed(() => {
|
|
117
|
+
const x = this.x();
|
|
118
|
+
const y = this.y();
|
|
119
|
+
const s = this.size();
|
|
120
|
+
const cx = x + s / 2;
|
|
121
|
+
const cy = y + s / 2;
|
|
122
|
+
const inset = s * 0.3;
|
|
123
|
+
const tl = `${x},${y}`;
|
|
124
|
+
const tr = `${x + s},${y}`;
|
|
125
|
+
const bl = `${x},${y + s}`;
|
|
126
|
+
const br = `${x + s},${y + s}`;
|
|
127
|
+
const ci = `${cx - inset},${cy - inset}`;
|
|
128
|
+
const cii = `${cx + inset},${cy - inset}`;
|
|
129
|
+
const ciii = `${cx + inset},${cy + inset}`;
|
|
130
|
+
const civ = `${cx - inset},${cy + inset}`;
|
|
131
|
+
return {
|
|
132
|
+
buccal: `${tl} ${tr} ${cii} ${ci}`,
|
|
133
|
+
lingual: `${bl} ${br} ${ciii} ${civ}`,
|
|
134
|
+
mesial: `${tl} ${bl} ${civ} ${ci}`,
|
|
135
|
+
distal: `${tr} ${br} ${ciii} ${cii}`,
|
|
136
|
+
center: { x: cx - inset, y: cy - inset, w: inset * 2, h: inset * 2 },
|
|
137
|
+
};
|
|
138
|
+
}, ...(ngDevMode ? [{ debugName: "surfaces" }] : /* istanbul ignore next */ []));
|
|
139
|
+
rootRects = computed(() => {
|
|
140
|
+
const x = this.x();
|
|
141
|
+
const y = this.y();
|
|
142
|
+
const s = this.size();
|
|
143
|
+
const n = this.definition().roots;
|
|
144
|
+
const rootH = s * 0.7;
|
|
145
|
+
const rootW = Math.min(s / n - 3, 10);
|
|
146
|
+
const totalW = n * rootW + (n - 1) * 3;
|
|
147
|
+
const startX = x + (s - totalW) / 2;
|
|
148
|
+
const rootY = this.isUpperArch() ? y - rootH : y + s;
|
|
149
|
+
return Array.from({ length: n }, (_, i) => ({
|
|
150
|
+
x: startX + i * (rootW + 3),
|
|
151
|
+
y: rootY,
|
|
152
|
+
width: rootW,
|
|
153
|
+
height: rootH,
|
|
154
|
+
}));
|
|
155
|
+
}, ...(ngDevMode ? [{ debugName: "rootRects" }] : /* istanbul ignore next */ []));
|
|
156
|
+
labelPos = computed(() => {
|
|
157
|
+
const s = this.size();
|
|
158
|
+
const rootH = s * 0.7;
|
|
159
|
+
return {
|
|
160
|
+
x: this.x() + s / 2,
|
|
161
|
+
y: this.isUpperArch()
|
|
162
|
+
? this.y() - rootH - 4
|
|
163
|
+
: this.y() + s + rootH + 12,
|
|
164
|
+
};
|
|
165
|
+
}, ...(ngDevMode ? [{ debugName: "labelPos" }] : /* istanbul ignore next */ []));
|
|
166
|
+
getSurface(surface) {
|
|
167
|
+
return this.conditions().find(c => c.surface === surface);
|
|
168
|
+
}
|
|
169
|
+
getSurfaceColor(surface) {
|
|
170
|
+
return this.getSurface(surface)?.color;
|
|
171
|
+
}
|
|
172
|
+
buildEvent(mouseEvent, surface) {
|
|
173
|
+
const d = this.definition();
|
|
174
|
+
const id = this.notation() === 'fdi' ? d.fdi : d.universal;
|
|
175
|
+
return { toothId: id, toothFdi: d.fdi, toothType: d.type, surface, conditions: this.conditions(), mouseEvent };
|
|
176
|
+
}
|
|
177
|
+
onSurface(e, surface) {
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
if (!this.interactive())
|
|
180
|
+
return;
|
|
181
|
+
const ev = this.buildEvent(e, surface);
|
|
182
|
+
this.surfaceClick.emit(ev);
|
|
183
|
+
this.toothClick.emit(ev);
|
|
184
|
+
}
|
|
185
|
+
onSurfaceHover(e, surface) {
|
|
186
|
+
if (!this.interactive())
|
|
187
|
+
return;
|
|
188
|
+
const s = this.size();
|
|
189
|
+
const cx = this.x() + s / 2;
|
|
190
|
+
// position tooltip above crown for upper arch, below for lower arch
|
|
191
|
+
const tipY = this.isUpperArch()
|
|
192
|
+
? this.y() - s * 0.7 - TOOLTIP_H - 2
|
|
193
|
+
: this.y() + s + s * 0.7 + TOOLTIP_H + 2;
|
|
194
|
+
this.hoveredSurface.set(surface);
|
|
195
|
+
this.tooltipPos.set({ x: cx, y: tipY });
|
|
196
|
+
const ev = this.buildEvent(e, surface);
|
|
197
|
+
this.surfaceHover.emit(ev);
|
|
198
|
+
this.toothHover.emit(ev);
|
|
199
|
+
}
|
|
200
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ToothComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
201
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: ToothComponent, isStandalone: true, selector: "g[ngx-tooth]", inputs: { definition: { classPropertyName: "definition", publicName: "definition", isSignal: true, isRequired: true, transformFunction: null }, conditions: { classPropertyName: "conditions", publicName: "conditions", isSignal: true, isRequired: false, transformFunction: null }, notation: { classPropertyName: "notation", publicName: "notation", isSignal: true, isRequired: false, transformFunction: null }, interactive: { classPropertyName: "interactive", publicName: "interactive", isSignal: true, isRequired: false, transformFunction: null }, showTooltip: { classPropertyName: "showTooltip", publicName: "showTooltip", isSignal: true, isRequired: false, transformFunction: null }, x: { classPropertyName: "x", publicName: "x", isSignal: true, isRequired: false, transformFunction: null }, y: { classPropertyName: "y", publicName: "y", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toothClick: "toothClick", surfaceClick: "surfaceClick", toothHover: "toothHover", surfaceHover: "surfaceHover" }, host: { listeners: { "mouseleave": "onHostLeave()" } }, ngImport: i0, template: `
|
|
202
|
+
@if (isUpperArch()) {
|
|
203
|
+
@for (root of rootRects(); track $index) {
|
|
204
|
+
<svg:rect
|
|
205
|
+
[attr.x]="root.x" [attr.y]="root.y"
|
|
206
|
+
[attr.width]="root.width" [attr.height]="root.height"
|
|
207
|
+
class="ngx-tooth-root"
|
|
208
|
+
/>
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
<svg:polygon
|
|
213
|
+
[attr.points]="surfaces().buccal"
|
|
214
|
+
[attr.fill]="getSurfaceColor('buccal') || getSurfaceColor('labial') || '#fff'"
|
|
215
|
+
class="ngx-surface"
|
|
216
|
+
(click)="onSurface($event, isAnterior() ? 'labial' : 'buccal')"
|
|
217
|
+
(mouseenter)="onSurfaceHover($event, isAnterior() ? 'labial' : 'buccal')"
|
|
218
|
+
/>
|
|
219
|
+
<svg:polygon
|
|
220
|
+
[attr.points]="surfaces().lingual"
|
|
221
|
+
[attr.fill]="getSurfaceColor('lingual') || '#fff'"
|
|
222
|
+
class="ngx-surface"
|
|
223
|
+
(click)="onSurface($event, 'lingual')"
|
|
224
|
+
(mouseenter)="onSurfaceHover($event, 'lingual')"
|
|
225
|
+
/>
|
|
226
|
+
<svg:polygon
|
|
227
|
+
[attr.points]="surfaces().mesial"
|
|
228
|
+
[attr.fill]="getSurfaceColor('mesial') || '#fff'"
|
|
229
|
+
class="ngx-surface"
|
|
230
|
+
(click)="onSurface($event, 'mesial')"
|
|
231
|
+
(mouseenter)="onSurfaceHover($event, 'mesial')"
|
|
232
|
+
/>
|
|
233
|
+
<svg:polygon
|
|
234
|
+
[attr.points]="surfaces().distal"
|
|
235
|
+
[attr.fill]="getSurfaceColor('distal') || '#fff'"
|
|
236
|
+
class="ngx-surface"
|
|
237
|
+
(click)="onSurface($event, 'distal')"
|
|
238
|
+
(mouseenter)="onSurfaceHover($event, 'distal')"
|
|
239
|
+
/>
|
|
240
|
+
<svg:rect
|
|
241
|
+
[attr.x]="surfaces().center.x" [attr.y]="surfaces().center.y"
|
|
242
|
+
[attr.width]="surfaces().center.w" [attr.height]="surfaces().center.h"
|
|
243
|
+
[attr.fill]="getSurfaceColor('occlusal') || getSurfaceColor('incisal') || '#fff'"
|
|
244
|
+
class="ngx-surface"
|
|
245
|
+
(click)="onSurface($event, isAnterior() ? 'incisal' : 'occlusal')"
|
|
246
|
+
(mouseenter)="onSurfaceHover($event, isAnterior() ? 'incisal' : 'occlusal')"
|
|
247
|
+
/>
|
|
248
|
+
|
|
249
|
+
<!-- Crown outline — purely decorative, pointer-events:none lets clicks reach surfaces -->
|
|
250
|
+
<svg:rect
|
|
251
|
+
[attr.x]="crownRect().x" [attr.y]="crownRect().y"
|
|
252
|
+
[attr.width]="crownRect().w" [attr.height]="crownRect().h"
|
|
253
|
+
class="ngx-tooth-crown-outline"
|
|
254
|
+
/>
|
|
255
|
+
|
|
256
|
+
<svg:text
|
|
257
|
+
[attr.x]="labelPos().x" [attr.y]="labelPos().y"
|
|
258
|
+
class="ngx-tooth-label"
|
|
259
|
+
text-anchor="middle"
|
|
260
|
+
>{{ label() }}</svg:text>
|
|
261
|
+
|
|
262
|
+
@if (!isUpperArch()) {
|
|
263
|
+
@for (root of rootRects(); track $index) {
|
|
264
|
+
<svg:rect
|
|
265
|
+
[attr.x]="root.x" [attr.y]="root.y"
|
|
266
|
+
[attr.width]="root.width" [attr.height]="root.height"
|
|
267
|
+
class="ngx-tooth-root"
|
|
268
|
+
/>
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
<!-- Tooltip -->
|
|
273
|
+
@if (showTooltip() && hoveredSurface()) {
|
|
274
|
+
<svg:g>
|
|
275
|
+
<svg:rect
|
|
276
|
+
[attr.x]="tooltipPos().x - TOOLTIP_W / 2"
|
|
277
|
+
[attr.y]="tooltipPos().y - TOOLTIP_H / 2"
|
|
278
|
+
[attr.width]="TOOLTIP_W"
|
|
279
|
+
[attr.height]="TOOLTIP_H"
|
|
280
|
+
[attr.rx]="TOOLTIP_R"
|
|
281
|
+
class="ngx-tooltip-bg"
|
|
282
|
+
/>
|
|
283
|
+
<svg:text
|
|
284
|
+
[attr.x]="tooltipPos().x"
|
|
285
|
+
[attr.y]="tooltipPos().y + 1"
|
|
286
|
+
class="ngx-tooltip-text"
|
|
287
|
+
text-anchor="middle"
|
|
288
|
+
dominant-baseline="middle"
|
|
289
|
+
>{{ hoveredSurface() }}</svg:text>
|
|
290
|
+
</svg:g>
|
|
291
|
+
}
|
|
292
|
+
`, isInline: true, styles: [".ngx-tooth-root{fill:#f0ece3;stroke:#999;stroke-width:1}.ngx-surface{stroke:#666;stroke-width:.8;cursor:pointer;transition:fill .12s}.ngx-surface:hover{fill:#bbdefb!important}.ngx-tooth-crown-outline{fill:none;stroke:#333;stroke-width:1.5;pointer-events:none}.ngx-tooth-label{font-size:9px;fill:#555;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-tooltip-bg{fill:#333;opacity:.88;pointer-events:none}.ngx-tooltip-text{font-size:9px;fill:#fff;pointer-events:none;-webkit-user-select:none;user-select:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
293
|
+
}
|
|
294
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ToothComponent, decorators: [{
|
|
295
|
+
type: Component,
|
|
296
|
+
args: [{ selector: 'g[ngx-tooth]', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: `
|
|
297
|
+
@if (isUpperArch()) {
|
|
298
|
+
@for (root of rootRects(); track $index) {
|
|
299
|
+
<svg:rect
|
|
300
|
+
[attr.x]="root.x" [attr.y]="root.y"
|
|
301
|
+
[attr.width]="root.width" [attr.height]="root.height"
|
|
302
|
+
class="ngx-tooth-root"
|
|
303
|
+
/>
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
<svg:polygon
|
|
308
|
+
[attr.points]="surfaces().buccal"
|
|
309
|
+
[attr.fill]="getSurfaceColor('buccal') || getSurfaceColor('labial') || '#fff'"
|
|
310
|
+
class="ngx-surface"
|
|
311
|
+
(click)="onSurface($event, isAnterior() ? 'labial' : 'buccal')"
|
|
312
|
+
(mouseenter)="onSurfaceHover($event, isAnterior() ? 'labial' : 'buccal')"
|
|
313
|
+
/>
|
|
314
|
+
<svg:polygon
|
|
315
|
+
[attr.points]="surfaces().lingual"
|
|
316
|
+
[attr.fill]="getSurfaceColor('lingual') || '#fff'"
|
|
317
|
+
class="ngx-surface"
|
|
318
|
+
(click)="onSurface($event, 'lingual')"
|
|
319
|
+
(mouseenter)="onSurfaceHover($event, 'lingual')"
|
|
320
|
+
/>
|
|
321
|
+
<svg:polygon
|
|
322
|
+
[attr.points]="surfaces().mesial"
|
|
323
|
+
[attr.fill]="getSurfaceColor('mesial') || '#fff'"
|
|
324
|
+
class="ngx-surface"
|
|
325
|
+
(click)="onSurface($event, 'mesial')"
|
|
326
|
+
(mouseenter)="onSurfaceHover($event, 'mesial')"
|
|
327
|
+
/>
|
|
328
|
+
<svg:polygon
|
|
329
|
+
[attr.points]="surfaces().distal"
|
|
330
|
+
[attr.fill]="getSurfaceColor('distal') || '#fff'"
|
|
331
|
+
class="ngx-surface"
|
|
332
|
+
(click)="onSurface($event, 'distal')"
|
|
333
|
+
(mouseenter)="onSurfaceHover($event, 'distal')"
|
|
334
|
+
/>
|
|
335
|
+
<svg:rect
|
|
336
|
+
[attr.x]="surfaces().center.x" [attr.y]="surfaces().center.y"
|
|
337
|
+
[attr.width]="surfaces().center.w" [attr.height]="surfaces().center.h"
|
|
338
|
+
[attr.fill]="getSurfaceColor('occlusal') || getSurfaceColor('incisal') || '#fff'"
|
|
339
|
+
class="ngx-surface"
|
|
340
|
+
(click)="onSurface($event, isAnterior() ? 'incisal' : 'occlusal')"
|
|
341
|
+
(mouseenter)="onSurfaceHover($event, isAnterior() ? 'incisal' : 'occlusal')"
|
|
342
|
+
/>
|
|
343
|
+
|
|
344
|
+
<!-- Crown outline — purely decorative, pointer-events:none lets clicks reach surfaces -->
|
|
345
|
+
<svg:rect
|
|
346
|
+
[attr.x]="crownRect().x" [attr.y]="crownRect().y"
|
|
347
|
+
[attr.width]="crownRect().w" [attr.height]="crownRect().h"
|
|
348
|
+
class="ngx-tooth-crown-outline"
|
|
349
|
+
/>
|
|
350
|
+
|
|
351
|
+
<svg:text
|
|
352
|
+
[attr.x]="labelPos().x" [attr.y]="labelPos().y"
|
|
353
|
+
class="ngx-tooth-label"
|
|
354
|
+
text-anchor="middle"
|
|
355
|
+
>{{ label() }}</svg:text>
|
|
356
|
+
|
|
357
|
+
@if (!isUpperArch()) {
|
|
358
|
+
@for (root of rootRects(); track $index) {
|
|
359
|
+
<svg:rect
|
|
360
|
+
[attr.x]="root.x" [attr.y]="root.y"
|
|
361
|
+
[attr.width]="root.width" [attr.height]="root.height"
|
|
362
|
+
class="ngx-tooth-root"
|
|
363
|
+
/>
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
<!-- Tooltip -->
|
|
368
|
+
@if (showTooltip() && hoveredSurface()) {
|
|
369
|
+
<svg:g>
|
|
370
|
+
<svg:rect
|
|
371
|
+
[attr.x]="tooltipPos().x - TOOLTIP_W / 2"
|
|
372
|
+
[attr.y]="tooltipPos().y - TOOLTIP_H / 2"
|
|
373
|
+
[attr.width]="TOOLTIP_W"
|
|
374
|
+
[attr.height]="TOOLTIP_H"
|
|
375
|
+
[attr.rx]="TOOLTIP_R"
|
|
376
|
+
class="ngx-tooltip-bg"
|
|
377
|
+
/>
|
|
378
|
+
<svg:text
|
|
379
|
+
[attr.x]="tooltipPos().x"
|
|
380
|
+
[attr.y]="tooltipPos().y + 1"
|
|
381
|
+
class="ngx-tooltip-text"
|
|
382
|
+
text-anchor="middle"
|
|
383
|
+
dominant-baseline="middle"
|
|
384
|
+
>{{ hoveredSurface() }}</svg:text>
|
|
385
|
+
</svg:g>
|
|
386
|
+
}
|
|
387
|
+
`, styles: [".ngx-tooth-root{fill:#f0ece3;stroke:#999;stroke-width:1}.ngx-surface{stroke:#666;stroke-width:.8;cursor:pointer;transition:fill .12s}.ngx-surface:hover{fill:#bbdefb!important}.ngx-tooth-crown-outline{fill:none;stroke:#333;stroke-width:1.5;pointer-events:none}.ngx-tooth-label{font-size:9px;fill:#555;pointer-events:none;-webkit-user-select:none;user-select:none}.ngx-tooltip-bg{fill:#333;opacity:.88;pointer-events:none}.ngx-tooltip-text{font-size:9px;fill:#fff;pointer-events:none;-webkit-user-select:none;user-select:none}\n"] }]
|
|
388
|
+
}], propDecorators: { definition: [{ type: i0.Input, args: [{ isSignal: true, alias: "definition", required: true }] }], conditions: [{ type: i0.Input, args: [{ isSignal: true, alias: "conditions", required: false }] }], notation: [{ type: i0.Input, args: [{ isSignal: true, alias: "notation", required: false }] }], interactive: [{ type: i0.Input, args: [{ isSignal: true, alias: "interactive", required: false }] }], showTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTooltip", required: false }] }], x: [{ type: i0.Input, args: [{ isSignal: true, alias: "x", required: false }] }], y: [{ type: i0.Input, args: [{ isSignal: true, alias: "y", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], toothClick: [{ type: i0.Output, args: ["toothClick"] }], surfaceClick: [{ type: i0.Output, args: ["surfaceClick"] }], toothHover: [{ type: i0.Output, args: ["toothHover"] }], surfaceHover: [{ type: i0.Output, args: ["surfaceHover"] }], onHostLeave: [{
|
|
389
|
+
type: HostListener,
|
|
390
|
+
args: ['mouseleave']
|
|
391
|
+
}] } });
|
|
392
|
+
|
|
393
|
+
const TOOTH_SIZE = 36;
|
|
394
|
+
const TOOTH_GAP = 6;
|
|
395
|
+
const STEP = TOOTH_SIZE + TOOTH_GAP;
|
|
396
|
+
const ROOT_H = TOOTH_SIZE * 0.7;
|
|
397
|
+
const LABEL_H = 16;
|
|
398
|
+
const ARCH_GAP = 28;
|
|
399
|
+
// Layout constants
|
|
400
|
+
const TEETH_PER_ARCH = 8; // adult teeth per quadrant
|
|
401
|
+
const PRIMARY_PER_ARCH = 5; // primary teeth per quadrant
|
|
402
|
+
const ADULT_ROW_W = TEETH_PER_ARCH * STEP - TOOTH_GAP;
|
|
403
|
+
// SVG padding
|
|
404
|
+
const PAD_X = 16;
|
|
405
|
+
const PAD_Y = ROOT_H + LABEL_H + 8;
|
|
406
|
+
// Arch midpoint x (center of chart)
|
|
407
|
+
const MID_X = PAD_X + ADULT_ROW_W;
|
|
408
|
+
class OdontogramComponent {
|
|
409
|
+
notation = input('fdi', ...(ngDevMode ? [{ debugName: "notation" }] : /* istanbul ignore next */ []));
|
|
410
|
+
teeth = input([], ...(ngDevMode ? [{ debugName: "teeth" }] : /* istanbul ignore next */ []));
|
|
411
|
+
showPrimary = input(true, ...(ngDevMode ? [{ debugName: "showPrimary" }] : /* istanbul ignore next */ []));
|
|
412
|
+
mode = input('interactive', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
413
|
+
showTooltip = input(true, ...(ngDevMode ? [{ debugName: "showTooltip" }] : /* istanbul ignore next */ []));
|
|
414
|
+
toothClick = output();
|
|
415
|
+
surfaceClick = output();
|
|
416
|
+
toothHover = output();
|
|
417
|
+
surfaceHover = output();
|
|
418
|
+
TOOTH_SIZE = TOOTH_SIZE;
|
|
419
|
+
PAD_X = PAD_X;
|
|
420
|
+
upperAdult = computed(() => ALL_TEETH.filter(t => t.type === 'adult' && t.quadrant.startsWith('upper'))
|
|
421
|
+
.sort((a, b) => this.globalOrder(a) - this.globalOrder(b)), ...(ngDevMode ? [{ debugName: "upperAdult" }] : /* istanbul ignore next */ []));
|
|
422
|
+
lowerAdult = computed(() => ALL_TEETH.filter(t => t.type === 'adult' && t.quadrant.startsWith('lower'))
|
|
423
|
+
.sort((a, b) => this.globalOrder(a) - this.globalOrder(b)), ...(ngDevMode ? [{ debugName: "lowerAdult" }] : /* istanbul ignore next */ []));
|
|
424
|
+
upperPrimary = computed(() => ALL_TEETH.filter(t => t.type === 'primary' && t.quadrant.startsWith('upper'))
|
|
425
|
+
.sort((a, b) => this.globalOrder(a) - this.globalOrder(b)), ...(ngDevMode ? [{ debugName: "upperPrimary" }] : /* istanbul ignore next */ []));
|
|
426
|
+
lowerPrimary = computed(() => ALL_TEETH.filter(t => t.type === 'primary' && t.quadrant.startsWith('lower'))
|
|
427
|
+
.sort((a, b) => this.globalOrder(a) - this.globalOrder(b)), ...(ngDevMode ? [{ debugName: "lowerPrimary" }] : /* istanbul ignore next */ []));
|
|
428
|
+
/** X position of an adult tooth within the SVG */
|
|
429
|
+
toothX(tooth) {
|
|
430
|
+
const isRight = tooth.quadrant.includes('right');
|
|
431
|
+
if (isRight) {
|
|
432
|
+
// Right quadrant: tooth order 1=innermost, 8=outermost → mirror left-to-right
|
|
433
|
+
return PAD_X + (TEETH_PER_ARCH - tooth.order) * STEP;
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// Left quadrant: tooth order 1=innermost
|
|
437
|
+
return PAD_X + TEETH_PER_ARCH * STEP + (tooth.order - 1) * STEP;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/** X position of a primary tooth — centered within the adult arch span */
|
|
441
|
+
primaryX(tooth) {
|
|
442
|
+
const adultSpanStart = PAD_X + (TEETH_PER_ARCH - PRIMARY_PER_ARCH) * STEP / 2;
|
|
443
|
+
const isRight = tooth.quadrant.includes('right');
|
|
444
|
+
if (isRight) {
|
|
445
|
+
return adultSpanStart + (PRIMARY_PER_ARCH - tooth.order) * STEP;
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
return PAD_X + TEETH_PER_ARCH * STEP + (adultSpanStart - PAD_X) + (tooth.order - 1) * STEP;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
upperAdultY = computed(() => PAD_Y, ...(ngDevMode ? [{ debugName: "upperAdultY" }] : /* istanbul ignore next */ []));
|
|
452
|
+
upperPrimaryY = computed(() => PAD_Y + TOOTH_SIZE + ROOT_H + ARCH_GAP, ...(ngDevMode ? [{ debugName: "upperPrimaryY" }] : /* istanbul ignore next */ []));
|
|
453
|
+
midlineY = computed(() => {
|
|
454
|
+
const base = this.upperAdultY() + TOOTH_SIZE + ROOT_H + ARCH_GAP / 2;
|
|
455
|
+
return this.showPrimary() ? this.upperPrimaryY() + TOOTH_SIZE + ROOT_H + ARCH_GAP / 2 : base;
|
|
456
|
+
}, ...(ngDevMode ? [{ debugName: "midlineY" }] : /* istanbul ignore next */ []));
|
|
457
|
+
lowerPrimaryY = computed(() => this.midlineY() + ARCH_GAP / 2, ...(ngDevMode ? [{ debugName: "lowerPrimaryY" }] : /* istanbul ignore next */ []));
|
|
458
|
+
lowerAdultY = computed(() => this.showPrimary()
|
|
459
|
+
? this.lowerPrimaryY() + TOOTH_SIZE + ROOT_H + ARCH_GAP
|
|
460
|
+
: this.midlineY() + ARCH_GAP / 2, ...(ngDevMode ? [{ debugName: "lowerAdultY" }] : /* istanbul ignore next */ []));
|
|
461
|
+
svgWidth = computed(() => PAD_X * 2 + TEETH_PER_ARCH * 2 * STEP - TOOTH_GAP, ...(ngDevMode ? [{ debugName: "svgWidth" }] : /* istanbul ignore next */ []));
|
|
462
|
+
svgHeight = computed(() => {
|
|
463
|
+
const bottom = this.lowerAdultY() + TOOTH_SIZE + ROOT_H + LABEL_H + PAD_Y;
|
|
464
|
+
return bottom;
|
|
465
|
+
}, ...(ngDevMode ? [{ debugName: "svgHeight" }] : /* istanbul ignore next */ []));
|
|
466
|
+
viewBox = computed(() => `0 0 ${this.svgWidth()} ${this.svgHeight()}`, ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
|
|
467
|
+
getConditions(tooth) {
|
|
468
|
+
const id = this.notation() === 'fdi' ? tooth.fdi : tooth.universal;
|
|
469
|
+
return this.teeth().find(t => t.id === id)?.conditions ?? [];
|
|
470
|
+
}
|
|
471
|
+
globalOrder(tooth) {
|
|
472
|
+
const isRight = tooth.quadrant.includes('right');
|
|
473
|
+
// Left-to-right order across full arch: right quadrant reversed, then left quadrant
|
|
474
|
+
return isRight ? TEETH_PER_ARCH - tooth.order : TEETH_PER_ARCH + tooth.order;
|
|
475
|
+
}
|
|
476
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: OdontogramComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
477
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: OdontogramComponent, isStandalone: true, selector: "ngx-odontogram", inputs: { notation: { classPropertyName: "notation", publicName: "notation", isSignal: true, isRequired: false, transformFunction: null }, teeth: { classPropertyName: "teeth", publicName: "teeth", isSignal: true, isRequired: false, transformFunction: null }, showPrimary: { classPropertyName: "showPrimary", publicName: "showPrimary", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, showTooltip: { classPropertyName: "showTooltip", publicName: "showTooltip", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toothClick: "toothClick", surfaceClick: "surfaceClick", toothHover: "toothHover", surfaceHover: "surfaceHover" }, ngImport: i0, template: `
|
|
478
|
+
<svg
|
|
479
|
+
[attr.width]="svgWidth()"
|
|
480
|
+
[attr.height]="svgHeight()"
|
|
481
|
+
[attr.viewBox]="viewBox()"
|
|
482
|
+
class="ngx-odontogram"
|
|
483
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
484
|
+
>
|
|
485
|
+
<!-- Upper adult arch -->
|
|
486
|
+
@for (tooth of upperAdult(); track tooth.fdi) {
|
|
487
|
+
<g ngx-tooth
|
|
488
|
+
[definition]="tooth"
|
|
489
|
+
[conditions]="getConditions(tooth)"
|
|
490
|
+
[notation]="notation()"
|
|
491
|
+
[interactive]="mode() === 'interactive'"
|
|
492
|
+
[showTooltip]="showTooltip()"
|
|
493
|
+
[x]="toothX(tooth)"
|
|
494
|
+
[y]="upperAdultY()"
|
|
495
|
+
[size]="TOOTH_SIZE"
|
|
496
|
+
(toothClick)="toothClick.emit($event)"
|
|
497
|
+
(surfaceClick)="surfaceClick.emit($event)"
|
|
498
|
+
(toothHover)="toothHover.emit($event)"
|
|
499
|
+
(surfaceHover)="surfaceHover.emit($event)"
|
|
500
|
+
/>
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
<!-- Upper primary arch -->
|
|
504
|
+
@if (showPrimary()) {
|
|
505
|
+
@for (tooth of upperPrimary(); track tooth.fdi) {
|
|
506
|
+
<g ngx-tooth
|
|
507
|
+
[definition]="tooth"
|
|
508
|
+
[conditions]="getConditions(tooth)"
|
|
509
|
+
[notation]="notation()"
|
|
510
|
+
[interactive]="mode() === 'interactive'"
|
|
511
|
+
[showTooltip]="showTooltip()"
|
|
512
|
+
[x]="primaryX(tooth)"
|
|
513
|
+
[y]="upperPrimaryY()"
|
|
514
|
+
[size]="TOOTH_SIZE"
|
|
515
|
+
(toothClick)="toothClick.emit($event)"
|
|
516
|
+
(surfaceClick)="surfaceClick.emit($event)"
|
|
517
|
+
(toothHover)="toothHover.emit($event)"
|
|
518
|
+
(surfaceHover)="surfaceHover.emit($event)"
|
|
519
|
+
/>
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
<!-- Arch midline -->
|
|
524
|
+
<line
|
|
525
|
+
[attr.x1]="PAD_X - 4" [attr.y1]="midlineY()"
|
|
526
|
+
[attr.x2]="svgWidth() - PAD_X + 4" [attr.y2]="midlineY()"
|
|
527
|
+
class="ngx-midline"
|
|
528
|
+
/>
|
|
529
|
+
|
|
530
|
+
<!-- Lower primary arch -->
|
|
531
|
+
@if (showPrimary()) {
|
|
532
|
+
@for (tooth of lowerPrimary(); track tooth.fdi) {
|
|
533
|
+
<g ngx-tooth
|
|
534
|
+
[definition]="tooth"
|
|
535
|
+
[conditions]="getConditions(tooth)"
|
|
536
|
+
[notation]="notation()"
|
|
537
|
+
[interactive]="mode() === 'interactive'"
|
|
538
|
+
[showTooltip]="showTooltip()"
|
|
539
|
+
[x]="primaryX(tooth)"
|
|
540
|
+
[y]="lowerPrimaryY()"
|
|
541
|
+
[size]="TOOTH_SIZE"
|
|
542
|
+
(toothClick)="toothClick.emit($event)"
|
|
543
|
+
(surfaceClick)="surfaceClick.emit($event)"
|
|
544
|
+
(toothHover)="toothHover.emit($event)"
|
|
545
|
+
(surfaceHover)="surfaceHover.emit($event)"
|
|
546
|
+
/>
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
<!-- Lower adult arch -->
|
|
551
|
+
@for (tooth of lowerAdult(); track tooth.fdi) {
|
|
552
|
+
<g ngx-tooth
|
|
553
|
+
[definition]="tooth"
|
|
554
|
+
[conditions]="getConditions(tooth)"
|
|
555
|
+
[notation]="notation()"
|
|
556
|
+
[interactive]="mode() === 'interactive'"
|
|
557
|
+
[showTooltip]="showTooltip()"
|
|
558
|
+
[x]="toothX(tooth)"
|
|
559
|
+
[y]="lowerAdultY()"
|
|
560
|
+
[size]="TOOTH_SIZE"
|
|
561
|
+
(toothClick)="toothClick.emit($event)"
|
|
562
|
+
(surfaceClick)="surfaceClick.emit($event)"
|
|
563
|
+
(toothHover)="toothHover.emit($event)"
|
|
564
|
+
(surfaceHover)="surfaceHover.emit($event)"
|
|
565
|
+
/>
|
|
566
|
+
}
|
|
567
|
+
</svg>
|
|
568
|
+
`, isInline: true, styles: [":host{display:inline-block}.ngx-odontogram{font-family:sans-serif}.ngx-midline{stroke:#bbb;stroke-width:1;stroke-dasharray:4 3}\n"], dependencies: [{ kind: "component", type: ToothComponent, selector: "g[ngx-tooth]", inputs: ["definition", "conditions", "notation", "interactive", "showTooltip", "x", "y", "size"], outputs: ["toothClick", "surfaceClick", "toothHover", "surfaceHover"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
569
|
+
}
|
|
570
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: OdontogramComponent, decorators: [{
|
|
571
|
+
type: Component,
|
|
572
|
+
args: [{ selector: 'ngx-odontogram', standalone: true, imports: [ToothComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
573
|
+
<svg
|
|
574
|
+
[attr.width]="svgWidth()"
|
|
575
|
+
[attr.height]="svgHeight()"
|
|
576
|
+
[attr.viewBox]="viewBox()"
|
|
577
|
+
class="ngx-odontogram"
|
|
578
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
579
|
+
>
|
|
580
|
+
<!-- Upper adult arch -->
|
|
581
|
+
@for (tooth of upperAdult(); track tooth.fdi) {
|
|
582
|
+
<g ngx-tooth
|
|
583
|
+
[definition]="tooth"
|
|
584
|
+
[conditions]="getConditions(tooth)"
|
|
585
|
+
[notation]="notation()"
|
|
586
|
+
[interactive]="mode() === 'interactive'"
|
|
587
|
+
[showTooltip]="showTooltip()"
|
|
588
|
+
[x]="toothX(tooth)"
|
|
589
|
+
[y]="upperAdultY()"
|
|
590
|
+
[size]="TOOTH_SIZE"
|
|
591
|
+
(toothClick)="toothClick.emit($event)"
|
|
592
|
+
(surfaceClick)="surfaceClick.emit($event)"
|
|
593
|
+
(toothHover)="toothHover.emit($event)"
|
|
594
|
+
(surfaceHover)="surfaceHover.emit($event)"
|
|
595
|
+
/>
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
<!-- Upper primary arch -->
|
|
599
|
+
@if (showPrimary()) {
|
|
600
|
+
@for (tooth of upperPrimary(); track tooth.fdi) {
|
|
601
|
+
<g ngx-tooth
|
|
602
|
+
[definition]="tooth"
|
|
603
|
+
[conditions]="getConditions(tooth)"
|
|
604
|
+
[notation]="notation()"
|
|
605
|
+
[interactive]="mode() === 'interactive'"
|
|
606
|
+
[showTooltip]="showTooltip()"
|
|
607
|
+
[x]="primaryX(tooth)"
|
|
608
|
+
[y]="upperPrimaryY()"
|
|
609
|
+
[size]="TOOTH_SIZE"
|
|
610
|
+
(toothClick)="toothClick.emit($event)"
|
|
611
|
+
(surfaceClick)="surfaceClick.emit($event)"
|
|
612
|
+
(toothHover)="toothHover.emit($event)"
|
|
613
|
+
(surfaceHover)="surfaceHover.emit($event)"
|
|
614
|
+
/>
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
<!-- Arch midline -->
|
|
619
|
+
<line
|
|
620
|
+
[attr.x1]="PAD_X - 4" [attr.y1]="midlineY()"
|
|
621
|
+
[attr.x2]="svgWidth() - PAD_X + 4" [attr.y2]="midlineY()"
|
|
622
|
+
class="ngx-midline"
|
|
623
|
+
/>
|
|
624
|
+
|
|
625
|
+
<!-- Lower primary arch -->
|
|
626
|
+
@if (showPrimary()) {
|
|
627
|
+
@for (tooth of lowerPrimary(); track tooth.fdi) {
|
|
628
|
+
<g ngx-tooth
|
|
629
|
+
[definition]="tooth"
|
|
630
|
+
[conditions]="getConditions(tooth)"
|
|
631
|
+
[notation]="notation()"
|
|
632
|
+
[interactive]="mode() === 'interactive'"
|
|
633
|
+
[showTooltip]="showTooltip()"
|
|
634
|
+
[x]="primaryX(tooth)"
|
|
635
|
+
[y]="lowerPrimaryY()"
|
|
636
|
+
[size]="TOOTH_SIZE"
|
|
637
|
+
(toothClick)="toothClick.emit($event)"
|
|
638
|
+
(surfaceClick)="surfaceClick.emit($event)"
|
|
639
|
+
(toothHover)="toothHover.emit($event)"
|
|
640
|
+
(surfaceHover)="surfaceHover.emit($event)"
|
|
641
|
+
/>
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
<!-- Lower adult arch -->
|
|
646
|
+
@for (tooth of lowerAdult(); track tooth.fdi) {
|
|
647
|
+
<g ngx-tooth
|
|
648
|
+
[definition]="tooth"
|
|
649
|
+
[conditions]="getConditions(tooth)"
|
|
650
|
+
[notation]="notation()"
|
|
651
|
+
[interactive]="mode() === 'interactive'"
|
|
652
|
+
[showTooltip]="showTooltip()"
|
|
653
|
+
[x]="toothX(tooth)"
|
|
654
|
+
[y]="lowerAdultY()"
|
|
655
|
+
[size]="TOOTH_SIZE"
|
|
656
|
+
(toothClick)="toothClick.emit($event)"
|
|
657
|
+
(surfaceClick)="surfaceClick.emit($event)"
|
|
658
|
+
(toothHover)="toothHover.emit($event)"
|
|
659
|
+
(surfaceHover)="surfaceHover.emit($event)"
|
|
660
|
+
/>
|
|
661
|
+
}
|
|
662
|
+
</svg>
|
|
663
|
+
`, styles: [":host{display:inline-block}.ngx-odontogram{font-family:sans-serif}.ngx-midline{stroke:#bbb;stroke-width:1;stroke-dasharray:4 3}\n"] }]
|
|
664
|
+
}], propDecorators: { notation: [{ type: i0.Input, args: [{ isSignal: true, alias: "notation", required: false }] }], teeth: [{ type: i0.Input, args: [{ isSignal: true, alias: "teeth", required: false }] }], showPrimary: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPrimary", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], showTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTooltip", required: false }] }], toothClick: [{ type: i0.Output, args: ["toothClick"] }], surfaceClick: [{ type: i0.Output, args: ["surfaceClick"] }], toothHover: [{ type: i0.Output, args: ["toothHover"] }], surfaceHover: [{ type: i0.Output, args: ["surfaceHover"] }] } });
|
|
665
|
+
|
|
666
|
+
/*
|
|
667
|
+
* Public API Surface of ngx-odontogram
|
|
668
|
+
*/
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Generated bundle index. Do not edit.
|
|
672
|
+
*/
|
|
673
|
+
|
|
674
|
+
export { ALL_TEETH, OdontogramComponent, ToothComponent, getTeethByQuadrant, getToothByFdi, getToothByUniversal };
|
|
675
|
+
//# sourceMappingURL=ngx-odontogram.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-odontogram.mjs","sources":["../../../projects/ngx-odontogram/src/lib/utils/tooth-definitions.ts","../../../projects/ngx-odontogram/src/lib/components/tooth/tooth.component.ts","../../../projects/ngx-odontogram/src/lib/components/odontogram/odontogram.component.ts","../../../projects/ngx-odontogram/src/public-api.ts","../../../projects/ngx-odontogram/src/ngx-odontogram.ts"],"sourcesContent":["import { ToothDefinition } from '../models/odontogram.models';\n\n// Adult teeth — FDI quadrants: 1=upper-right, 2=upper-left, 3=lower-left, 4=lower-right\n// Universal: upper-right 1-8, upper-left 9-16, lower-left 17-24, lower-right 25-32\nconst ADULT_TEETH: ToothDefinition[] = [\n // Upper right (FDI 11-18, Universal 8-1)\n { fdi: 11, universal: 8, type: 'adult', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 8 },\n { fdi: 12, universal: 7, type: 'adult', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 7 },\n { fdi: 13, universal: 6, type: 'adult', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 6 },\n { fdi: 14, universal: 5, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 1, order: 5 },\n { fdi: 15, universal: 4, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 1, order: 4 },\n { fdi: 16, universal: 3, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 3, order: 3 },\n { fdi: 17, universal: 2, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 3, order: 2 },\n { fdi: 18, universal: 1, type: 'adult', quadrant: 'upper-right', isAnterior: false, roots: 3, order: 1 },\n // Upper left (FDI 21-28, Universal 9-16)\n { fdi: 21, universal: 9, type: 'adult', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 1 },\n { fdi: 22, universal: 10, type: 'adult', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 2 },\n { fdi: 23, universal: 11, type: 'adult', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 3 },\n { fdi: 24, universal: 12, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 1, order: 4 },\n { fdi: 25, universal: 13, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 1, order: 5 },\n { fdi: 26, universal: 14, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 3, order: 6 },\n { fdi: 27, universal: 15, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 3, order: 7 },\n { fdi: 28, universal: 16, type: 'adult', quadrant: 'upper-left', isAnterior: false, roots: 3, order: 8 },\n // Lower left (FDI 31-38, Universal 17-24)\n { fdi: 31, universal: 24, type: 'adult', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 1 },\n { fdi: 32, universal: 23, type: 'adult', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 2 },\n { fdi: 33, universal: 22, type: 'adult', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 3 },\n { fdi: 34, universal: 21, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 1, order: 4 },\n { fdi: 35, universal: 20, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 1, order: 5 },\n { fdi: 36, universal: 19, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 2, order: 6 },\n { fdi: 37, universal: 18, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 2, order: 7 },\n { fdi: 38, universal: 17, type: 'adult', quadrant: 'lower-left', isAnterior: false, roots: 2, order: 8 },\n // Lower right (FDI 41-48, Universal 25-32)\n { fdi: 41, universal: 25, type: 'adult', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 8 },\n { fdi: 42, universal: 26, type: 'adult', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 7 },\n { fdi: 43, universal: 27, type: 'adult', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 6 },\n { fdi: 44, universal: 28, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 1, order: 5 },\n { fdi: 45, universal: 29, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 1, order: 4 },\n { fdi: 46, universal: 30, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 2, order: 3 },\n { fdi: 47, universal: 31, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 2, order: 2 },\n { fdi: 48, universal: 32, type: 'adult', quadrant: 'lower-right', isAnterior: false, roots: 2, order: 1 },\n];\n\n// Primary teeth — FDI quadrants: 5=upper-right, 6=upper-left, 7=lower-left, 8=lower-right\n// Universal primary: A-T (letters)\nconst PRIMARY_TEETH: ToothDefinition[] = [\n // Upper right (FDI 51-55, Universal E-A)\n { fdi: 51, universal: 'E', type: 'primary', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 5 },\n { fdi: 52, universal: 'D', type: 'primary', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 4 },\n { fdi: 53, universal: 'C', type: 'primary', quadrant: 'upper-right', isAnterior: true, roots: 1, order: 3 },\n { fdi: 54, universal: 'B', type: 'primary', quadrant: 'upper-right', isAnterior: false, roots: 1, order: 2 },\n { fdi: 55, universal: 'A', type: 'primary', quadrant: 'upper-right', isAnterior: false, roots: 2, order: 1 },\n // Upper left (FDI 61-65, Universal F-J)\n { fdi: 61, universal: 'F', type: 'primary', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 1 },\n { fdi: 62, universal: 'G', type: 'primary', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 2 },\n { fdi: 63, universal: 'H', type: 'primary', quadrant: 'upper-left', isAnterior: true, roots: 1, order: 3 },\n { fdi: 64, universal: 'I', type: 'primary', quadrant: 'upper-left', isAnterior: false, roots: 1, order: 4 },\n { fdi: 65, universal: 'J', type: 'primary', quadrant: 'upper-left', isAnterior: false, roots: 2, order: 5 },\n // Lower left (FDI 71-75, Universal O-K)\n { fdi: 71, universal: 'O', type: 'primary', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 1 },\n { fdi: 72, universal: 'N', type: 'primary', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 2 },\n { fdi: 73, universal: 'M', type: 'primary', quadrant: 'lower-left', isAnterior: true, roots: 1, order: 3 },\n { fdi: 74, universal: 'L', type: 'primary', quadrant: 'lower-left', isAnterior: false, roots: 1, order: 4 },\n { fdi: 75, universal: 'K', type: 'primary', quadrant: 'lower-left', isAnterior: false, roots: 2, order: 5 },\n // Lower right (FDI 81-85, Universal T-P)\n { fdi: 81, universal: 'T', type: 'primary', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 5 },\n { fdi: 82, universal: 'S', type: 'primary', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 4 },\n { fdi: 83, universal: 'R', type: 'primary', quadrant: 'lower-right', isAnterior: true, roots: 1, order: 3 },\n { fdi: 84, universal: 'Q', type: 'primary', quadrant: 'lower-right', isAnterior: false, roots: 1, order: 2 },\n { fdi: 85, universal: 'P', type: 'primary', quadrant: 'lower-right', isAnterior: false, roots: 2, order: 1 },\n];\n\nexport const ALL_TEETH: ToothDefinition[] = [...ADULT_TEETH, ...PRIMARY_TEETH];\n\nexport function getToothByFdi(fdi: number): ToothDefinition | undefined {\n return ALL_TEETH.find(t => t.fdi === fdi);\n}\n\nexport function getToothByUniversal(id: number | string): ToothDefinition | undefined {\n return ALL_TEETH.find(t => t.universal === id);\n}\n\nexport function getTeethByQuadrant(quadrant: string, type: 'adult' | 'primary' | 'all' = 'all'): ToothDefinition[] {\n return ALL_TEETH\n .filter(t => t.quadrant === quadrant && (type === 'all' || t.type === type))\n .sort((a, b) => a.order - b.order);\n}\n","import {\n Component, input, output, computed, signal, ChangeDetectionStrategy, ViewEncapsulation, HostListener\n} from '@angular/core';\nimport { OdontogramEvent, SurfaceCondition, Surface, ToothDefinition, Notation } from '../../models/odontogram.models';\n\nconst TOOLTIP_W = 52;\nconst TOOLTIP_H = 16;\nconst TOOLTIP_R = 4;\n\n@Component({\n selector: 'g[ngx-tooth]',\n standalone: true,\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n styles: [`\n .ngx-tooth-root {\n fill: #f0ece3;\n stroke: #999;\n stroke-width: 1;\n }\n .ngx-surface {\n stroke: #666;\n stroke-width: 0.8;\n cursor: pointer;\n transition: fill 0.12s;\n }\n .ngx-surface:hover {\n fill: #bbdefb !important;\n }\n .ngx-tooth-crown-outline {\n fill: none;\n stroke: #333;\n stroke-width: 1.5;\n pointer-events: none;\n }\n .ngx-tooth-label {\n font-size: 9px;\n fill: #555;\n pointer-events: none;\n user-select: none;\n }\n .ngx-tooltip-bg {\n fill: #333;\n opacity: 0.88;\n pointer-events: none;\n }\n .ngx-tooltip-text {\n font-size: 9px;\n fill: #fff;\n pointer-events: none;\n user-select: none;\n }\n `],\n template: `\n @if (isUpperArch()) {\n @for (root of rootRects(); track $index) {\n <svg:rect\n [attr.x]=\"root.x\" [attr.y]=\"root.y\"\n [attr.width]=\"root.width\" [attr.height]=\"root.height\"\n class=\"ngx-tooth-root\"\n />\n }\n }\n\n <svg:polygon\n [attr.points]=\"surfaces().buccal\"\n [attr.fill]=\"getSurfaceColor('buccal') || getSurfaceColor('labial') || '#fff'\"\n class=\"ngx-surface\"\n (click)=\"onSurface($event, isAnterior() ? 'labial' : 'buccal')\"\n (mouseenter)=\"onSurfaceHover($event, isAnterior() ? 'labial' : 'buccal')\"\n />\n <svg:polygon\n [attr.points]=\"surfaces().lingual\"\n [attr.fill]=\"getSurfaceColor('lingual') || '#fff'\"\n class=\"ngx-surface\"\n (click)=\"onSurface($event, 'lingual')\"\n (mouseenter)=\"onSurfaceHover($event, 'lingual')\"\n />\n <svg:polygon\n [attr.points]=\"surfaces().mesial\"\n [attr.fill]=\"getSurfaceColor('mesial') || '#fff'\"\n class=\"ngx-surface\"\n (click)=\"onSurface($event, 'mesial')\"\n (mouseenter)=\"onSurfaceHover($event, 'mesial')\"\n />\n <svg:polygon\n [attr.points]=\"surfaces().distal\"\n [attr.fill]=\"getSurfaceColor('distal') || '#fff'\"\n class=\"ngx-surface\"\n (click)=\"onSurface($event, 'distal')\"\n (mouseenter)=\"onSurfaceHover($event, 'distal')\"\n />\n <svg:rect\n [attr.x]=\"surfaces().center.x\" [attr.y]=\"surfaces().center.y\"\n [attr.width]=\"surfaces().center.w\" [attr.height]=\"surfaces().center.h\"\n [attr.fill]=\"getSurfaceColor('occlusal') || getSurfaceColor('incisal') || '#fff'\"\n class=\"ngx-surface\"\n (click)=\"onSurface($event, isAnterior() ? 'incisal' : 'occlusal')\"\n (mouseenter)=\"onSurfaceHover($event, isAnterior() ? 'incisal' : 'occlusal')\"\n />\n\n <!-- Crown outline — purely decorative, pointer-events:none lets clicks reach surfaces -->\n <svg:rect\n [attr.x]=\"crownRect().x\" [attr.y]=\"crownRect().y\"\n [attr.width]=\"crownRect().w\" [attr.height]=\"crownRect().h\"\n class=\"ngx-tooth-crown-outline\"\n />\n\n <svg:text\n [attr.x]=\"labelPos().x\" [attr.y]=\"labelPos().y\"\n class=\"ngx-tooth-label\"\n text-anchor=\"middle\"\n >{{ label() }}</svg:text>\n\n @if (!isUpperArch()) {\n @for (root of rootRects(); track $index) {\n <svg:rect\n [attr.x]=\"root.x\" [attr.y]=\"root.y\"\n [attr.width]=\"root.width\" [attr.height]=\"root.height\"\n class=\"ngx-tooth-root\"\n />\n }\n }\n\n <!-- Tooltip -->\n @if (showTooltip() && hoveredSurface()) {\n <svg:g>\n <svg:rect\n [attr.x]=\"tooltipPos().x - TOOLTIP_W / 2\"\n [attr.y]=\"tooltipPos().y - TOOLTIP_H / 2\"\n [attr.width]=\"TOOLTIP_W\"\n [attr.height]=\"TOOLTIP_H\"\n [attr.rx]=\"TOOLTIP_R\"\n class=\"ngx-tooltip-bg\"\n />\n <svg:text\n [attr.x]=\"tooltipPos().x\"\n [attr.y]=\"tooltipPos().y + 1\"\n class=\"ngx-tooltip-text\"\n text-anchor=\"middle\"\n dominant-baseline=\"middle\"\n >{{ hoveredSurface() }}</svg:text>\n </svg:g>\n }\n `,\n})\nexport class ToothComponent {\n definition = input.required<ToothDefinition>();\n conditions = input<SurfaceCondition[]>([]);\n notation = input<Notation>('fdi');\n interactive = input<boolean>(true);\n showTooltip = input<boolean>(true);\n x = input<number>(0);\n y = input<number>(0);\n size = input<number>(36);\n\n toothClick = output<OdontogramEvent>();\n surfaceClick = output<OdontogramEvent>();\n toothHover = output<OdontogramEvent>();\n surfaceHover = output<OdontogramEvent>();\n\n hoveredSurface = signal<string | null>(null);\n tooltipPos = signal<{ x: number; y: number }>({ x: 0, y: 0 });\n\n readonly TOOLTIP_W = TOOLTIP_W;\n readonly TOOLTIP_H = TOOLTIP_H;\n readonly TOOLTIP_R = TOOLTIP_R;\n\n @HostListener('mouseleave')\n onHostLeave() {\n this.hoveredSurface.set(null);\n }\n\n isAnterior = computed(() => this.definition().isAnterior);\n isUpperArch = computed(() => this.definition().quadrant.startsWith('upper'));\n\n label = computed(() => {\n const d = this.definition();\n return this.notation() === 'fdi' ? String(d.fdi) : String(d.universal);\n });\n\n crownRect = computed(() => ({ x: this.x(), y: this.y(), w: this.size(), h: this.size() }));\n\n surfaces = computed(() => {\n const x = this.x();\n const y = this.y();\n const s = this.size();\n const cx = x + s / 2;\n const cy = y + s / 2;\n const inset = s * 0.3;\n\n const tl = `${x},${y}`;\n const tr = `${x + s},${y}`;\n const bl = `${x},${y + s}`;\n const br = `${x + s},${y + s}`;\n const ci = `${cx - inset},${cy - inset}`;\n const cii = `${cx + inset},${cy - inset}`;\n const ciii = `${cx + inset},${cy + inset}`;\n const civ = `${cx - inset},${cy + inset}`;\n\n return {\n buccal: `${tl} ${tr} ${cii} ${ci}`,\n lingual: `${bl} ${br} ${ciii} ${civ}`,\n mesial: `${tl} ${bl} ${civ} ${ci}`,\n distal: `${tr} ${br} ${ciii} ${cii}`,\n center: { x: cx - inset, y: cy - inset, w: inset * 2, h: inset * 2 },\n };\n });\n\n rootRects = computed(() => {\n const x = this.x();\n const y = this.y();\n const s = this.size();\n const n = this.definition().roots;\n const rootH = s * 0.7;\n const rootW = Math.min(s / n - 3, 10);\n const totalW = n * rootW + (n - 1) * 3;\n const startX = x + (s - totalW) / 2;\n const rootY = this.isUpperArch() ? y - rootH : y + s;\n\n return Array.from({ length: n }, (_, i) => ({\n x: startX + i * (rootW + 3),\n y: rootY,\n width: rootW,\n height: rootH,\n }));\n });\n\n labelPos = computed(() => {\n const s = this.size();\n const rootH = s * 0.7;\n return {\n x: this.x() + s / 2,\n y: this.isUpperArch()\n ? this.y() - rootH - 4\n : this.y() + s + rootH + 12,\n };\n });\n\n getSurface(surface: Surface): SurfaceCondition | undefined {\n return this.conditions().find(c => c.surface === surface);\n }\n\n getSurfaceColor(surface: Surface): string | undefined {\n return this.getSurface(surface)?.color;\n }\n\n private buildEvent(mouseEvent: MouseEvent, surface?: Surface): OdontogramEvent {\n const d = this.definition();\n const id = this.notation() === 'fdi' ? d.fdi : d.universal;\n return { toothId: id, toothFdi: d.fdi, toothType: d.type, surface, conditions: this.conditions(), mouseEvent };\n }\n\n onSurface(e: MouseEvent, surface: Surface) {\n e.stopPropagation();\n if (!this.interactive()) return;\n const ev = this.buildEvent(e, surface);\n this.surfaceClick.emit(ev);\n this.toothClick.emit(ev);\n }\n\n onSurfaceHover(e: MouseEvent, surface: Surface) {\n if (!this.interactive()) return;\n const s = this.size();\n const cx = this.x() + s / 2;\n // position tooltip above crown for upper arch, below for lower arch\n const tipY = this.isUpperArch()\n ? this.y() - s * 0.7 - TOOLTIP_H - 2\n : this.y() + s + s * 0.7 + TOOLTIP_H + 2;\n this.hoveredSurface.set(surface);\n this.tooltipPos.set({ x: cx, y: tipY });\n const ev = this.buildEvent(e, surface);\n this.surfaceHover.emit(ev);\n this.toothHover.emit(ev);\n }\n}\n","import {\n Component, input, output, computed, ChangeDetectionStrategy\n} from '@angular/core';\nimport { ToothComponent } from '../tooth/tooth.component';\nimport {\n Notation, ToothData, OdontogramEvent, SurfaceCondition, ToothDefinition\n} from '../../models/odontogram.models';\nimport { ALL_TEETH } from '../../utils/tooth-definitions';\n\nconst TOOTH_SIZE = 36;\nconst TOOTH_GAP = 6;\nconst STEP = TOOTH_SIZE + TOOTH_GAP;\nconst ROOT_H = TOOTH_SIZE * 0.7;\nconst LABEL_H = 16;\nconst ARCH_GAP = 28;\n\n// Layout constants\nconst TEETH_PER_ARCH = 8; // adult teeth per quadrant\nconst PRIMARY_PER_ARCH = 5; // primary teeth per quadrant\nconst ADULT_ROW_W = TEETH_PER_ARCH * STEP - TOOTH_GAP;\n\n// SVG padding\nconst PAD_X = 16;\nconst PAD_Y = ROOT_H + LABEL_H + 8;\n\n// Arch midpoint x (center of chart)\nconst MID_X = PAD_X + ADULT_ROW_W;\n\n@Component({\n selector: 'ngx-odontogram',\n standalone: true,\n imports: [ToothComponent],\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `\n <svg\n [attr.width]=\"svgWidth()\"\n [attr.height]=\"svgHeight()\"\n [attr.viewBox]=\"viewBox()\"\n class=\"ngx-odontogram\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <!-- Upper adult arch -->\n @for (tooth of upperAdult(); track tooth.fdi) {\n <g ngx-tooth\n [definition]=\"tooth\"\n [conditions]=\"getConditions(tooth)\"\n [notation]=\"notation()\"\n [interactive]=\"mode() === 'interactive'\"\n [showTooltip]=\"showTooltip()\"\n [x]=\"toothX(tooth)\"\n [y]=\"upperAdultY()\"\n [size]=\"TOOTH_SIZE\"\n (toothClick)=\"toothClick.emit($event)\"\n (surfaceClick)=\"surfaceClick.emit($event)\"\n (toothHover)=\"toothHover.emit($event)\"\n (surfaceHover)=\"surfaceHover.emit($event)\"\n />\n }\n\n <!-- Upper primary arch -->\n @if (showPrimary()) {\n @for (tooth of upperPrimary(); track tooth.fdi) {\n <g ngx-tooth\n [definition]=\"tooth\"\n [conditions]=\"getConditions(tooth)\"\n [notation]=\"notation()\"\n [interactive]=\"mode() === 'interactive'\"\n [showTooltip]=\"showTooltip()\"\n [x]=\"primaryX(tooth)\"\n [y]=\"upperPrimaryY()\"\n [size]=\"TOOTH_SIZE\"\n (toothClick)=\"toothClick.emit($event)\"\n (surfaceClick)=\"surfaceClick.emit($event)\"\n (toothHover)=\"toothHover.emit($event)\"\n (surfaceHover)=\"surfaceHover.emit($event)\"\n />\n }\n }\n\n <!-- Arch midline -->\n <line\n [attr.x1]=\"PAD_X - 4\" [attr.y1]=\"midlineY()\"\n [attr.x2]=\"svgWidth() - PAD_X + 4\" [attr.y2]=\"midlineY()\"\n class=\"ngx-midline\"\n />\n\n <!-- Lower primary arch -->\n @if (showPrimary()) {\n @for (tooth of lowerPrimary(); track tooth.fdi) {\n <g ngx-tooth\n [definition]=\"tooth\"\n [conditions]=\"getConditions(tooth)\"\n [notation]=\"notation()\"\n [interactive]=\"mode() === 'interactive'\"\n [showTooltip]=\"showTooltip()\"\n [x]=\"primaryX(tooth)\"\n [y]=\"lowerPrimaryY()\"\n [size]=\"TOOTH_SIZE\"\n (toothClick)=\"toothClick.emit($event)\"\n (surfaceClick)=\"surfaceClick.emit($event)\"\n (toothHover)=\"toothHover.emit($event)\"\n (surfaceHover)=\"surfaceHover.emit($event)\"\n />\n }\n }\n\n <!-- Lower adult arch -->\n @for (tooth of lowerAdult(); track tooth.fdi) {\n <g ngx-tooth\n [definition]=\"tooth\"\n [conditions]=\"getConditions(tooth)\"\n [notation]=\"notation()\"\n [interactive]=\"mode() === 'interactive'\"\n [showTooltip]=\"showTooltip()\"\n [x]=\"toothX(tooth)\"\n [y]=\"lowerAdultY()\"\n [size]=\"TOOTH_SIZE\"\n (toothClick)=\"toothClick.emit($event)\"\n (surfaceClick)=\"surfaceClick.emit($event)\"\n (toothHover)=\"toothHover.emit($event)\"\n (surfaceHover)=\"surfaceHover.emit($event)\"\n />\n }\n </svg>\n `,\n styles: [`\n :host { display: inline-block; }\n\n .ngx-odontogram { font-family: sans-serif; }\n\n .ngx-midline {\n stroke: #bbb;\n stroke-width: 1;\n stroke-dasharray: 4 3;\n }\n `],\n})\nexport class OdontogramComponent {\n notation = input<Notation>('fdi');\n teeth = input<ToothData[]>([]);\n showPrimary = input<boolean>(true);\n mode = input<'view' | 'interactive'>('interactive');\n showTooltip = input<boolean>(true);\n\n toothClick = output<OdontogramEvent>();\n surfaceClick = output<OdontogramEvent>();\n toothHover = output<OdontogramEvent>();\n surfaceHover = output<OdontogramEvent>();\n\n readonly TOOTH_SIZE = TOOTH_SIZE;\n readonly PAD_X = PAD_X;\n\n upperAdult = computed(() =>\n ALL_TEETH.filter(t => t.type === 'adult' && t.quadrant.startsWith('upper'))\n .sort((a, b) => this.globalOrder(a) - this.globalOrder(b))\n );\n\n lowerAdult = computed(() =>\n ALL_TEETH.filter(t => t.type === 'adult' && t.quadrant.startsWith('lower'))\n .sort((a, b) => this.globalOrder(a) - this.globalOrder(b))\n );\n\n upperPrimary = computed(() =>\n ALL_TEETH.filter(t => t.type === 'primary' && t.quadrant.startsWith('upper'))\n .sort((a, b) => this.globalOrder(a) - this.globalOrder(b))\n );\n\n lowerPrimary = computed(() =>\n ALL_TEETH.filter(t => t.type === 'primary' && t.quadrant.startsWith('lower'))\n .sort((a, b) => this.globalOrder(a) - this.globalOrder(b))\n );\n\n /** X position of an adult tooth within the SVG */\n toothX(tooth: ToothDefinition): number {\n const isRight = tooth.quadrant.includes('right');\n if (isRight) {\n // Right quadrant: tooth order 1=innermost, 8=outermost → mirror left-to-right\n return PAD_X + (TEETH_PER_ARCH - tooth.order) * STEP;\n } else {\n // Left quadrant: tooth order 1=innermost\n return PAD_X + TEETH_PER_ARCH * STEP + (tooth.order - 1) * STEP;\n }\n }\n\n /** X position of a primary tooth — centered within the adult arch span */\n primaryX(tooth: ToothDefinition): number {\n const adultSpanStart = PAD_X + (TEETH_PER_ARCH - PRIMARY_PER_ARCH) * STEP / 2;\n const isRight = tooth.quadrant.includes('right');\n if (isRight) {\n return adultSpanStart + (PRIMARY_PER_ARCH - tooth.order) * STEP;\n } else {\n return PAD_X + TEETH_PER_ARCH * STEP + (adultSpanStart - PAD_X) + (tooth.order - 1) * STEP;\n }\n }\n\n upperAdultY = computed(() => PAD_Y);\n upperPrimaryY = computed(() => PAD_Y + TOOTH_SIZE + ROOT_H + ARCH_GAP);\n midlineY = computed(() => {\n const base = this.upperAdultY() + TOOTH_SIZE + ROOT_H + ARCH_GAP / 2;\n return this.showPrimary() ? this.upperPrimaryY() + TOOTH_SIZE + ROOT_H + ARCH_GAP / 2 : base;\n });\n lowerPrimaryY = computed(() => this.midlineY() + ARCH_GAP / 2);\n lowerAdultY = computed(() =>\n this.showPrimary()\n ? this.lowerPrimaryY() + TOOTH_SIZE + ROOT_H + ARCH_GAP\n : this.midlineY() + ARCH_GAP / 2\n );\n\n svgWidth = computed(() => PAD_X * 2 + TEETH_PER_ARCH * 2 * STEP - TOOTH_GAP);\n svgHeight = computed(() => {\n const bottom = this.lowerAdultY() + TOOTH_SIZE + ROOT_H + LABEL_H + PAD_Y;\n return bottom;\n });\n viewBox = computed(() => `0 0 ${this.svgWidth()} ${this.svgHeight()}`);\n\n getConditions(tooth: ToothDefinition): SurfaceCondition[] {\n const id = this.notation() === 'fdi' ? tooth.fdi : tooth.universal;\n return this.teeth().find(t => t.id === id)?.conditions ?? [];\n }\n\n private globalOrder(tooth: ToothDefinition): number {\n const isRight = tooth.quadrant.includes('right');\n // Left-to-right order across full arch: right quadrant reversed, then left quadrant\n return isRight ? TEETH_PER_ARCH - tooth.order : TEETH_PER_ARCH + tooth.order;\n }\n}\n","/*\n * Public API Surface of ngx-odontogram\n */\n\nexport * from './lib/models/odontogram.models';\nexport * from './lib/utils/tooth-definitions';\nexport * from './lib/components/tooth/tooth.component';\nexport * from './lib/components/odontogram/odontogram.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ngx-odontogram",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive SVG odontogram (dental chart) component for Angular. Supports FDI and Universal notation, adult and primary teeth, with full event-driven interactivity.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"odontogram",
|
|
8
|
+
"dental",
|
|
9
|
+
"tooth",
|
|
10
|
+
"chart",
|
|
11
|
+
"svg",
|
|
12
|
+
"fdi",
|
|
13
|
+
"universal"
|
|
14
|
+
],
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": ""
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@angular/common": ">=17.0.0",
|
|
23
|
+
"@angular/core": ">=17.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"tslib": "^2.3.0"
|
|
27
|
+
},
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"module": "fesm2022/ngx-odontogram.mjs",
|
|
30
|
+
"typings": "types/ngx-odontogram.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
"./package.json": {
|
|
33
|
+
"default": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./types/ngx-odontogram.d.ts",
|
|
37
|
+
"default": "./fesm2022/ngx-odontogram.mjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"type": "module"
|
|
41
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
|
|
3
|
+
type Notation = 'fdi' | 'universal';
|
|
4
|
+
type ToothType = 'adult' | 'primary';
|
|
5
|
+
type Surface = 'mesial' | 'distal' | 'occlusal' | 'lingual' | 'buccal' | 'incisal' | 'labial';
|
|
6
|
+
type Quadrant = 'upper-right' | 'upper-left' | 'lower-left' | 'lower-right';
|
|
7
|
+
interface SurfaceCondition {
|
|
8
|
+
surface: Surface;
|
|
9
|
+
condition: string;
|
|
10
|
+
color?: string;
|
|
11
|
+
}
|
|
12
|
+
interface ToothData {
|
|
13
|
+
/** FDI number (e.g. 16) or Universal number (e.g. 3) or Universal primary letter (e.g. 'A') */
|
|
14
|
+
id: number | string;
|
|
15
|
+
conditions: SurfaceCondition[];
|
|
16
|
+
}
|
|
17
|
+
interface OdontogramEvent {
|
|
18
|
+
toothId: number | string;
|
|
19
|
+
toothFdi: number;
|
|
20
|
+
toothType: ToothType;
|
|
21
|
+
surface?: Surface;
|
|
22
|
+
conditions: SurfaceCondition[];
|
|
23
|
+
mouseEvent: MouseEvent;
|
|
24
|
+
}
|
|
25
|
+
interface ToothDefinition {
|
|
26
|
+
fdi: number;
|
|
27
|
+
universal: number | string;
|
|
28
|
+
type: ToothType;
|
|
29
|
+
quadrant: Quadrant;
|
|
30
|
+
/** Whether this tooth has an occlusal surface (posterior) or incisal (anterior) */
|
|
31
|
+
isAnterior: boolean;
|
|
32
|
+
/** Number of roots to draw */
|
|
33
|
+
roots: number;
|
|
34
|
+
/** Display order left-to-right within its arch row */
|
|
35
|
+
order: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare const ALL_TEETH: ToothDefinition[];
|
|
39
|
+
declare function getToothByFdi(fdi: number): ToothDefinition | undefined;
|
|
40
|
+
declare function getToothByUniversal(id: number | string): ToothDefinition | undefined;
|
|
41
|
+
declare function getTeethByQuadrant(quadrant: string, type?: 'adult' | 'primary' | 'all'): ToothDefinition[];
|
|
42
|
+
|
|
43
|
+
declare class ToothComponent {
|
|
44
|
+
definition: _angular_core.InputSignal<ToothDefinition>;
|
|
45
|
+
conditions: _angular_core.InputSignal<SurfaceCondition[]>;
|
|
46
|
+
notation: _angular_core.InputSignal<Notation>;
|
|
47
|
+
interactive: _angular_core.InputSignal<boolean>;
|
|
48
|
+
showTooltip: _angular_core.InputSignal<boolean>;
|
|
49
|
+
x: _angular_core.InputSignal<number>;
|
|
50
|
+
y: _angular_core.InputSignal<number>;
|
|
51
|
+
size: _angular_core.InputSignal<number>;
|
|
52
|
+
toothClick: _angular_core.OutputEmitterRef<OdontogramEvent>;
|
|
53
|
+
surfaceClick: _angular_core.OutputEmitterRef<OdontogramEvent>;
|
|
54
|
+
toothHover: _angular_core.OutputEmitterRef<OdontogramEvent>;
|
|
55
|
+
surfaceHover: _angular_core.OutputEmitterRef<OdontogramEvent>;
|
|
56
|
+
hoveredSurface: _angular_core.WritableSignal<string | null>;
|
|
57
|
+
tooltipPos: _angular_core.WritableSignal<{
|
|
58
|
+
x: number;
|
|
59
|
+
y: number;
|
|
60
|
+
}>;
|
|
61
|
+
readonly TOOLTIP_W = 52;
|
|
62
|
+
readonly TOOLTIP_H = 16;
|
|
63
|
+
readonly TOOLTIP_R = 4;
|
|
64
|
+
onHostLeave(): void;
|
|
65
|
+
isAnterior: _angular_core.Signal<boolean>;
|
|
66
|
+
isUpperArch: _angular_core.Signal<boolean>;
|
|
67
|
+
label: _angular_core.Signal<string>;
|
|
68
|
+
crownRect: _angular_core.Signal<{
|
|
69
|
+
x: number;
|
|
70
|
+
y: number;
|
|
71
|
+
w: number;
|
|
72
|
+
h: number;
|
|
73
|
+
}>;
|
|
74
|
+
surfaces: _angular_core.Signal<{
|
|
75
|
+
buccal: string;
|
|
76
|
+
lingual: string;
|
|
77
|
+
mesial: string;
|
|
78
|
+
distal: string;
|
|
79
|
+
center: {
|
|
80
|
+
x: number;
|
|
81
|
+
y: number;
|
|
82
|
+
w: number;
|
|
83
|
+
h: number;
|
|
84
|
+
};
|
|
85
|
+
}>;
|
|
86
|
+
rootRects: _angular_core.Signal<{
|
|
87
|
+
x: number;
|
|
88
|
+
y: number;
|
|
89
|
+
width: number;
|
|
90
|
+
height: number;
|
|
91
|
+
}[]>;
|
|
92
|
+
labelPos: _angular_core.Signal<{
|
|
93
|
+
x: number;
|
|
94
|
+
y: number;
|
|
95
|
+
}>;
|
|
96
|
+
getSurface(surface: Surface): SurfaceCondition | undefined;
|
|
97
|
+
getSurfaceColor(surface: Surface): string | undefined;
|
|
98
|
+
private buildEvent;
|
|
99
|
+
onSurface(e: MouseEvent, surface: Surface): void;
|
|
100
|
+
onSurfaceHover(e: MouseEvent, surface: Surface): void;
|
|
101
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<ToothComponent, never>;
|
|
102
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<ToothComponent, "g[ngx-tooth]", never, { "definition": { "alias": "definition"; "required": true; "isSignal": true; }; "conditions": { "alias": "conditions"; "required": false; "isSignal": true; }; "notation": { "alias": "notation"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "showTooltip": { "alias": "showTooltip"; "required": false; "isSignal": true; }; "x": { "alias": "x"; "required": false; "isSignal": true; }; "y": { "alias": "y"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, { "toothClick": "toothClick"; "surfaceClick": "surfaceClick"; "toothHover": "toothHover"; "surfaceHover": "surfaceHover"; }, never, never, true, never>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
declare class OdontogramComponent {
|
|
106
|
+
notation: _angular_core.InputSignal<Notation>;
|
|
107
|
+
teeth: _angular_core.InputSignal<ToothData[]>;
|
|
108
|
+
showPrimary: _angular_core.InputSignal<boolean>;
|
|
109
|
+
mode: _angular_core.InputSignal<"view" | "interactive">;
|
|
110
|
+
showTooltip: _angular_core.InputSignal<boolean>;
|
|
111
|
+
toothClick: _angular_core.OutputEmitterRef<OdontogramEvent>;
|
|
112
|
+
surfaceClick: _angular_core.OutputEmitterRef<OdontogramEvent>;
|
|
113
|
+
toothHover: _angular_core.OutputEmitterRef<OdontogramEvent>;
|
|
114
|
+
surfaceHover: _angular_core.OutputEmitterRef<OdontogramEvent>;
|
|
115
|
+
readonly TOOTH_SIZE = 36;
|
|
116
|
+
readonly PAD_X = 16;
|
|
117
|
+
upperAdult: _angular_core.Signal<ToothDefinition[]>;
|
|
118
|
+
lowerAdult: _angular_core.Signal<ToothDefinition[]>;
|
|
119
|
+
upperPrimary: _angular_core.Signal<ToothDefinition[]>;
|
|
120
|
+
lowerPrimary: _angular_core.Signal<ToothDefinition[]>;
|
|
121
|
+
/** X position of an adult tooth within the SVG */
|
|
122
|
+
toothX(tooth: ToothDefinition): number;
|
|
123
|
+
/** X position of a primary tooth — centered within the adult arch span */
|
|
124
|
+
primaryX(tooth: ToothDefinition): number;
|
|
125
|
+
upperAdultY: _angular_core.Signal<number>;
|
|
126
|
+
upperPrimaryY: _angular_core.Signal<number>;
|
|
127
|
+
midlineY: _angular_core.Signal<number>;
|
|
128
|
+
lowerPrimaryY: _angular_core.Signal<number>;
|
|
129
|
+
lowerAdultY: _angular_core.Signal<number>;
|
|
130
|
+
svgWidth: _angular_core.Signal<number>;
|
|
131
|
+
svgHeight: _angular_core.Signal<number>;
|
|
132
|
+
viewBox: _angular_core.Signal<string>;
|
|
133
|
+
getConditions(tooth: ToothDefinition): SurfaceCondition[];
|
|
134
|
+
private globalOrder;
|
|
135
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<OdontogramComponent, never>;
|
|
136
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<OdontogramComponent, "ngx-odontogram", never, { "notation": { "alias": "notation"; "required": false; "isSignal": true; }; "teeth": { "alias": "teeth"; "required": false; "isSignal": true; }; "showPrimary": { "alias": "showPrimary"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "showTooltip": { "alias": "showTooltip"; "required": false; "isSignal": true; }; }, { "toothClick": "toothClick"; "surfaceClick": "surfaceClick"; "toothHover": "toothHover"; "surfaceHover": "surfaceHover"; }, never, never, true, never>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export { ALL_TEETH, OdontogramComponent, ToothComponent, getTeethByQuadrant, getToothByFdi, getToothByUniversal };
|
|
140
|
+
export type { Notation, OdontogramEvent, Quadrant, Surface, SurfaceCondition, ToothData, ToothDefinition, ToothType };
|