odontogram 0.1.2 → 0.2.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/dist/index.cjs ADDED
@@ -0,0 +1,310 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let lit = require("lit");
3
+ let lit_decorators_js = require("lit/decorators.js");
4
+
5
+ //#region \0@oxc-project+runtime@0.114.0/helpers/decorate.js
6
+ function __decorate(decorators, target, key, desc) {
7
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
8
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
9
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
10
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
11
+ }
12
+
13
+ //#endregion
14
+ //#region src/og-tooth.ts
15
+ let OgTooth = class OgTooth extends lit.LitElement {
16
+ constructor(..._args) {
17
+ super(..._args);
18
+ this.toothId = "";
19
+ this.colorClick = "#ff6961";
20
+ this.selections = {
21
+ vestibular: false,
22
+ distal: false,
23
+ palatine: false,
24
+ mesial: false,
25
+ occlusal: false
26
+ };
27
+ }
28
+ _toggle(surface, clickFn, unclickFn) {
29
+ const isSelected = !this.selections[surface];
30
+ this.selections = {
31
+ ...this.selections,
32
+ [surface]: isSelected
33
+ };
34
+ const eventName = isSelected ? clickFn : unclickFn;
35
+ this.dispatchEvent(new CustomEvent(eventName, {
36
+ detail: {
37
+ toothId: this.toothId,
38
+ surface,
39
+ state: isSelected
40
+ },
41
+ bubbles: true,
42
+ composed: true
43
+ }));
44
+ }
45
+ render() {
46
+ return lit.html`
47
+ <div class="tooth-container">
48
+ <span class="label">${this.toothId}</span>
49
+ <div class="tooth-relative">
50
+ <div class="surface top ${this.selections.vestibular ? "selected" : ""}"
51
+ @click=${() => this._toggle("vestibular", "vestibularC", "vestibularU")}></div>
52
+
53
+ <div class="surface left ${this.selections.distal ? "selected" : ""}"
54
+ @click=${() => this._toggle("distal", "distalC", "distalU")}></div>
55
+
56
+ <div class="surface bottom ${this.selections.palatine ? "selected" : ""}"
57
+ @click=${() => this._toggle("palatine", "palatineC", "palatineU")}></div>
58
+
59
+ <div class="surface right ${this.selections.mesial ? "selected" : ""}"
60
+ @click=${() => this._toggle("mesial", "mesialC", "mesialU")}></div>
61
+
62
+ <div class="surface center ${this.selections.occlusal ? "selected" : ""}"
63
+ @click=${() => this._toggle("occlusal", "occlusalC", "occlusalU")}></div>
64
+ </div>
65
+ </div>
66
+ `;
67
+ }
68
+ static {
69
+ this.styles = lit.css`
70
+ .tooth-container { display: flex; flex-direction: column; align-items: center; width: 50px; }
71
+ .label { font-size: 12px; margin-bottom: 5px; font-weight: bold; color: #333; }
72
+ .tooth-relative { position: relative; width: 44px; height: 44px; background: #eee; }
73
+ .surface {
74
+ position: absolute; width: 20px; height: 20px; outline: 2px solid #000;
75
+ background-color: #fff; cursor: pointer; box-sizing: border-box; transition: background-color 0.2s;
76
+ }
77
+ .selected { background-color: var(--click-color, #ff6961) !important; }
78
+ .top { top: 0; left: 0; border-top-left-radius: 100%; }
79
+ .left { bottom: 0; left: 0; border-bottom-left-radius: 100%; }
80
+ .bottom { bottom: 0; right: 0; border-bottom-right-radius: 100%; }
81
+ .right { top: 0; right: 0; border-top-right-radius: 100%; }
82
+ .center { top: 25%; right: 25%; border-radius: 50%; z-index: 2; }
83
+ `;
84
+ }
85
+ };
86
+ __decorate([(0, lit_decorators_js.property)({ type: String })], OgTooth.prototype, "toothId", void 0);
87
+ __decorate([(0, lit_decorators_js.property)({ type: String })], OgTooth.prototype, "colorClick", void 0);
88
+ __decorate([(0, lit_decorators_js.property)({ type: Object })], OgTooth.prototype, "selections", void 0);
89
+ OgTooth = __decorate([(0, lit_decorators_js.customElement)("og-tooth")], OgTooth);
90
+
91
+ //#endregion
92
+ //#region src/og-odontogram.ts
93
+ let OgOdontogram = class OgOdontogram extends lit.LitElement {
94
+ constructor(..._args) {
95
+ super(..._args);
96
+ this.mode = "adult";
97
+ this.chartData = {};
98
+ this.teethState = {};
99
+ this.layouts = {
100
+ adult: {
101
+ upperRight: [
102
+ 18,
103
+ 17,
104
+ 16,
105
+ 15,
106
+ 14,
107
+ 13,
108
+ 12,
109
+ 11
110
+ ],
111
+ upperLeft: [
112
+ 21,
113
+ 22,
114
+ 23,
115
+ 24,
116
+ 25,
117
+ 26,
118
+ 27,
119
+ 28
120
+ ],
121
+ lowerRight: [
122
+ 48,
123
+ 47,
124
+ 46,
125
+ 45,
126
+ 44,
127
+ 43,
128
+ 42,
129
+ 41
130
+ ],
131
+ lowerLeft: [
132
+ 31,
133
+ 32,
134
+ 33,
135
+ 34,
136
+ 35,
137
+ 36,
138
+ 37,
139
+ 38
140
+ ]
141
+ },
142
+ baby: {
143
+ upperRight: [
144
+ 55,
145
+ 54,
146
+ 53,
147
+ 52,
148
+ 51
149
+ ],
150
+ upperLeft: [
151
+ 61,
152
+ 62,
153
+ 63,
154
+ 64,
155
+ 65
156
+ ],
157
+ lowerRight: [
158
+ 85,
159
+ 84,
160
+ 83,
161
+ 82,
162
+ 81
163
+ ],
164
+ lowerLeft: [
165
+ 71,
166
+ 72,
167
+ 73,
168
+ 74,
169
+ 75
170
+ ]
171
+ },
172
+ old: {
173
+ upperRight: [
174
+ 17,
175
+ 16,
176
+ 15,
177
+ 14,
178
+ 13,
179
+ 12,
180
+ 11
181
+ ],
182
+ upperLeft: [
183
+ 21,
184
+ 22,
185
+ 23,
186
+ 24,
187
+ 25,
188
+ 26,
189
+ 27
190
+ ],
191
+ lowerRight: [
192
+ 47,
193
+ 46,
194
+ 45,
195
+ 44,
196
+ 43,
197
+ 42,
198
+ 41
199
+ ],
200
+ lowerLeft: [
201
+ 31,
202
+ 32,
203
+ 33,
204
+ 34,
205
+ 35,
206
+ 36,
207
+ 37
208
+ ]
209
+ }
210
+ };
211
+ }
212
+ willUpdate(changedProperties) {
213
+ if (changedProperties.has("chartData")) this.teethState = { ...this.chartData };
214
+ }
215
+ _updateState(id, region, value) {
216
+ const toothId = id.toString();
217
+ const newState = { ...this.teethState };
218
+ if (!newState[toothId]) newState[toothId] = {
219
+ vestibular: false,
220
+ distal: false,
221
+ palatine: false,
222
+ mesial: false,
223
+ occlusal: false
224
+ };
225
+ newState[toothId] = {
226
+ ...newState[toothId],
227
+ [region]: value
228
+ };
229
+ this.teethState = newState;
230
+ this.dispatchEvent(new CustomEvent("odontogram-change", {
231
+ detail: {
232
+ data: this.teethState,
233
+ mode: this.mode
234
+ },
235
+ bubbles: true,
236
+ composed: true
237
+ }));
238
+ }
239
+ renderTooth(id) {
240
+ const state = this.teethState[id.toString()] || {
241
+ vestibular: false,
242
+ distal: false,
243
+ palatine: false,
244
+ mesial: false,
245
+ occlusal: false
246
+ };
247
+ return lit.html`
248
+ <og-tooth
249
+ .toothId=${id.toString()}
250
+ .selections=${state}
251
+ @vestibularC=${() => this._updateState(id, "vestibular", true)}
252
+ @vestibularU=${() => this._updateState(id, "vestibular", false)}
253
+ @distalC=${() => this._updateState(id, "distal", true)}
254
+ @distalU=${() => this._updateState(id, "distal", false)}
255
+ @palatineC=${() => this._updateState(id, "palatine", true)}
256
+ @palatineU=${() => this._updateState(id, "palatine", false)}
257
+ @mesialC=${() => this._updateState(id, "mesial", true)}
258
+ @mesialU=${() => this._updateState(id, "mesial", false)}
259
+ @occlusalC=${() => this._updateState(id, "occlusal", true)}
260
+ @occlusalU=${() => this._updateState(id, "occlusal", false)}
261
+ ></og-tooth>
262
+ `;
263
+ }
264
+ render() {
265
+ const layout = this.layouts[this.mode] || this.layouts.adult;
266
+ return lit.html`
267
+ <div class="odontogram-wrapper mode-${this.mode}">
268
+ <div class="arch">
269
+ <div class="quadrant">${layout.upperRight.map((id) => this.renderTooth(id))}</div>
270
+ <div class="quadrant">${layout.upperLeft.map((id) => this.renderTooth(id))}</div>
271
+ </div>
272
+ <div class="arch">
273
+ <div class="quadrant">${layout.lowerRight.map((id) => this.renderTooth(id))}</div>
274
+ <div class="quadrant">${layout.lowerLeft.map((id) => this.renderTooth(id))}</div>
275
+ </div>
276
+ </div>
277
+ `;
278
+ }
279
+ static {
280
+ this.styles = lit.css`
281
+ :host { display: block; padding: 20px; }
282
+ .odontogram-wrapper { display: flex; flex-direction: column; gap: 40px; align-items: center; }
283
+ .arch { display: flex; gap: 40px; }
284
+ .quadrant { display: flex; gap: 4px; }
285
+
286
+ /* You can add specific colors or spacing per mode if you want */
287
+ .mode-baby { gap: 20px; }
288
+ .mode-baby og-tooth { transform: scale(0.9); }
289
+ `;
290
+ }
291
+ };
292
+ __decorate([(0, lit_decorators_js.property)({ type: String })], OgOdontogram.prototype, "mode", void 0);
293
+ __decorate([(0, lit_decorators_js.property)({ type: Object })], OgOdontogram.prototype, "chartData", void 0);
294
+ __decorate([(0, lit_decorators_js.state)()], OgOdontogram.prototype, "teethState", void 0);
295
+ OgOdontogram = __decorate([(0, lit_decorators_js.customElement)("og-odontogram")], OgOdontogram);
296
+
297
+ //#endregion
298
+ Object.defineProperty(exports, 'OgOdontogram', {
299
+ enumerable: true,
300
+ get: function () {
301
+ return OgOdontogram;
302
+ }
303
+ });
304
+ Object.defineProperty(exports, 'OgTooth', {
305
+ enumerable: true,
306
+ get: function () {
307
+ return OgTooth;
308
+ }
309
+ });
310
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["LitElement","LitElement"],"sources":["../src/og-tooth.ts","../src/og-odontogram.ts"],"sourcesContent":["import { LitElement, css, html } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\n\n// Shared Interface for Type Safety\nexport interface ToothSurfaces {\n vestibular: boolean;\n distal: boolean;\n palatine: boolean;\n mesial: boolean;\n occlusal: boolean;\n}\n\n@customElement('og-tooth')\nexport class OgTooth extends LitElement {\n @property({ type: String }) toothId = '';\n @property({ type: String }) colorClick = '#ff6961';\n\n @property({ type: Object })\n selections: ToothSurfaces = {\n vestibular: false, distal: false, palatine: false, mesial: false, occlusal: false\n };\n\n private _toggle(surface: keyof ToothSurfaces, clickFn: string, unclickFn: string) {\n const isSelected = !this.selections[surface];\n\n // Create new object for Lit reactivity\n this.selections = { ...this.selections, [surface]: isSelected };\n\n const eventName = isSelected ? clickFn : unclickFn;\n this.dispatchEvent(new CustomEvent(eventName, {\n detail: { toothId: this.toothId, surface, state: isSelected },\n bubbles: true,\n composed: true\n }));\n }\n\n render() {\n return html`\n <div class=\"tooth-container\">\n <span class=\"label\">${this.toothId}</span>\n <div class=\"tooth-relative\">\n <div class=\"surface top ${this.selections.vestibular ? 'selected' : ''}\" \n @click=${() => this._toggle('vestibular', 'vestibularC', 'vestibularU')}></div>\n \n <div class=\"surface left ${this.selections.distal ? 'selected' : ''}\" \n @click=${() => this._toggle('distal', 'distalC', 'distalU')}></div>\n\n <div class=\"surface bottom ${this.selections.palatine ? 'selected' : ''}\" \n @click=${() => this._toggle('palatine', 'palatineC', 'palatineU')}></div>\n\n <div class=\"surface right ${this.selections.mesial ? 'selected' : ''}\" \n @click=${() => this._toggle('mesial', 'mesialC', 'mesialU')}></div>\n\n <div class=\"surface center ${this.selections.occlusal ? 'selected' : ''}\" \n @click=${() => this._toggle('occlusal', 'occlusalC', 'occlusalU')}></div>\n </div>\n </div>\n `;\n }\n\n static styles = css`\n .tooth-container { display: flex; flex-direction: column; align-items: center; width: 50px; }\n .label { font-size: 12px; margin-bottom: 5px; font-weight: bold; color: #333; }\n .tooth-relative { position: relative; width: 44px; height: 44px; background: #eee; }\n .surface {\n position: absolute; width: 20px; height: 20px; outline: 2px solid #000;\n background-color: #fff; cursor: pointer; box-sizing: border-box; transition: background-color 0.2s;\n }\n .selected { background-color: var(--click-color, #ff6961) !important; }\n .top { top: 0; left: 0; border-top-left-radius: 100%; }\n .left { bottom: 0; left: 0; border-bottom-left-radius: 100%; }\n .bottom { bottom: 0; right: 0; border-bottom-right-radius: 100%; }\n .right { top: 0; right: 0; border-top-right-radius: 100%; }\n .center { top: 25%; right: 25%; border-radius: 50%; z-index: 2; }\n `;\n}","import { LitElement, css, html } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport type { ToothSurfaces } from './og-tooth';\nimport './og-tooth';\n\nexport type PatientMode = 'adult' | 'baby' | 'old';\n\n@customElement('og-odontogram')\nexport class OgOdontogram extends LitElement {\n @property({ type: String }) mode: PatientMode = 'adult';\n @property({ type: Object }) chartData: Record<string, ToothSurfaces> = {};\n @state() private teethState: Record<string, ToothSurfaces> = {};\n\n // FDI Tooth Layouts\n private layouts = {\n adult: {\n upperRight: [18, 17, 16, 15, 14, 13, 12, 11],\n upperLeft: [21, 22, 23, 24, 25, 26, 27, 28],\n lowerRight: [48, 47, 46, 45, 44, 43, 42, 41],\n lowerLeft: [31, 32, 33, 34, 35, 36, 37, 38]\n },\n baby: {\n upperRight: [55, 54, 53, 52, 51],\n upperLeft: [61, 62, 63, 64, 65],\n lowerRight: [85, 84, 83, 82, 81],\n lowerLeft: [71, 72, 73, 74, 75]\n },\n old: {\n // Typically adult layout, but maybe we exclude wisdom teeth (18, 28, 38, 48)\n upperRight: [17, 16, 15, 14, 13, 12, 11],\n upperLeft: [21, 22, 23, 24, 25, 26, 27],\n lowerRight: [47, 46, 45, 44, 43, 42, 41],\n lowerLeft: [31, 32, 33, 34, 35, 36, 37]\n }\n };\n\n willUpdate(changedProperties: Map<string, any>) {\n if (changedProperties.has('chartData')) {\n this.teethState = { ...this.chartData };\n }\n }\n\n private _updateState(id: number, region: keyof ToothSurfaces, value: boolean) {\n const toothId = id.toString();\n const newState = { ...this.teethState };\n if (!newState[toothId]) {\n newState[toothId] = { vestibular: false, distal: false, palatine: false, mesial: false, occlusal: false };\n }\n newState[toothId] = { ...newState[toothId], [region]: value };\n this.teethState = newState;\n\n this.dispatchEvent(new CustomEvent('odontogram-change', {\n detail: { data: this.teethState, mode: this.mode },\n bubbles: true,\n composed: true\n }));\n }\n\n renderTooth(id: number) {\n const state = this.teethState[id.toString()] || {\n vestibular: false, distal: false, palatine: false, mesial: false, occlusal: false\n };\n\n return html`\n <og-tooth \n .toothId=${id.toString()} \n .selections=${state}\n @vestibularC=${() => this._updateState(id, 'vestibular', true)}\n @vestibularU=${() => this._updateState(id, 'vestibular', false)}\n @distalC=${() => this._updateState(id, 'distal', true)}\n @distalU=${() => this._updateState(id, 'distal', false)}\n @palatineC=${() => this._updateState(id, 'palatine', true)}\n @palatineU=${() => this._updateState(id, 'palatine', false)}\n @mesialC=${() => this._updateState(id, 'mesial', true)}\n @mesialU=${() => this._updateState(id, 'mesial', false)}\n @occlusalC=${() => this._updateState(id, 'occlusal', true)}\n @occlusalU=${() => this._updateState(id, 'occlusal', false)}\n ></og-tooth>\n `;\n }\n\n render() {\n const layout = this.layouts[this.mode] || this.layouts.adult;\n\n return html`\n <div class=\"odontogram-wrapper mode-${this.mode}\">\n <div class=\"arch\">\n <div class=\"quadrant\">${layout.upperRight.map(id => this.renderTooth(id))}</div>\n <div class=\"quadrant\">${layout.upperLeft.map(id => this.renderTooth(id))}</div>\n </div>\n <div class=\"arch\">\n <div class=\"quadrant\">${layout.lowerRight.map(id => this.renderTooth(id))}</div>\n <div class=\"quadrant\">${layout.lowerLeft.map(id => this.renderTooth(id))}</div>\n </div>\n </div>\n `;\n }\n\n static styles = css`\n :host { display: block; padding: 20px; }\n .odontogram-wrapper { display: flex; flex-direction: column; gap: 40px; align-items: center; }\n .arch { display: flex; gap: 40px; }\n .quadrant { display: flex; gap: 4px; }\n \n /* You can add specific colors or spacing per mode if you want */\n .mode-baby { gap: 20px; }\n .mode-baby og-tooth { transform: scale(0.9); }\n `;\n}"],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import * as lit from "lit";
2
+ import { LitElement } from "lit";
3
+
4
+ //#region node_modules/lit-html/development/lit-html.d.ts
5
+ /** TemplateResult types */
6
+ declare const HTML_RESULT = 1;
7
+ declare const SVG_RESULT = 2;
8
+ declare const MATHML_RESULT = 3;
9
+ type ResultType = typeof HTML_RESULT | typeof SVG_RESULT | typeof MATHML_RESULT;
10
+ /**
11
+ * The return type of the template tag functions, {@linkcode html} and
12
+ * {@linkcode svg} when it hasn't been compiled by @lit-labs/compiler.
13
+ *
14
+ * A `TemplateResult` object holds all the information about a template
15
+ * expression required to render it: the template strings, expression values,
16
+ * and type of template (html or svg).
17
+ *
18
+ * `TemplateResult` objects do not create any DOM on their own. To create or
19
+ * update DOM you need to render the `TemplateResult`. See
20
+ * [Rendering](https://lit.dev/docs/components/rendering) for more information.
21
+ *
22
+ */
23
+ type UncompiledTemplateResult<T extends ResultType = ResultType> = {
24
+ ['_$litType$']: T;
25
+ strings: TemplateStringsArray;
26
+ values: unknown[];
27
+ };
28
+ /**
29
+ * The return type of the template tag functions, {@linkcode html} and
30
+ * {@linkcode svg}.
31
+ *
32
+ * A `TemplateResult` object holds all the information about a template
33
+ * expression required to render it: the template strings, expression values,
34
+ * and type of template (html or svg).
35
+ *
36
+ * `TemplateResult` objects do not create any DOM on their own. To create or
37
+ * update DOM you need to render the `TemplateResult`. See
38
+ * [Rendering](https://lit.dev/docs/components/rendering) for more information.
39
+ *
40
+ * In Lit 4, this type will be an alias of
41
+ * MaybeCompiledTemplateResult, so that code will get type errors if it assumes
42
+ * that Lit templates are not compiled. When deliberately working with only
43
+ * one, use either {@linkcode CompiledTemplateResult} or
44
+ * {@linkcode UncompiledTemplateResult} explicitly.
45
+ */
46
+ type TemplateResult<T extends ResultType = ResultType> = UncompiledTemplateResult<T>;
47
+ //#endregion
48
+ //#region src/og-tooth.d.ts
49
+ interface ToothSurfaces {
50
+ vestibular: boolean;
51
+ distal: boolean;
52
+ palatine: boolean;
53
+ mesial: boolean;
54
+ occlusal: boolean;
55
+ }
56
+ declare class OgTooth extends LitElement {
57
+ toothId: string;
58
+ colorClick: string;
59
+ selections: ToothSurfaces;
60
+ private _toggle;
61
+ render(): TemplateResult<1>;
62
+ static styles: lit.CSSResult;
63
+ }
64
+ //#endregion
65
+ //#region src/og-odontogram.d.ts
66
+ type PatientMode = 'adult' | 'baby' | 'old';
67
+ declare class OgOdontogram extends LitElement {
68
+ mode: PatientMode;
69
+ chartData: Record<string, ToothSurfaces>;
70
+ private teethState;
71
+ private layouts;
72
+ willUpdate(changedProperties: Map<string, any>): void;
73
+ private _updateState;
74
+ renderTooth(id: number): TemplateResult<1>;
75
+ render(): TemplateResult<1>;
76
+ static styles: lit.CSSResult;
77
+ }
78
+ //#endregion
79
+ export { OgOdontogram, OgTooth, PatientMode, ToothSurfaces };
80
+ //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -1,6 +1,50 @@
1
1
  import * as lit from "lit";
