odontogram 0.1.1 → 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.1",
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,16 +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
- "build-storybook": "storybook build"
58
+ "build-storybook": "storybook build",
59
+ "deploy-storybook": "gh-pages -d storybook-static",
60
+ "test:coverage": "vitest run --coverage",
61
+ "test": "vitest"
46
62
  },
47
63
  "dependencies": {
48
64
  "lit": "^3.3.1"
@@ -55,7 +71,10 @@
55
71
  "@storybook/web-components-vite": "^10.2.13",
56
72
  "@vitest/browser-playwright": "^4.0.18",
57
73
  "@vitest/coverage-v8": "^4.0.18",
74
+ "gh-pages": "^6.3.0",
58
75
  "playwright": "^1.58.2",
76
+ "publint": "^0.3.17",
77
+ "release-it": "^19.2.4",
59
78
  "storybook": "^10.2.13",
60
79
  "tsdown": "0.21.0-beta.2",
61
80
  "typescript": "~5.9.3",
@@ -63,4 +82,4 @@
63
82
  "vite-plugin-dts": "^4.5.4",
64
83
  "vitest": "^4.0.18"
65
84
  }
66
- }
85
+ }
package/readme.md ADDED
@@ -0,0 +1,172 @@
1
+ # 🦷 Og-odontogram
2
+
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.
9
+
10
+ ## ✨ Features
11
+
12
+ * **Zero Framework Dependency**: Works with React, Vue, Angular, or plain HTML.
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.
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.
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).
20
+ * **CSS Theming**: Customize selection colors using CSS variables.
21
+ * **Open-WC Compliant**: Shipped as unoptimized ESM for maximum bundler compatibility.
22
+
23
+ ---
24
+
25
+ ## 📦 Installation
26
+
27
+ Install via NPM:
28
+
29
+ ```bash
30
+ npm install odontogram
31
+
32
+ ```
33
+
34
+ ---
35
+
36
+ ## 🚀 Quick Start
37
+
38
+ ### Plain HTML / Vanilla JS
39
+
40
+ ```html
41
+ <script type="module">
42
+ import 'odontogram';
43
+ </script>
44
+
45
+ <og-odontogram id="my-chart" mode="adult" notation="fdi"></og-odontogram>
46
+
47
+ <script>
48
+ const chart = document.getElementById('my-chart');
49
+
50
+ // Listen for changes
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);
57
+ });
58
+
59
+ // Download as PNG
60
+ chart.downloadPng('patient-chart.png');
61
+ </script>
62
+
63
+ ```
64
+
65
+ ### React / Next.js
66
+
67
+ ```jsx
68
+ import 'odontogram';
69
+
70
+ function App() {
71
+ return (
72
+ <og-odontogram
73
+ mode="baby"
74
+ notation="universal"
75
+ onodontogram-change={(e) => console.log(e.detail.data)}
76
+ />
77
+ );
78
+ }
79
+
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 🛠 API & Configuration
85
+
86
+ ### Properties
87
+
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). |
93
+
94
+ ### Custom Events
95
+
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. |
107
+
108
+ ### CSS Variables
109
+
110
+ Customize the look of the selected regions:
111
+
112
+ ```css
113
+ og-odontogram {
114
+ --click-color: #ff6961; /* The color of selected surfaces */
115
+ }
116
+
117
+ ```
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
+
125
+ ---
126
+
127
+ ## 📊 Data Structure
128
+
129
+ The component exports a clean JSON structure representing the "history" or "state" of the teeth.
130
+
131
+ ```json
132
+ {
133
+ "16": {
134
+ "vestibular": true,
135
+ "distal": false,
136
+ "palatine": false,
137
+ "mesial": true,
138
+ "occlusal": true
139
+ }
140
+ }
141
+
142
+ ```
143
+
144
+ ---
145
+
146
+ ## 🎨 Storybook (Development & Demo)
147
+
148
+ We use Storybook to showcase all modes and test interactivity. You can see the "Adult", "Pediatric", and "Senior" layouts in isolation.
149
+
150
+ To run Storybook locally:
151
+
152
+ 1. Clone the repo.
153
+ 2. `npm install`
154
+ 3. `npm run storybook`
155
+
156
+ ---
157
+
158
+ ## 🏗 Developing
159
+
160
+ If you want to contribute or build from source:
161
+
162
+ * **Build for Production**: `npm run build` (uses `tsdown` to generate `.mjs` and `.d.mts`).
163
+ * **Test**: `npm run test` (uses Vitest + Playwright for browser testing).
164
+ * **Lint**: `npm run lint`.
165
+
166
+ ## 📄 License
167
+
168
+ MIT © [Biomathcode](https://github.com/biomathcode)
169
+
170
+ ---
171
+
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
  };