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 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 };