2
2
  import { LitElement } from "lit";
3
3
 
4
+ //#region node_modules/lit-html/development/lit-html.d.ts
5
+ /** TemplateResult types */
6
+ declare const HTML_RESULT = 1;
7
+ declare const SVG_RESULT = 2;
8
+ declare const MATHML_RESULT = 3;
9
+ type ResultType = typeof HTML_RESULT | typeof SVG_RESULT | typeof MATHML_RESULT;
10
+ /**
11
+ * The return type of the template tag functions, {@linkcode html} and
12
+ * {@linkcode svg} when it hasn't been compiled by @lit-labs/compiler.
13
+ *
14
+ * A `TemplateResult` object holds all the information about a template
15
+ * expression required to render it: the template strings, expression values,
16
+ * and type of template (html or svg).
17
+ *
18
+ * `TemplateResult` objects do not create any DOM on their own. To create or
19
+ * update DOM you need to render the `TemplateResult`. See
20
+ * [Rendering](https://lit.dev/docs/components/rendering) for more information.
21
+ *
22
+ */
23
+ type UncompiledTemplateResult<T extends ResultType = ResultType> = {
24
+ ['_$litType$']: T;
25
+ strings: TemplateStringsArray;
26
+ values: unknown[];
27
+ };
28
+ /**
29
+ * The return type of the template tag functions, {@linkcode html} and
30
+ * {@linkcode svg}.
31
+ *
32
+ * A `TemplateResult` object holds all the information about a template
33
+ * expression required to render it: the template strings, expression values,
34
+ * and type of template (html or svg).
35
+ *
36
+ * `TemplateResult` objects do not create any DOM on their own. To create or
37
+ * update DOM you need to render the `TemplateResult`. See
38
+ * [Rendering](https://lit.dev/docs/components/rendering) for more information.
39
+ *
40
+ * In Lit 4, this type will be an alias of
41
+ * MaybeCompiledTemplateResult, so that code will get type errors if it assumes
42
+ * that Lit templates are not compiled. When deliberately working with only
43
+ * one, use either {@linkcode CompiledTemplateResult} or
44
+ * {@linkcode UncompiledTemplateResult} explicitly.
45
+ */
46
+ type TemplateResult<T extends ResultType = ResultType> = UncompiledTemplateResult<T>;
47
+ //#endregion
4
48
  //#region src/og-tooth.d.ts
