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 +310 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +80 -0
- package/dist/index.d.mts +50 -5
- package/dist/index.mjs +7 -6
- package/dist/index.mjs.map +1 -0
- package/package.json +23 -6
- package/readme.md +46 -14
- package/src/og-odontogram.ts +2 -2
- package/src/stories/Layout.stories.ts +25 -25
- package/src/stories/Odontogram.stories.ts +5 -5
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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"}
|
package/dist/index.d.cts
ADDED
|
@@ -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():
|
|
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
|
|
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):
|
|
31
|
-
render():
|
|
74
|
+
renderTooth(id: number): TemplateResult<1>;
|
|
75
|
+
render(): TemplateResult<1>;
|
|
32
76
|
static styles: lit.CSSResult;
|
|
33
77
|
}
|
|
34
78
|
//#endregion
|
|
35
|
-
export {
|
|
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
|
|
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 })],
|
|
292
|
-
__decorate([property({ type: Object })],
|
|
293
|
-
__decorate([state()],
|
|
294
|
-
|
|
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 {
|
|
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.
|
|
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": "
|
|
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
|
-
|
|
3
|
+
|
|
4
|
+
[](https://www.npmjs.com/package/odontogram)
|
|
5
|
+
[](https://www.npmjs.com/package/odontogram)
|
|
6
|
+
[](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('
|
|
43
|
-
|
|
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 '
|
|
68
|
+
import 'odontogram';
|
|
53
69
|
|
|
54
70
|
function App() {
|
|
55
71
|
return (
|
|
56
72
|
<og-odontogram
|
|
57
73
|
mode="baby"
|
|
58
|
-
|
|
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
|
-
| `
|
|
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
|
|
79
|
-
|
|
|
80
|
-
| `
|
|
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?**
|
package/src/og-odontogram.ts
CHANGED
|
@@ -5,8 +5,8 @@ import './og-tooth';
|
|
|
5
5
|
|
|
6
6
|
export type PatientMode = 'adult' | 'baby' | 'old';
|
|
7
7
|
|
|
8
|
-
@customElement('og-
|
|
9
|
-
export class
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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-
|
|
34
|
+
<og-odontogram
|
|
35
35
|
.mode=${args.mode}
|
|
36
36
|
.chartData=${args.chartData || {}}
|
|
37
37
|
@odontogram-change=${handleUpdate}>
|
|
38
|
-
</og-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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: '
|
|
8
|
-
component: 'og-
|
|
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-
|
|
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-
|
|
63
|
+
<og-odontogram
|
|
64
64
|
.notation=${args.notation}
|
|
65
65
|
.chartData=${args.chartData}>
|
|
66
|
-
</og-
|
|
66
|
+
</og-odontogram>
|
|
67
67
|
`,
|
|
68
68
|
};
|