5
49
  interface ToothSurfaces {
6
50
  vestibular: boolean;
@@ -14,22 +58,23 @@ declare class OgTooth extends LitElement {
14
58
  colorClick: string;
15
59
  selections: ToothSurfaces;
16
60
  private _toggle;
17
- render(): lit.TemplateResult<1>;
61
+ render(): TemplateResult<1>;
18
62
  static styles: lit.CSSResult;
19
63
  }
20
64
  //#endregion
21
65
  //#region src/og-odontogram.d.ts
22
66
  type PatientMode = 'adult' | 'baby' | 'old';
23
- declare class OgDontogram extends LitElement {
67
+ declare class OgOdontogram extends LitElement {
24
68
  mode: PatientMode;
25
69
  chartData: Record<string, ToothSurfaces>;
26
70
  private teethState;
27
71
  private layouts;
28
72
  willUpdate(changedProperties: Map<string, any>): void;
29
73
  private _updateState;
30
- renderTooth(id: number): lit.TemplateResult<1>;
31
- render(): lit.TemplateResult<1>;
74
+ renderTooth(id: number): TemplateResult<1>;
75
+ render(): TemplateResult<1>;
32
76
  static styles: lit.CSSResult;
33
77
  }
34
78
  //#endregion
35
- export { OgDontogram, OgTooth, PatientMode, ToothSurfaces };
79
+ export { OgOdontogram, OgTooth, PatientMode, ToothSurfaces };
80
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -89,7 +89,7 @@ OgTooth = __decorate([customElement("og-tooth")], OgTooth);
89
89
 
90
90
  //#endregion
91
91
  //#region src/og-odontogram.ts
92
- let OgDontogram = class OgDontogram extends LitElement {
92
+ let OgOdontogram = class OgOdontogram extends LitElement {
93
93
  constructor(..._args) {
94
94
  super(..._args);
95
95
  this.mode = "adult";
@@ -288,10 +288,11 @@ let OgDontogram = class OgDontogram extends LitElement {
288
288
  `;
289
289
  }
290
290
  };
291
- __decorate([property({ type: String })], OgDontogram.prototype, "mode", void 0);
292
- __decorate([property({ type: Object })], OgDontogram.prototype, "chartData", void 0);
293
- __decorate([state()], OgDontogram.prototype, "teethState", void 0);
294
- OgDontogram = __decorate([customElement("og-dontogram")], OgDontogram);
291
+ __decorate([property({ type: String })], OgOdontogram.prototype, "mode", void 0);
292
+ __decorate([property({ type: Object })], OgOdontogram.prototype, "chartData", void 0);
293
+ __decorate([state()], OgOdontogram.prototype, "teethState", void 0);
294
+ OgOdontogram = __decorate([customElement("og-odontogram")], OgOdontogram);
295
295
 
296
296
  //#endregion
297
- export { OgDontogram, OgTooth };
297
+ export { OgOdontogram, OgTooth };
298
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/og-tooth.ts","../src/og-odontogram.ts"],"sourcesContent":["import { LitElement, css, html } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\n\n// Shared Interface for Type Safety\nexport interface ToothSurfaces {\n vestibular: boolean;\n distal: boolean;\n palatine: boolean;\n mesial: boolean;\n occlusal: boolean;\n}\n\n@customElement('og-tooth')\nexport class OgTooth extends LitElement {\n @property({ type: String }) toothId = '';\n @property({ type: String }) colorClick = '#ff6961';\n\n @property({ type: Object })\n selections: ToothSurfaces = {\n vestibular: false, distal: false, palatine: false, mesial: false, occlusal: false\n };\n\n private _toggle(surface: keyof ToothSurfaces, clickFn: string, unclickFn: string) {\n const isSelected = !this.selections[surface];\n\n // Create new object for Lit reactivity\n this.selections = { ...this.selections, [surface]: isSelected };\n\n const eventName = isSelected ? clickFn : unclickFn;\n this.dispatchEvent(new CustomEvent(eventName, {\n detail: { toothId: this.toothId, surface, state: isSelected },\n bubbles: true,\n composed: true\n }));\n }\n\n render() {\n return html`\n <div class=\"tooth-container\">\n <span class=\"label\">${this.toothId}</span>\n <div class=\"tooth-relative\">\n <div class=\"surface top ${this.selections.vestibular ? 'selected' : ''}\" \n @click=${() => this._toggle('vestibular', 'vestibularC', 'vestibularU')}></div>\n \n <div class=\"surface left ${this.selections.distal ? 'selected' : ''}\" \n @click=${() => this._toggle('distal', 'distalC', 'distalU')}></div>\n\n <div class=\"surface bottom ${this.selections.palatine ? 'selected' : ''}\" \n @click=${() => this._toggle('palatine', 'palatineC', 'palatineU')}></div>\n\n <div class=\"surface right ${this.selections.mesial ? 'selected' : ''}\" \n @click=${() => this._toggle('mesial', 'mesialC', 'mesialU')}></div>\n\n <div class=\"surface center ${this.selections.occlusal ? 'selected' : ''}\" \n @click=${() => this._toggle('occlusal', 'occlusalC', 'occlusalU')}></div>\n </div>\n </div>\n `;\n }\n\n static styles = css`\n .tooth-container { display: flex; flex-direction: column; align-items: center; width: 50px; }\n .label { font-size: 12px; margin-bottom: 5px; font-weight: bold; color: #333; }\n .tooth-relative { position: relative; width: 44px; height: 44px; background: #eee; }\n .surface {\n position: absolute; width: 20px; height: 20px; outline: 2px solid #000;\n background-color: #fff; cursor: pointer; box-sizing: border-box; transition: background-color 0.2s;\n }\n .selected { background-color: var(--click-color, #ff6961) !important; }\n .top { top: 0; left: 0; border-top-left-radius: 100%; }\n .left { bottom: 0; left: 0; border-bottom-left-radius: 100%; }\n .bottom { bottom: 0; right: 0; border-bottom-right-radius: 100%; }\n .right { top: 0; right: 0; border-top-right-radius: 100%; }\n .center { top: 25%; right: 25%; border-radius: 50%; z-index: 2; }\n `;\n}","import { LitElement, css, html } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport type { ToothSurfaces } from './og-tooth';\nimport './og-tooth';\n\nexport type PatientMode = 'adult' | 'baby' | 'old';\n\n@customElement('og-odontogram')\nexport class OgOdontogram extends LitElement {\n @property({ type: String }) mode: PatientMode = 'adult';\n @property({ type: Object }) chartData: Record<string, ToothSurfaces> = {};\n @state() private teethState: Record<string, ToothSurfaces> = {};\n\n // FDI Tooth Layouts\n private layouts = {\n adult: {\n upperRight: [18, 17, 16, 15, 14, 13, 12, 11],\n upperLeft: [21, 22, 23, 24, 25, 26, 27, 28],\n lowerRight: [48, 47, 46, 45, 44, 43, 42, 41],\n lowerLeft: [31, 32, 33, 34, 35, 36, 37, 38]\n },\n baby: {\n upperRight: [55, 54, 53, 52, 51],\n upperLeft: [61, 62, 63, 64, 65],\n lowerRight: [85, 84, 83, 82, 81],\n lowerLeft: [71, 72, 73, 74, 75]\n },\n old: {\n // Typically adult layout, but maybe we exclude wisdom teeth (18, 28, 38, 48)\n upperRight: [17, 16, 15, 14, 13, 12, 11],\n upperLeft: [21, 22, 23, 24, 25, 26, 27],\n lowerRight: [47, 46, 45, 44, 43, 42, 41],\n lowerLeft: [31, 32, 33, 34, 35, 36, 37]\n }\n };\n\n willUpdate(changedProperties: Map<string, any>) {\n if (changedProperties.has('chartData')) {\n this.teethState = { ...this.chartData };\n }\n }\n\n private _updateState(id: number, region: keyof ToothSurfaces, value: boolean) {\n const toothId = id.toString();\n const newState = { ...this.teethState };\n if (!newState[toothId]) {\n newState[toothId] = { vestibular: false, distal: false, palatine: false, mesial: false, occlusal: false };\n }\n newState[toothId] = { ...newState[toothId], [region]: value };\n this.teethState = newState;\n\n this.dispatchEvent(new CustomEvent('odontogram-change', {\n detail: { data: this.teethState, mode: this.mode },\n bubbles: true,\n composed: true\n }));\n }\n\n renderTooth(id: number) {\n const state = this.teethState[id.toString()] || {\n vestibular: false, distal: false, palatine: false, mesial: false, occlusal: false\n };\n\n return html`\n <og-tooth \n .toothId=${id.toString()} \n .selections=${state}\n @vestibularC=${() => this._updateState(id, 'vestibular', true)}\n @vestibularU=${() => this._updateState(id, 'vestibular', false)}\n @distalC=${() => this._updateState(id, 'distal', true)}\n @distalU=${() => this._updateState(id, 'distal', false)}\n @palatineC=${() => this._updateState(id, 'palatine', true)}\n @palatineU=${() => this._updateState(id, 'palatine', false)}\n @mesialC=${() => this._updateState(id, 'mesial', true)}\n @mesialU=${() => this._updateState(id, 'mesial', false)}\n @occlusalC=${() => this._updateState(id, 'occlusal', true)}\n @occlusalU=${() => this._updateState(id, 'occlusal', false)}\n ></og-tooth>\n `;\n }\n\n render() {\n const layout = this.layouts[this.mode] || this.layouts.adult;\n\n return html`\n <div class=\"odontogram-wrapper mode-${this.mode}\">\n <div class=\"arch\">\n <div class=\"quadrant\">${layout.upperRight.map(id => this.renderTooth(id))}</div>\n <div class=\"quadrant\">${layout.upperLeft.map(id => this.renderTooth(id))}</div>\n </div>\n <div class=\"arch\">\n <div class=\"quadrant\">${layout.lowerRight.map(id => this.renderTooth(id))}</div>\n <div class=\"quadrant\">${layout.lowerLeft.map(id => this.renderTooth(id))}</div>\n </div>\n </div>\n `;\n }\n\n static styles = css`\n :host { display: block; padding: 20px; }\n .odontogram-wrapper { display: flex; flex-direction: column; gap: 40px; align-items: center; }\n .arch { display: flex; gap: 40px; }\n .quadrant { display: flex; gap: 4px; }\n \n /* You can add specific colors or spacing per mode if you want */\n .mode-baby { gap: 20px; }\n .mode-baby og-tooth { transform: scale(0.9); }\n `;\n}"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "odontogram",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "A lightweight, interactive web component odontogram built with Lit.",
5
5
  "main": "./dist/index.mjs",
6
6
  "module": "./dist/index.mjs",
@@ -33,17 +33,32 @@
33
33
  ],
34
34
  "repository": {
35
35
  "type": "git",
36
- "url": "https://github.com/biomathcode/odontogram"
36
+ "url": "https://github.com/biomathcode/odontogram.git"
37
+ },
38
+ "release-it": {
39
+ "git": {
40
+ "commitMessage": "chore(release): v${version}"
41
+ },
42
+ "github": {
43
+ "release": true
44
+ },
45
+ "npm": {
46
+ "publish": false
47
+ }
37
48
  },
38
49
  "type": "module",
39
50
  "scripts": {
40
- "dev": "vite",
41
- "build": "tsdown",
51
+ "dev": "pnpm run storybook",
52
+ "build": "tsdown --format esm --format cjs",
53
+ "build:check": "tsdown --publint",
42
54
  "build:vite": "tsc && vite build",
55
+ "release": "pnpm build && pnpm release-it",
43
56
  "preview": "vite preview",
44
57
  "storybook": "storybook dev -p 6006",
45
58
  "build-storybook": "storybook build",
46
- "deploy-storybook": "gh-pages -d storybook-static"
59
+ "deploy-storybook": "gh-pages -d storybook-static",
60
+ "test:coverage": "vitest run --coverage",
61
+ "test": "vitest"
47
62
  },
48
63
  "dependencies": {
49
64
  "lit": "^3.3.1"
@@ -58,6 +73,8 @@
58
73
  "@vitest/coverage-v8": "^4.0.18",
59
74
  "gh-pages": "^6.3.0",
60
75
  "playwright": "^1.58.2",
76
+ "publint": "^0.3.17",
77
+ "release-it": "^19.2.4",
61
78
  "storybook": "^10.2.13",
62
79
  "tsdown": "0.21.0-beta.2",
63
80
  "typescript": "~5.9.3",
@@ -65,4 +82,4 @@
65
82
  "vite-plugin-dts": "^4.5.4",
66
83
  "vitest": "^4.0.18"
67
84
  }
68
- }
85
+ }
package/readme.md CHANGED
@@ -1,13 +1,22 @@
1
1
  # 🦷 Og-odontogram
2
2
 
3
- A lightweight, interactive, and highly customizable **Web Component Ogdontogram** built with [Lit](https://lit.dev/). Perfect for dental software, patient records, and educational tools.
3
+
4
+ [![npm version](https://img.shields.io/npm/v/odontogram?color=blue\&label=npm)](https://www.npmjs.com/package/odontogram)
5
+ [![npm downloads](https://img.shields.io/npm/dm/odontogram?color=green\&label=downloads)](https://www.npmjs.com/package/odontogram)
6
+ [![Storybook](https://img.shields.io/badge/Storybook-Demo-orange)](https://biomathcode.github.io/odontogram)
7
+
8
+ A lightweight, interactive, and highly customizable **Web Component og-odontogram** built with [Lit](https://lit.dev/). Perfect for dental software, patient records, and educational tools.
4
9
 
5
10
  ## ✨ Features
6
11
 
7
12
  * **Zero Framework Dependency**: Works with React, Vue, Angular, or plain HTML.
8
13
  * **Multi-Mode Support**: Toggle between `adult` (32 teeth), `baby` (20 primary teeth), and `old` (geriatric) layouts.
14
+ * **Multiple Notations**: Display tooth labels in `fdi`, `universal`, or `palmer` notation.
9
15
  * **Interactive Regions**: Supports 5 surfaces per tooth: Vestibular, Distal, Palatine, Mesial, and Occlusal.
16
+ * **Accessible by Keyboard**: Every tooth surface is focusable and can be toggled with `Enter` / `Space`.
17
+ * **Screen-Reader Friendly**: Surfaces expose `aria-pressed`, descriptive labels, and live updates.
10
18
  * **JSON Powered**: Export and rehydrate the entire chart state via a simple JSON object.
19
+ * **PNG Export**: Download the current chart view as a PNG with built-in canvas export (no extra dependency).
11
20
  * **CSS Theming**: Customize selection colors using CSS variables.
12
21
  * **Open-WC Compliant**: Shipped as unoptimized ESM for maximum bundler compatibility.
13
22
 
@@ -33,15 +42,22 @@ npm install odontogram
33
42
  import 'odontogram';
34
43
  </script>
35
44
 
36
- <og-odontogram id="my-chart" mode="adult"></og-odontogram>
45
+ <og-odontogram id="my-chart" mode="adult" notation="fdi"></og-odontogram>
37
46
 
38
47
  <script>
39
48
  const chart = document.getElementById('my-chart');
40
49
 
41
50
  // Listen for changes
42
- chart.addEventListener('oodontogram-change', (e) => {
43
- console.log('New State:', e.detail.data);
51
+ chart.addEventListener('odontogram-change', (e) => {
52
+ // FDI-keyed state
53
+ console.log('FDI state:', e.detail.data);
54
+
55
+ // Current-notation state (e.g. universal/palmer labels)
56
+ console.log('Notation state:', e.detail.notationData);
44
57
  });
58
+
59
+ // Download as PNG
60
+ chart.downloadPng('patient-chart.png');
45
61
  </script>
46
62
 
47
63
  ```
@@ -49,13 +65,14 @@ npm install odontogram
49
65
  ### React / Next.js
50
66
 
51
67
  ```jsx
52
- import 'oodontogram';
68
+ import 'odontogram';
53
69
 
54
70
  function App() {
55
71
  return (
56
72
  <og-odontogram
57
73
  mode="baby"
58
- onoodontogram-change={(e) => console.log(e.detail.data)}
74
+ notation="universal"
75
+ onodontogram-change={(e) => console.log(e.detail.data)}
59
76
  />
60
77
  );
61
78
  }
@@ -68,16 +85,25 @@ function App() {
68
85
 
69
86
  ### Properties
70
87
 
71
- | Property | Type | Default | Description |
72
- | ----------- | -------- | --------- | ---------------------------------------- |
73
- | `mode` | `string` | `'adult'` | Patient type: `adult`, `baby`, or `old`. |
74
- | `chartData` | `object` | `{}` | Initial state to pre-fill the chart. |
88
+ | Property | Type | Default | Description |
89
+ | ----------- | -------- | --------- | ----------------------------------------------- |
90
+ | `mode` | `string` | `'adult'` | Patient type: `adult`, `baby`, or `old`. |
91
+ | `notation` | `string` | `'fdi'` | Label system: `fdi`, `universal`, or `palmer`. |
92
+ | `chartData` | `object` | `{}` | Initial state (internally keyed in FDI format). |
75
93
 
76
94
  ### Custom Events
77
95
 
78
- | Event | Detail | Description |
79
- | -------------------- | --------------------------- | ------------------------------------------ |
80
- | `oodontogram-change` | `{ data: {}, mode: string}` | Fired whenever a tooth surface is toggled. |
96
+ | Event | Detail | Description |
97
+ | ------------------- | ---------------------------------------- | ------------------------------------------ |
98
+ | `odontogram-change` | `{ data, notationData, mode, notation }` | Fired whenever a tooth surface is toggled. |
99
+
100
+ ### Public Methods
101
+
102
+ | Method | Signature | Description |
103
+ | -------------- | --------------------------------------------------------------- | --------------------------------------------------- |
104
+ | `getChartData` | `(notation = currentNotation) => Record<string, ToothSurfaces>` | Returns chart data keyed by the requested notation. |
105
+ | `toPngDataUrl` | `() => string` | Returns a PNG data URL for the current chart. |
106
+ | `downloadPng` | `(filename?: string) => void` | Downloads a PNG file of the current chart. |
81
107
 
82
108
  ### CSS Variables
83
109
 
@@ -90,6 +116,12 @@ og-odontogram {
90
116
 
91
117
  ```
92
118
 
119
+ ### Accessibility
120
+
121
+ * Tab to each tooth surface, then use `Enter` or `Space` to toggle.
122
+ * Surfaces expose `aria-pressed` and descriptive labels.
123
+ * Live announcements communicate selection changes for assistive technology.
124
+
93
125
  ---
94
126
 
95
127
  ## 📊 Data Structure
@@ -137,4 +169,4 @@ MIT © [Biomathcode](https://github.com/biomathcode)
137
169
 
138
170
  ---
139
171
 
140
- **Would you like me to help you set up a GitHub Action to automatically publish this to NPM whenever you create a new release?**
172
+ **Would you like me to help you set up a GitHub Action to automatically publish this to NPM whenever you create a new release?**
@@ -5,8 +5,8 @@ import './og-tooth';
5
5
 
6
6
  export type PatientMode = 'adult' | 'baby' | 'old';
7
7
 
8
- @customElement('og-dontogram')
9
- export class OgDontogram extends LitElement {
8
+ @customElement('og-odontogram')
9
+ export class OgOdontogram extends LitElement {
10
10
  @property({ type: String }) mode: PatientMode = 'adult';
11
11
  @property({ type: Object }) chartData: Record<string, ToothSurfaces> = {};
12
12
  @state() private teethState: Record<string, ToothSurfaces> = {};
@@ -2,28 +2,28 @@ import { html } from 'lit';
2
2
  import '../og-odontogram';
3
3
 
4
4
  export default {
5
- title: 'Odontogram/Main',
6
- component: 'og-dontogram',
7
- argTypes: {
8
- mode: {
9
- control: 'select',
10
- options: ['adult', 'baby', 'old'],
11
- description: 'Select the patient type'
12
- },
13
- selectionColor: { control: 'color' }
14
- }
5
+ title: 'Odontogram/Main',
6
+ component: 'og-odontogram',
7
+ argTypes: {
8
+ mode: {
9
+ control: 'select',
10
+ options: ['adult', 'baby', 'old'],
11
+ description: 'Select the patient type'
12
+ },
13
+ selectionColor: { control: 'color' }
14
+ }
15
15
  };
16
16
 
17
17
  export const Template = (args: any) => {
18
- // We handle the event here to update the external textarea in the story
19
- const handleUpdate = (e: CustomEvent) => {
20
- const area = document.getElementById('story-json-output') as HTMLTextAreaElement;
21
- if (area) {
22
- area.value = JSON.stringify(e.detail.data, null, 2);
23
- }
24
- };
18
+ // We handle the event here to update the external textarea in the story
19
+ const handleUpdate = (e: CustomEvent) => {
20
+ const area = document.getElementById('story-json-output') as HTMLTextAreaElement;
21
+ if (area) {
22
+ area.value = JSON.stringify(e.detail.data, null, 2);
23
+ }
24
+ };
25
25
 
26
- return html`
26
+ return html`
27
27
  <style>
28
28
  .story-container { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; gap: 2rem; }
29
29
  textarea { width: 100%; max-width: 600px; height: 150px; font-family: monospace; padding: 10px; border-radius: 8px; border: 1px solid #ccc; }
@@ -31,11 +31,11 @@ export const Template = (args: any) => {
31
31
  </style>
32
32
 
33
33
  <div class="story-container" style="--click-color: ${args.selectionColor || '#ff6961'}">
34
- <og-dontogram
34
+ <og-odontogram
35
35
  .mode=${args.mode}
36
36
  .chartData=${args.chartData || {}}
37
37
  @odontogram-change=${handleUpdate}>
38
- </og-dontogram>
38
+ </og-odontogram>
39
39
 
40
40
  <div>
41
41
  <h3>Exported Patient Data (External)</h3>
@@ -56,9 +56,9 @@ export const Senior = Template.bind({});
56
56
 
57
57
  export const PreFilled = Template.bind({});
58
58
  (PreFilled as any).args = {
59
- mode: 'adult',
60
- chartData: {
61
- "11": { vestibular: false, distal: false, palatine: false, mesial: false, occlusal: true },
62
- "46": { vestibular: true, distal: true, palatine: false, mesial: false, occlusal: false }
63
- }
59
+ mode: 'adult',
60
+ chartData: {
61
+ "11": { vestibular: false, distal: false, palatine: false, mesial: false, occlusal: true },
62
+ "46": { vestibular: true, distal: true, palatine: false, mesial: false, occlusal: false }
63
+ }
64
64
  };
@@ -4,8 +4,8 @@ import '../og-odontogram'; // Import your component file
4
4
  import '../og-tooth'; // Import the sub-component
5
5
 
6
6
  const meta: Meta = {
7
- title: 'Dental/OgDontogram',
8
- component: 'og-dontogram',
7
+ title: 'OgOdontogram',
8
+ component: 'og-odontogram',
9
9
  argTypes: {
10
10
  notation: {
11
11
  control: { type: 'select' },
@@ -32,7 +32,7 @@ export const Default: Story = {
32
32
  },
33
33
  render: (args) => html`
34
34
  <div style="padding: 2rem; background: #f4f7f6; min-height: 100vh;">
35
- <og-dontogram id="myChart"></og-dontogram>
35
+ <og-odontogram id="myChart"></og-odontogram>
36
36
 
37
37
  <div style="margin-top: 20px;">
38
38
  <label>Exported JSON State:</label><br>
@@ -60,9 +60,9 @@ export const WithPreexistingConditions: Story = {
60
60
  chartData: mockData,
61
61
  },
62
62
  render: (args) => html`
63
- <og-dontogram
63
+ <og-odontogram
64
64
  .notation=${args.notation}
65
65
  .chartData=${args.chartData}>
66
- </og-dontogram>
66
+ </og-odontogram>
67
67
  `,
68
68
  };