@alfadocs/ui-kit-debug 0.63.0 → 0.64.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/_chunks/{ai-prompt-input-C6sCr1Vi.js → ai-prompt-input-Dx8eXoPm.js} +2 -2
- package/dist/_chunks/{ai-prompt-input-C6sCr1Vi.js.map → ai-prompt-input-Dx8eXoPm.js.map} +1 -1
- package/dist/_chunks/{audio-recorder-D6OVfNiZ.js → audio-recorder-CdXuT9ln.js} +2 -2
- package/dist/_chunks/{audio-recorder-D6OVfNiZ.js.map → audio-recorder-CdXuT9ln.js.map} +1 -1
- package/dist/_chunks/{bishop-score-B9tvgoIq.js → bishop-score-CzjSx-dm.js} +2 -2
- package/dist/_chunks/{bishop-score-B9tvgoIq.js.map → bishop-score-CzjSx-dm.js.map} +1 -1
- package/dist/_chunks/{booking-BUV9fspj.js → booking-ChfvWy3P.js} +2 -2
- package/dist/_chunks/{booking-BUV9fspj.js.map → booking-ChfvWy3P.js.map} +1 -1
- package/dist/_chunks/{care-plan-card-QmNpGggC.js → care-plan-card-DhZNoXs4.js} +2 -2
- package/dist/_chunks/{care-plan-card-QmNpGggC.js.map → care-plan-card-DhZNoXs4.js.map} +1 -1
- package/dist/_chunks/{care-plan-entry-card-Cnra7vUc.js → care-plan-entry-card-DW70yBOD.js} +2 -2
- package/dist/_chunks/{care-plan-entry-card-Cnra7vUc.js.map → care-plan-entry-card-DW70yBOD.js.map} +1 -1
- package/dist/_chunks/{editable-currency-cell-renderer-D5C5tCfu.js → editable-currency-cell-renderer-BQgaKFCz.js} +2 -2
- package/dist/_chunks/{editable-currency-cell-renderer-D5C5tCfu.js.map → editable-currency-cell-renderer-BQgaKFCz.js.map} +1 -1
- package/dist/_chunks/{gestational-age-calculator-AkNFfZYs.js → gestational-age-calculator-D85E8lGN.js} +2 -2
- package/dist/_chunks/{gestational-age-calculator-AkNFfZYs.js.map → gestational-age-calculator-D85E8lGN.js.map} +1 -1
- package/dist/_chunks/{pregnancy-dating-Dg6dTe1p.js → pregnancy-dating-7NUaAfob.js} +2 -2
- package/dist/_chunks/{pregnancy-dating-Dg6dTe1p.js.map → pregnancy-dating-7NUaAfob.js.map} +1 -1
- package/dist/_chunks/{select-hsCaJSX3.js → select-CEtRcon5.js} +46 -45
- package/dist/_chunks/select-CEtRcon5.js.map +1 -0
- package/dist/_chunks/{tabs-BpPYVme_.js → tabs-BIQ0ew1T.js} +2 -2
- package/dist/_chunks/{tabs-BpPYVme_.js.map → tabs-BIQ0ew1T.js.map} +1 -1
- package/dist/_chunks/tooth-scheme-CiphQaON.js +1257 -0
- package/dist/_chunks/tooth-scheme-CiphQaON.js.map +1 -0
- package/dist/_chunks/{unit-converter-3sINXO3m.js → unit-converter-BIbXHIQA.js} +2 -2
- package/dist/_chunks/{unit-converter-3sINXO3m.js.map → unit-converter-BIbXHIQA.js.map} +1 -1
- package/dist/agent-catalog.json +41 -4
- package/dist/components/ai-prompt-input/index.js +1 -1
- package/dist/components/audio-recorder/index.js +1 -1
- package/dist/components/bishop-score/index.js +1 -1
- package/dist/components/booking/index.js +1 -1
- package/dist/components/care-plan-card/index.js +1 -1
- package/dist/components/care-plan-entry-card/index.js +1 -1
- package/dist/components/data-table/index.js +1 -1
- package/dist/components/gestational-age-calculator/index.js +1 -1
- package/dist/components/pregnancy-dating/index.js +1 -1
- package/dist/components/select/index.js +1 -1
- package/dist/components/select/select.d.ts +8 -0
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/tabs/index.js +1 -1
- package/dist/components/tooth-scheme/index.d.ts +2 -2
- package/dist/components/tooth-scheme/index.d.ts.map +1 -1
- package/dist/components/tooth-scheme/index.js +22 -15
- package/dist/components/tooth-scheme/tooth-data.d.ts +79 -18
- package/dist/components/tooth-scheme/tooth-data.d.ts.map +1 -1
- package/dist/components/tooth-scheme/tooth-scheme.agent.d.ts +2 -0
- package/dist/components/tooth-scheme/tooth-scheme.agent.d.ts.map +1 -1
- package/dist/components/tooth-scheme/tooth-scheme.d.ts +48 -1
- package/dist/components/tooth-scheme/tooth-scheme.d.ts.map +1 -1
- package/dist/components/unit-converter/index.js +1 -1
- package/dist/i18n/locales/ar.d.ts +17 -0
- package/dist/i18n/locales/ar.d.ts.map +1 -1
- package/dist/i18n/locales/ar.js +18 -1
- package/dist/i18n/locales/ar.js.map +1 -1
- package/dist/i18n/locales/de.d.ts +17 -0
- package/dist/i18n/locales/de.d.ts.map +1 -1
- package/dist/i18n/locales/de.js +18 -1
- package/dist/i18n/locales/de.js.map +1 -1
- package/dist/i18n/locales/el.d.ts +17 -0
- package/dist/i18n/locales/el.d.ts.map +1 -1
- package/dist/i18n/locales/el.js +18 -1
- package/dist/i18n/locales/el.js.map +1 -1
- package/dist/i18n/locales/en.d.ts +17 -0
- package/dist/i18n/locales/en.d.ts.map +1 -1
- package/dist/i18n/locales/en.js +18 -1
- package/dist/i18n/locales/en.js.map +1 -1
- package/dist/i18n/locales/es.d.ts +17 -0
- package/dist/i18n/locales/es.d.ts.map +1 -1
- package/dist/i18n/locales/es.js +18 -1
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +17 -0
- package/dist/i18n/locales/fr.d.ts.map +1 -1
- package/dist/i18n/locales/fr.js +18 -1
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hi.d.ts +17 -0
- package/dist/i18n/locales/hi.d.ts.map +1 -1
- package/dist/i18n/locales/hi.js +18 -1
- package/dist/i18n/locales/hi.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +17 -0
- package/dist/i18n/locales/it.d.ts.map +1 -1
- package/dist/i18n/locales/it.js +18 -1
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/ja.d.ts +17 -0
- package/dist/i18n/locales/ja.d.ts.map +1 -1
- package/dist/i18n/locales/ja.js +18 -1
- package/dist/i18n/locales/ja.js.map +1 -1
- package/dist/i18n/locales/nl.d.ts +17 -0
- package/dist/i18n/locales/nl.d.ts.map +1 -1
- package/dist/i18n/locales/nl.js +18 -1
- package/dist/i18n/locales/nl.js.map +1 -1
- package/dist/i18n/locales/pl.d.ts +17 -0
- package/dist/i18n/locales/pl.d.ts.map +1 -1
- package/dist/i18n/locales/pl.js +18 -1
- package/dist/i18n/locales/pl.js.map +1 -1
- package/dist/i18n/locales/pt.d.ts +17 -0
- package/dist/i18n/locales/pt.d.ts.map +1 -1
- package/dist/i18n/locales/pt.js +18 -1
- package/dist/i18n/locales/pt.js.map +1 -1
- package/dist/i18n/locales/ro.d.ts +17 -0
- package/dist/i18n/locales/ro.d.ts.map +1 -1
- package/dist/i18n/locales/ro.js +18 -1
- package/dist/i18n/locales/ro.js.map +1 -1
- package/dist/i18n/locales/ru.d.ts +17 -0
- package/dist/i18n/locales/ru.d.ts.map +1 -1
- package/dist/i18n/locales/ru.js +18 -1
- package/dist/i18n/locales/ru.js.map +1 -1
- package/dist/i18n/locales/sq.d.ts +17 -0
- package/dist/i18n/locales/sq.d.ts.map +1 -1
- package/dist/i18n/locales/sq.js +18 -1
- package/dist/i18n/locales/sq.js.map +1 -1
- package/dist/i18n/locales/sv.d.ts +17 -0
- package/dist/i18n/locales/sv.d.ts.map +1 -1
- package/dist/i18n/locales/sv.js +18 -1
- package/dist/i18n/locales/sv.js.map +1 -1
- package/dist/i18n/locales/tr.d.ts +17 -0
- package/dist/i18n/locales/tr.d.ts.map +1 -1
- package/dist/i18n/locales/tr.js +18 -1
- package/dist/i18n/locales/tr.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +17 -0
- package/dist/i18n/locales/zh.d.ts.map +1 -1
- package/dist/i18n/locales/zh.js +18 -1
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/index.js +180 -173
- package/dist/locales/ar.json +18 -1
- package/dist/locales/de.json +18 -1
- package/dist/locales/el.json +18 -1
- package/dist/locales/en.json +18 -1
- package/dist/locales/es.json +18 -1
- package/dist/locales/fr.json +18 -1
- package/dist/locales/hi.json +18 -1
- package/dist/locales/it.json +18 -1
- package/dist/locales/ja.json +18 -1
- package/dist/locales/nl.json +18 -1
- package/dist/locales/pl.json +18 -1
- package/dist/locales/pt.json +18 -1
- package/dist/locales/ro.json +18 -1
- package/dist/locales/ru.json +18 -1
- package/dist/locales/sq.json +18 -1
- package/dist/locales/sv.json +18 -1
- package/dist/locales/tr.json +18 -1
- package/dist/locales/zh.json +18 -1
- package/dist/tokens.css +1 -1
- package/package.json +1 -1
- package/dist/_chunks/select-hsCaJSX3.js.map +0 -1
- package/dist/_chunks/tooth-scheme-CxlsLjfN.js +0 -753
- package/dist/_chunks/tooth-scheme-CxlsLjfN.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooth-scheme-CiphQaON.js","sources":["../../src/components/tooth-scheme/tooth-scheme.agent.ts","../../src/components/tooth-scheme/tooth-data.ts","../../src/components/tooth-scheme/tooth-scheme.tsx"],"sourcesContent":["import type { AgentAdapter } from '../../agent/types';\nimport type { ToothSchemeHandle } from './tooth-scheme';\nimport type { Surface, ToothCondition } from './tooth-data';\n\ntype FdiId = Parameters<ToothSchemeHandle['focusTooth']>[0];\n\nexport const toothSchemeAgent: AgentAdapter<ToothSchemeHandle> = {\n id: 'tooth-scheme',\n // pick — focus/select a tooth; edit_inline — record/clear findings;\n // view_change — the side/plan projection + dentition are view props.\n capabilities: ['pick', 'edit_inline', 'view_change'],\n state: {\n chart: {\n type: 'object',\n description:\n 'Current dental chart — findings (and marked surfaces) keyed by FDI id.',\n read: (handle) => handle.getChart(),\n },\n chartedTeeth: {\n type: 'string[]',\n description: 'FDI ids of every tooth carrying at least one finding.',\n read: (handle) => Object.keys(handle.getChart()),\n },\n },\n actions: {\n focus_tooth: {\n safety: 'read',\n argsType: '{ id: FdiId }',\n description: 'Move focus to a specific tooth by FDI id.',\n invoke: (handle, args: { id: FdiId }) => {\n handle.focusTooth(args.id);\n },\n },\n get_tooth: {\n safety: 'read',\n argsType: '{ id: FdiId }',\n description: 'Read the findings recorded on one tooth (FDI id).',\n invoke: (handle, args: { id: FdiId }) => handle.getTooth(args.id),\n },\n teeth_with: {\n safety: 'read',\n argsType: '{ condition: ToothCondition }',\n description:\n 'List the FDI ids of every tooth carrying a given finding (e.g. all teeth with caries).',\n invoke: (handle, args: { condition: ToothCondition }) =>\n handle.teethWith(args.condition),\n },\n set_finding: {\n safety: 'write',\n argsType:\n '{ id: FdiId; condition: ToothCondition; surfaces?: Surface[] }',\n description:\n 'Record a finding on a tooth (crown, implant, root canal, caries, …). Optionally mark the affected surfaces for a surface finding.',\n invoke: (\n handle,\n args: { id: FdiId; condition: ToothCondition; surfaces?: Surface[] },\n ) => {\n handle.setFinding(args.id, args.condition, args.surfaces);\n },\n },\n remove_finding: {\n safety: 'write',\n argsType: '{ id: FdiId; condition: ToothCondition }',\n description: 'Remove a single finding from a tooth.',\n invoke: (handle, args: { id: FdiId; condition: ToothCondition }) => {\n handle.removeFinding(args.id, args.condition);\n },\n },\n clear_tooth: {\n safety: 'destructive',\n argsType: '{ id: FdiId }',\n description: 'Clear every finding from a tooth.',\n invoke: (handle, args: { id: FdiId }) => {\n handle.clearTooth(args.id);\n },\n },\n },\n domHooks: {\n root: { attr: 'data-component', value: 'tooth-scheme' },\n instanceId: {\n attr: 'data-component-id',\n sourceProp: 'id',\n description: 'Sourced from the id prop.',\n },\n item: {\n attr: 'data-fdi',\n description:\n 'Each rendered tooth `<g>` carries its FDI id as `data-fdi`. The `data-projection` attribute on the root exposes the side/plan view.',\n },\n },\n};\n\n// Re-exported for callers building typed args against the adapter.\nexport type { Surface, ToothCondition };\n","/* ------------------------------------------------------------------ */\n/* ToothScheme — data model + lookup tables. */\n/* */\n/* FDI ISO 3950 is the canonical internal id. This module exposes */\n/* conversion tables to Universal (ADA) and a simplified Palmer-style */\n/* quadrant+position string so consuming UIs can toggle numbering. */\n/* */\n/* Why simplified Palmer? True Palmer notation uses quadrant bracket */\n/* glyphs (\"⌐\", \"¬\", \"L\", \"J\") that do not render reliably in every */\n/* browser/font combination. We emit a readable `UR3` / `LL6` form */\n/* instead and document the deviation in the component MDX. */\n/* ------------------------------------------------------------------ */\n\n/* ------------------------------------------------------------------ */\n/* Core types */\n/* ------------------------------------------------------------------ */\n\nexport type FdiId = string; // '11'–'48' permanent, '51'–'85' primary\n\nexport type Dentition = 'permanent' | 'primary' | 'mixed';\nexport type Numbering = 'fdi' | 'universal' | 'palmer';\nexport type ToothMode = 'interactive' | 'display';\nexport type Surface = 'mesial' | 'distal' | 'occlusal' | 'buccal' | 'lingual';\nexport type ToothCondition =\n | 'caries'\n | 'filled'\n | 'crowned'\n | 'temporaryCrown'\n | 'bridge'\n | 'missing'\n | 'implant'\n | 'implantExtraction'\n | 'stub'\n | 'destroyed'\n | 'rootCanal';\n\nexport interface ToothState {\n conditions: ToothCondition[];\n surfaces: Surface[];\n notes?: string;\n}\n\nexport type ToothChart = Record<FdiId, ToothState>;\n\nexport type Anatomy = 'incisor' | 'canine' | 'premolar' | 'molar';\nexport type Arch = 'upper' | 'lower';\nexport type Side = 'right' | 'left'; // patient's side (NOT viewer's)\nexport type Quadrant = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;\n\nexport interface ToothMeta {\n fdi: FdiId;\n /** '1'–'32' for permanent, 'A'–'T' for primary. */\n universal: string;\n /** Simplified Palmer quadrant-and-position string, e.g. 'UR3', 'LL6'. */\n palmer: string;\n quadrant: Quadrant;\n /** 1–8 (permanent) or 1–5 (primary), counted from the midline outward. */\n positionInQuadrant: number;\n anatomy: Anatomy;\n arch: Arch;\n side: Side;\n dentition: 'permanent' | 'primary';\n}\n\n/* ------------------------------------------------------------------ */\n/* Layout orders */\n/* */\n/* Layout reads from the viewer's perspective, top-left to bottom- */\n/* right. The chart is FIXED in clinical convention: the patient's */\n/* right side sits on the viewer's left, regardless of document `dir`. */\n/* ------------------------------------------------------------------ */\n\n/**\n * 32 permanent teeth in visual layout order:\n * upper row: 18..11 (patient upper-right, outermost → midline), then 21..28 (upper-left, midline → outermost)\n * lower row: 48..41 (patient lower-right, outermost → midline), then 31..38 (lower-left, midline → outermost)\n */\nexport const PERMANENT_TEETH: FdiId[] = [\n // upper\n '18',\n '17',\n '16',\n '15',\n '14',\n '13',\n '12',\n '11',\n '21',\n '22',\n '23',\n '24',\n '25',\n '26',\n '27',\n '28',\n // lower\n '48',\n '47',\n '46',\n '45',\n '44',\n '43',\n '42',\n '41',\n '31',\n '32',\n '33',\n '34',\n '35',\n '36',\n '37',\n '38',\n];\n\n/**\n * 20 primary teeth in visual layout order:\n * upper row: 55..51 (upper-right outermost → midline), then 61..65\n * lower row: 85..81, then 71..75\n */\nexport const PRIMARY_TEETH: FdiId[] = [\n // upper\n '55',\n '54',\n '53',\n '52',\n '51',\n '61',\n '62',\n '63',\n '64',\n '65',\n // lower\n '85',\n '84',\n '83',\n '82',\n '81',\n '71',\n '72',\n '73',\n '74',\n '75',\n];\n\n/* ------------------------------------------------------------------ */\n/* Anatomy by position */\n/* ------------------------------------------------------------------ */\n\nfunction permanentAnatomy(positionInQuadrant: number): Anatomy {\n if (positionInQuadrant <= 2) return 'incisor';\n if (positionInQuadrant === 3) return 'canine';\n if (positionInQuadrant <= 5) return 'premolar';\n return 'molar';\n}\n\nfunction primaryAnatomy(positionInQuadrant: number): Anatomy {\n if (positionInQuadrant <= 2) return 'incisor';\n if (positionInQuadrant === 3) return 'canine';\n // Primary dentition has no premolars — positions 4 & 5 are molars.\n return 'molar';\n}\n\n/* ------------------------------------------------------------------ */\n/* Palmer helper */\n/* ------------------------------------------------------------------ */\n\nfunction palmerCode(quadrant: Quadrant, position: number): string {\n // Permanent: 1=UR, 2=UL, 3=LL, 4=LR. Primary: 5=UR, 6=UL, 7=LL, 8=LR.\n // Mapping is identical within each dentition ring; the number is what\n // changes, so we fold 5→1, 6→2, 7→3, 8→4 to derive the quadrant code.\n const normalised = ((quadrant - 1) % 4) + 1;\n const code =\n normalised === 1\n ? 'UR'\n : normalised === 2\n ? 'UL'\n : normalised === 3\n ? 'LL'\n : 'LR';\n return `${code}${position}`;\n}\n\n/* ------------------------------------------------------------------ */\n/* Universal (ADA) conversion */\n/* */\n/* Permanent — Universal numbers 1..32 run clockwise from the */\n/* patient's upper-right 3rd molar (FDI 18 = Universal 1), across */\n/* the upper arch (18→11 = 1→8, 21→28 = 9→16), then down the left */\n/* lower arch (38→31 = 17→24) and across the right lower (41→48 = */\n/* 25→32). */\n/* */\n/* Primary — Universal letters A..T walk the same path: */\n/* 55→51 = A→E, 61→65 = F→J, 75→71 = K→O, 81→85 = P→T. */\n/* ------------------------------------------------------------------ */\n\nfunction permanentUniversal(quadrant: Quadrant, position: number): string {\n switch (quadrant) {\n case 1:\n // 18→1, 17→2, …, 11→8\n return String(1 + (8 - position));\n case 2:\n // 21→9, 22→10, …, 28→16\n return String(8 + position);\n case 3:\n // 38→17, 37→18, …, 31→24\n return String(17 + (8 - position));\n case 4:\n // 41→25, 42→26, …, 48→32\n return String(24 + position);\n default:\n return '';\n }\n}\n\nfunction primaryUniversal(quadrant: Quadrant, position: number): string {\n const letters = 'ABCDEFGHIJKLMNOPQRST';\n let index = -1;\n switch (quadrant) {\n case 5:\n // 55→A, 54→B, 53→C, 52→D, 51→E\n index = 0 + (5 - position);\n break;\n case 6:\n // 61→F, 62→G, 63→H, 64→I, 65→J\n index = 5 + (position - 1);\n break;\n case 7:\n // 75→K, 74→L, 73→M, 72→N, 71→O\n index = 10 + (5 - position);\n break;\n case 8:\n // 81→P, 82→Q, 83→R, 84→S, 85→T\n index = 15 + (position - 1);\n break;\n default:\n index = -1;\n }\n return index >= 0 ? letters[index] : '';\n}\n\n/* ------------------------------------------------------------------ */\n/* Meta builder */\n/* ------------------------------------------------------------------ */\n\nfunction buildMeta(fdi: FdiId): ToothMeta {\n const quadrant = Number(fdi[0]) as Quadrant;\n const position = Number(fdi[1]);\n const isPrimary = quadrant >= 5;\n const anatomy = isPrimary\n ? primaryAnatomy(position)\n : permanentAnatomy(position);\n const arch: Arch =\n quadrant === 1 || quadrant === 2 || quadrant === 5 || quadrant === 6\n ? 'upper'\n : 'lower';\n const side: Side =\n quadrant === 1 || quadrant === 4 || quadrant === 5 || quadrant === 8\n ? 'right'\n : 'left';\n const universal = isPrimary\n ? primaryUniversal(quadrant, position)\n : permanentUniversal(quadrant, position);\n const palmer = palmerCode(quadrant, position);\n return {\n fdi,\n universal,\n palmer,\n quadrant,\n positionInQuadrant: position,\n anatomy,\n arch,\n side,\n dentition: isPrimary ? 'primary' : 'permanent',\n };\n}\n\nexport const FDI_TO_META: Record<FdiId, ToothMeta> = [\n ...PERMANENT_TEETH,\n ...PRIMARY_TEETH,\n].reduce<Record<FdiId, ToothMeta>>((acc, fdi) => {\n acc[fdi] = buildMeta(fdi);\n return acc;\n}, {});\n\nexport const FDI_TO_UNIVERSAL: Record<FdiId, string> = Object.fromEntries(\n Object.entries(FDI_TO_META).map(([fdi, meta]) => [fdi, meta.universal]),\n);\n\nexport const FDI_TO_PALMER: Record<FdiId, string> = Object.fromEntries(\n Object.entries(FDI_TO_META).map(([fdi, meta]) => [fdi, meta.palmer]),\n);\n\n/**\n * FDI used to resolve a tooth's RENDER. There are no separate deciduous\n * renders — primary teeth reuse the permanent image of the same position\n * (quadrant 5→1, 6→2, 7→3, 8→4; position digit unchanged), scaled down by\n * the renderer. Permanent teeth resolve to themselves.\n */\nexport function assetFdi(meta: ToothMeta): FdiId {\n if (meta.dentition !== 'primary') return meta.fdi;\n return `${meta.quadrant - 4}${meta.positionInQuadrant}`;\n}\n\n/** Render scale applied to a tooth image (primary teeth are smaller). */\nexport const PRIMARY_TOOTH_SCALE = 0.72;\n\n/* ------------------------------------------------------------------ */\n/* Condition → token + pattern */\n/* */\n/* Every condition carries BOTH a colour AND a fill pattern so */\n/* colour-blind clinicians can distinguish states in the SVG (and in */\n/* black-and-white prints) — colour is never the sole channel. */\n/* ------------------------------------------------------------------ */\n\n/** CSS variable name (without the leading `var(...)`) that drives a condition's colour. */\nexport const CONDITION_TOKENS: Record<ToothCondition, string> = {\n caries: '--destructive',\n filled: '--info',\n crowned: '--warning',\n temporaryCrown: '--warning',\n bridge: '--primary',\n missing: '--muted-foreground',\n implant: '--accent',\n implantExtraction: '--accent',\n stub: '--muted-foreground',\n destroyed: '--destructive',\n rootCanal: '--primary',\n};\n\n/**\n * Convenience: the `fill=\"var(--destructive)\"` string ready to paste onto\n * an SVG attribute. Kept in sync with `CONDITION_TOKENS`. Used for the legend\n * swatch + the surface-condition ring; whole-tooth conditions render a\n * distinct symbol image (the glyph is the colour-independent channel).\n */\nexport const CONDITION_COLORS: Record<ToothCondition, string> = {\n caries: 'var(--destructive)',\n filled: 'var(--info)',\n crowned: 'var(--warning)',\n temporaryCrown: 'var(--warning)',\n bridge: 'var(--primary)',\n missing: 'var(--muted-foreground)',\n implant: 'var(--accent)',\n implantExtraction: 'var(--accent)',\n stub: 'var(--muted-foreground)',\n destroyed: 'var(--destructive)',\n rootCanal: 'var(--primary)',\n};\n\n/* ------------------------------------------------------------------ */\n/* Condition → side-view symbol overlay */\n/* */\n/* Whole-tooth findings overlay a photoreal symbol render on the */\n/* facial tooth. `caries` / `filled` are per-surface findings shown */\n/* in the plan (occlusal) view and carry no side symbol here. The */\n/* folder layout is `{symbol}/{top|bottom}/...` under `side/`. */\n/* ------------------------------------------------------------------ */\n\nexport interface SideSymbolSpec {\n /** Asset folder under `side/`. */\n folder: string;\n /** How the file resolves inside `{folder}/{view}/`. */\n resolve: 'per-fdi' | 'general' | 'rct';\n}\n\nexport const CONDITION_SIDE_SYMBOL: Partial<\n Record<ToothCondition, SideSymbolSpec>\n> = {\n crowned: { folder: 'crown', resolve: 'per-fdi' },\n temporaryCrown: { folder: 'temporaryCrown', resolve: 'per-fdi' },\n bridge: { folder: 'replacement', resolve: 'per-fdi' },\n destroyed: { folder: 'destroyed', resolve: 'per-fdi' },\n implant: { folder: 'implant', resolve: 'general' },\n implantExtraction: { folder: 'implantExtraction', resolve: 'general' },\n stub: { folder: 'stub', resolve: 'general' },\n rootCanal: { folder: 'rootTreatment', resolve: 'rct' },\n};\n\n/** Resolve the side-view symbol overlay URL for a condition on a tooth. */\nexport function conditionSymbolUrl(\n meta: ToothMeta,\n assetBaseUrl: string,\n spec: SideSymbolSpec,\n): string {\n const view = meta.arch === 'upper' ? 'top' : 'bottom';\n const base = `${assetBaseUrl}/side/${spec.folder}/${view}`;\n if (spec.resolve === 'general') return `${base}/general.webp`;\n const fdi = assetFdi(meta);\n if (spec.resolve === 'rct') return `${base}/${fdi}/full.webp`;\n return `${base}/${fdi}.webp`;\n}\n\n/* ------------------------------------------------------------------ */\n/* Plan (occlusal) view — per-surface findings */\n/* */\n/* The \"Stato dentale\" / Findings surface marks caries & fillings on */\n/* individual tooth surfaces in the occlusal (plan) view. The asset */\n/* surface names differ from the clinical `Surface` enum: buccal → */\n/* vestibular (cheek/lip side), lingual → oral (tongue side). */\n/* ------------------------------------------------------------------ */\n\nexport type Projection = 'side' | 'plan';\n/** The user-selectable view: a single projection, or both stacked together. */\nexport type ViewMode = Projection | 'both';\n\n/** Map the clinical `Surface` enum to the asset surface filename token. */\nexport const SURFACE_ASSET_NAME: Record<Surface, string> = {\n mesial: 'mesial',\n distal: 'distal',\n occlusal: 'occlusal',\n buccal: 'vestibular',\n lingual: 'oral',\n};\n\n/** Conditions that render as per-surface fills in the plan view. */\nexport const CONDITION_PLAN_FOLDER: Partial<Record<ToothCondition, string>> = {\n caries: 'caries',\n filled: 'filling',\n};\n\n/** Resolve a per-surface plan-view overlay URL (e.g. caries on the distal). */\nexport function surfaceSymbolUrl(\n meta: ToothMeta,\n assetBaseUrl: string,\n folder: string,\n surface: Surface,\n): string {\n const view = meta.arch === 'upper' ? 'top' : 'bottom';\n return `${assetBaseUrl}/plan/${folder}/${view}/${assetFdi(meta)}-${SURFACE_ASSET_NAME[surface]}.webp`;\n}\n\n/* ------------------------------------------------------------------ */\n/* Surface zones — interactive per-surface picking (plan view) */\n/* ------------------------------------------------------------------ */\n\n/**\n * Clickable surface regions of a plan-view tooth, named by SCREEN position.\n * `zoneSurface()` resolves each to the anatomical `Surface` per quadrant.\n */\nexport type SurfaceZone = 'top' | 'bottom' | 'left' | 'right' | 'center';\n\n/**\n * Which zones a tooth exposes. Molars & premolars have a central occlusal\n * table (5 zones); incisors & canines have no occlusal surface to mark, so\n * their crown splits into 4 directional zones.\n */\nexport function zonesForTooth(meta: ToothMeta): SurfaceZone[] {\n const fiveZone = meta.anatomy === 'molar' || meta.anatomy === 'premolar';\n return fiveZone\n ? ['top', 'bottom', 'left', 'right', 'center']\n : ['top', 'bottom', 'left', 'right'];\n}\n\n/**\n * Map a screen zone to the anatomical surface, QUADRANT-AWARE. Two flips make\n * this non-trivial (ported from the platform odontogram's `_getAreaBySide`, so\n * a finding always lands on the correct surface wherever the tooth sits):\n *\n * - mesial / distal (left & right zones) flip between the patient's right\n * quadrants {1,4} and left quadrants {2,3} — \"toward the midline\" reverses.\n * - buccal(vestibular) / lingual(oral) (top & bottom zones) flip between the\n * upper and lower arch.\n * - centre is always occlusal.\n *\n * Primary quadrants (5-8) normalise to their permanent counterpart (1-4).\n */\nexport function zoneSurface(meta: ToothMeta, zone: SurfaceZone): Surface {\n if (zone === 'center') return 'occlusal';\n if (zone === 'top') return meta.arch === 'upper' ? 'buccal' : 'lingual';\n if (zone === 'bottom') return meta.arch === 'upper' ? 'lingual' : 'buccal';\n // left / right → mesial / distal, by quadrant.\n const q = meta.quadrant <= 4 ? meta.quadrant : meta.quadrant - 4;\n const leftIsDistal = q === 1 || q === 4; // Q1 (upper-right) & Q4 (lower-left)\n if (zone === 'left') return leftIsDistal ? 'distal' : 'mesial';\n return leftIsDistal ? 'mesial' : 'distal';\n}\n\nconst r2 = (n: number) => Math.round(n * 100) / 100;\n\n/**\n * SVG `d` path for a clickable surface zone within a tooth's local box.\n * 5-zone teeth frame a central occlusal rect with four edge trapezoids;\n * 4-zone teeth split the box along both diagonals into four triangles meeting\n * at the centre.\n */\nexport function zonePath(\n zone: SurfaceZone,\n rect: { x: number; y: number; w: number; h: number },\n hasCenter: boolean,\n): string {\n const { x, y, w, h } = rect;\n const x1 = x + w;\n const y1 = y + h;\n const p = (px: number, py: number) => `${r2(px)} ${r2(py)}`;\n if (!hasCenter) {\n const cx = x + w / 2;\n const cy = y + h / 2;\n switch (zone) {\n case 'top':\n return `M ${p(x, y)} L ${p(x1, y)} L ${p(cx, cy)} Z`;\n case 'bottom':\n return `M ${p(x, y1)} L ${p(x1, y1)} L ${p(cx, cy)} Z`;\n case 'left':\n return `M ${p(x, y)} L ${p(cx, cy)} L ${p(x, y1)} Z`;\n case 'right':\n return `M ${p(x1, y)} L ${p(cx, cy)} L ${p(x1, y1)} Z`;\n default:\n return '';\n }\n }\n // 5-zone: central occlusal rect inset 30% on each axis, four trapezoids.\n const ix = x + w * 0.3;\n const iy = y + h * 0.3;\n const ix1 = x + w * 0.7;\n const iy1 = y + h * 0.7;\n switch (zone) {\n case 'center':\n return `M ${p(ix, iy)} L ${p(ix1, iy)} L ${p(ix1, iy1)} L ${p(ix, iy1)} Z`;\n case 'top':\n return `M ${p(x, y)} L ${p(x1, y)} L ${p(ix1, iy)} L ${p(ix, iy)} Z`;\n case 'bottom':\n return `M ${p(x, y1)} L ${p(x1, y1)} L ${p(ix1, iy1)} L ${p(ix, iy1)} Z`;\n case 'left':\n return `M ${p(x, y)} L ${p(ix, iy)} L ${p(ix, iy1)} L ${p(x, y1)} Z`;\n case 'right':\n return `M ${p(x1, y)} L ${p(ix1, iy)} L ${p(ix1, iy1)} L ${p(x1, y1)} Z`;\n default:\n return '';\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Viewport layout helpers */\n/* */\n/* Image-based rendering: every photoreal render shares a fixed canvas */\n/* (375×808 facial, 328×328 occlusal), so relative tooth size is baked */\n/* into the artwork. The chart lays teeth out in UNIFORM slots and lets */\n/* each image carry its own anatomy. */\n/* ------------------------------------------------------------------ */\n\nexport const TOOTH_GAP = 5;\nexport const TOOTH_ROW_GAP = 40;\nexport const TOOTH_HIT_PAD = 4;\nexport const TOOTH_SLOT_WIDTH = 36;\n/** Facial-view slot block-size; the plan/occlusal view uses a shorter slot. */\nexport const TOOTH_SLOT_HEIGHT = 62;\n\n/**\n * Resolve a tooth render URL. `view` follows the arch (`top` = upper,\n * `bottom` = lower); `symbol` selects the artwork layer (`default` is the\n * healthy tooth; condition/finding overlays use their symbol folder).\n */\nexport function toothImageUrl(\n meta: ToothMeta,\n assetBaseUrl: string,\n symbol = 'default',\n projection: 'side' | 'plan' = 'side',\n): string {\n const view = meta.arch === 'upper' ? 'top' : 'bottom';\n return `${assetBaseUrl}/${projection}/${symbol}/${view}/${assetFdi(meta)}.webp`;\n}\n\nexport interface PositionedTooth {\n fdi: FdiId;\n meta: ToothMeta;\n x: number;\n y: number;\n}\n\n/**\n * Produce the positioned list of teeth for a given dentition. The layout\n * is a two-row grid reading viewer-left-to-right across the upper arch,\n * then viewer-left-to-right across the lower arch. Clinical convention\n * is preserved: the patient's right sits on the viewer's left in both\n * arches, regardless of document direction.\n */\nexport function layoutTeeth(\n dentition: Dentition,\n slotHeight: number = TOOTH_SLOT_HEIGHT,\n): {\n teeth: PositionedTooth[];\n width: number;\n height: number;\n} {\n const ids: FdiId[] =\n dentition === 'primary'\n ? PRIMARY_TEETH\n : dentition === 'mixed'\n ? [...PERMANENT_TEETH, ...PRIMARY_TEETH]\n : PERMANENT_TEETH;\n\n // Upper / lower partition is inferred from meta.arch.\n const upper: FdiId[] = [];\n const lower: FdiId[] = [];\n for (const id of ids) {\n const meta = FDI_TO_META[id];\n if (meta.arch === 'upper') upper.push(id);\n else lower.push(id);\n }\n\n // Uniform slots — relative tooth size is baked into each render, so every\n // tooth occupies the same slot and the artwork carries the anatomy.\n const slot = TOOTH_SLOT_WIDTH + TOOTH_GAP;\n const maxCount = Math.max(upper.length, lower.length);\n const maxRowWidth = maxCount > 0 ? maxCount * slot - TOOTH_GAP : 0;\n\n function positionRow(row: FdiId[], yOffset: number): PositionedTooth[] {\n const rowWidth = row.length > 0 ? row.length * slot - TOOTH_GAP : 0;\n const startX = (maxRowWidth - rowWidth) / 2;\n return row.map((id, i) => {\n const meta = FDI_TO_META[id];\n return {\n fdi: id,\n meta,\n x: startX + i * slot,\n y: yOffset,\n };\n });\n }\n\n const upperRow = positionRow(upper, 0);\n const lowerRow = positionRow(lower, slotHeight + TOOTH_ROW_GAP);\n\n const width = maxRowWidth > 0 ? maxRowWidth : maxCount * slot;\n const height = slotHeight * 2 + TOOTH_ROW_GAP;\n\n return { teeth: [...upperRow, ...lowerRow], width, height };\n}\n\n/* ------------------------------------------------------------------ */\n/* Labels by numbering */\n/* ------------------------------------------------------------------ */\n\nexport function labelFor(id: FdiId, numbering: Numbering): string {\n switch (numbering) {\n case 'universal':\n return FDI_TO_UNIVERSAL[id] ?? id;\n case 'palmer':\n return FDI_TO_PALMER[id] ?? id;\n case 'fdi':\n default:\n return id;\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Chart helpers */\n/* ------------------------------------------------------------------ */\n\nexport function emptyChart(): ToothChart {\n return {};\n}\n\nexport function chartFromConditions(\n conditions: Partial<Record<FdiId, ToothCondition[]>>,\n): ToothChart {\n const chart: ToothChart = {};\n for (const [id, list] of Object.entries(conditions)) {\n if (!list || list.length === 0) continue;\n chart[id] = { conditions: list, surfaces: [] };\n }\n return chart;\n}\n","/* ------------------------------------------------------------------ */\n/* ToothScheme — the consolidated AlfaDocs odontogram. */\n/* */\n/* - Image-base + SVG overlay: each tooth is a photoreal CGI render */\n/* (`<image>`, served from `assetBaseUrl`); the SVG layer on top */\n/* carries findings, selection, keyboard nav and a11y. No third-party */\n/* engine — `src/docs/08-third-party.mdx §Dental chart` rejected the */\n/* candidates (jQuery, abandoned, poor a11y). */\n/* */\n/* - Colour-not-alone: whole-tooth findings render a DISTINCT symbol */\n/* glyph image (the shape is the channel) + the tooth's accessible */\n/* name + tooltip; surface findings (caries / filled) render a */\n/* per-surface fill in the plan view, or a distinguished ring (solid */\n/* = caries, dashed = filled) + legend + tooltip in the side view. */\n/* */\n/* - Two projections: `side` (facial — whole-tooth findings) and */\n/* `plan` (occlusal — per-surface caries / fillings, the \"Stato */\n/* dentale\" marking). Per-arch orientation comes from the top/bottom */\n/* view folders, so the SVG never mirrors. */\n/* */\n/* - Chart orientation is FIXED to clinical convention: the patient's */\n/* right sits on the viewer's left in both arches regardless of */\n/* `dir`. A mirrored chart would invert every recorded diagnosis. */\n/* ------------------------------------------------------------------ */\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type KeyboardEvent,\n type ReactElement,\n} from 'react';\nimport * as RadixTooltip from '@radix-ui/react-tooltip';\nimport { cva } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { Settings } from 'lucide-react';\nimport { useAgentRegistration } from '../../agent/registry';\nimport { IconButton } from '../button';\nimport { Popover } from '../popover';\nimport { Select } from '../select';\nimport { Switch } from '../switch';\nimport { toothSchemeAgent } from './tooth-scheme.agent';\n\nimport {\n CONDITION_COLORS,\n CONDITION_SIDE_SYMBOL,\n CONDITION_PLAN_FOLDER,\n conditionSymbolUrl,\n surfaceSymbolUrl,\n zonesForTooth,\n zoneSurface,\n zonePath,\n FDI_TO_META,\n PERMANENT_TEETH,\n PRIMARY_TEETH,\n TOOTH_HIT_PAD,\n TOOTH_SLOT_WIDTH,\n TOOTH_SLOT_HEIGHT,\n PRIMARY_TOOTH_SCALE,\n chartFromConditions,\n labelFor,\n layoutTeeth,\n toothImageUrl,\n type Projection,\n type ViewMode,\n type Dentition,\n type FdiId,\n type Numbering,\n type Surface,\n type SurfaceZone,\n type ToothChart,\n type ToothCondition,\n type ToothMode,\n type ToothState,\n} from './tooth-data';\n\n/* ------------------------------------------------------------------ */\n/* Public types */\n/* ------------------------------------------------------------------ */\n\nexport interface ToothSchemeProps {\n /** Opaque instance id — emitted as `data-component-id` for the agent registry. */\n id?: string;\n /** Which teeth to render. Default `'permanent'`. */\n dentition?: Dentition;\n /** Numbering system shown on each tooth label. Default `'fdi'`. */\n numbering?: Numbering;\n /** `'interactive'` enables selection + keyboard nav; `'display'` is read-only. Default `'interactive'`. */\n mode?: ToothMode;\n /**\n * `'side'` shows the facial view (whole-tooth findings); `'plan'` shows the\n * occlusal view (per-surface caries / fillings); `'both'` stacks the side\n * chart above an interactive occlusal companion that shares the chart data.\n * Default `'side'`.\n */\n projection?: ViewMode;\n /** Controlled chart state — the full `Record<FdiId, ToothState>`. */\n value?: ToothChart;\n /** Uncontrolled initial chart state. */\n defaultValue?: ToothChart;\n /** Fired whenever the chart changes — always emits FDI ids. */\n onChange?: (next: ToothChart) => void;\n /** Fired when a tooth is focused or clicked. */\n onSelect?: (toothId: FdiId) => void;\n /** Shortcut for pre-populating conditions: `{ '16': ['caries'], '36': ['filled'] }`. */\n conditions?: Partial<Record<FdiId, ToothCondition[]>>;\n /**\n * The finding a tooth-click applies in interactive mode (the selected\n * tool in a Findings / \"Stato dentale\" panel). Clicking a tooth toggles\n * this condition. When unset, clicking cycles caries on/off.\n */\n activeCondition?: ToothCondition;\n /**\n * Base URL the photoreal tooth renders are served from. Resolves to\n * `${assetBaseUrl}/{side|plan}/{symbol}/{top|bottom}/{fdi}.webp`. Defaults\n * to the kit's bundled static path; host the `toothscheme/teeth` set\n * elsewhere (CDN) by overriding this. Trusted developer-controlled value —\n * interpolated into an SVG `<image href>` (which cannot execute scripts),\n * not user input; intentionally not runtime-validated.\n */\n assetBaseUrl?: string;\n /**\n * Show the finding-colour legend below the chart. Default `false` — findings\n * render as distinct symbol glyphs (self-describing) + tooltips, so the\n * colour key is opt-in; a consuming \"Stato dentale\" surface uses a\n * finding-type panel as its key instead.\n */\n legend?: boolean;\n /**\n * Render a built-in options affordance: a gear button that opens a popover\n * letting the END USER toggle the legend and switch dentition / numbering /\n * projection. Opt-in — when on, those four become user-overridable (seeded\n * from the props); when off, the props drive the chart directly. Default\n * `false`.\n */\n controls?: boolean;\n /** Accessible label override for the chart region. */\n ariaLabel?: string;\n /** Extra class names on the wrapper. */\n className?: string;\n}\n\nexport interface ToothSchemeHandle {\n /** Programmatically focus a tooth by FDI id. */\n focusTooth: (id: FdiId) => void;\n /** Read the current chart state. */\n getChart: () => ToothChart;\n /** Read the findings recorded on one tooth (empty array if none). */\n getTooth: (id: FdiId) => ToothCondition[];\n /** Read the FDI ids of every tooth carrying a given finding. */\n teethWith: (condition: ToothCondition) => FdiId[];\n /** Add a finding to a tooth (no-op if already present). Optionally mark surfaces. */\n setFinding: (\n id: FdiId,\n condition: ToothCondition,\n surfaces?: Surface[],\n ) => void;\n /** Remove a finding from a tooth (no-op if absent). */\n removeFinding: (id: FdiId, condition: ToothCondition) => void;\n /** Clear every finding from a tooth. */\n clearTooth: (id: FdiId) => void;\n}\n\n/* ------------------------------------------------------------------ */\n/* CVA */\n/* ------------------------------------------------------------------ */\n\nconst rootVariants = cva(\n [\n 'ds:tooth-scheme-alfadocs',\n 'ds:flex ds:flex-col',\n 'ds:gap-[var(--spacing-sm)]',\n 'ds:bg-[var(--background)] ds:text-[var(--foreground)]',\n // Block-level flex column so the SVG's w-full can grow to fill the frame.\n // min-w-fit on the SVG floors it at natural size, so narrow frames scroll\n // here rather than shrinking the per-tooth hit target below the 44px floor.\n 'ds:max-w-full ds:overflow-x-auto',\n // When the scroll container itself is the tab stop (display mode, see\n // scrollTabIndex) it needs a focus ring. Use an outline (not box-shadow):\n // forced-colors / high-contrast mode zeroes box-shadow, which would leave\n // HCM keyboard users with no visible focus. outline survives, and the\n // CanvasText fallback repaints it in the system palette.\n 'ds:outline-none',\n 'ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-solid',\n 'ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)]',\n 'ds:forced-colors:focus-visible:outline-[CanvasText]',\n ].join(' '),\n);\n\n// The SVG renders at its natural pixel size (set via width/height below) so\n// Responsive: the SVG grows to FILL a wider frame (w-full) but never scales\n// below its natural size (min-w-fit floors it at the viewBox's intrinsic\n// width), so the per-tooth target keeps its ≥44px floor and the root scrolls on\n// narrow frames instead of shrinking. h-auto preserves the viewBox aspect.\nconst svgVariants = cva(\n [\n 'ds:block',\n 'ds:w-full ds:min-w-fit ds:h-auto',\n 'ds:text-[var(--foreground)]',\n ].join(' '),\n);\n\n// `group` here is load-bearing — the focus-ring <rect> below uses\n// `group-focus-visible:opacity-100` to appear when the parent <g> takes\n// focus. Without this class the selector never fires and keyboard users\n// see no focus indicator (a11y-critical-fixes.mdx).\nconst toothGroupVariants = cva(\n [\n 'ds:group ds:cursor-pointer',\n 'ds:focus:outline-none',\n 'ds:transition-[fill,stroke,opacity] ds:duration-[var(--animation-duration)]',\n 'ds:motion-reduce:transition-none',\n ].join(' '),\n);\n\nconst legendVariants = cva(\n [\n 'ds:flex ds:flex-wrap ds:items-center',\n 'ds:gap-[var(--spacing-sm)]',\n 'type-meta ds:text-[var(--muted-foreground)]',\n ].join(' '),\n);\n\nconst legendItemVariants = cva(\n ['ds:inline-flex ds:items-center', 'ds:gap-[var(--spacing-xs)]'].join(' '),\n);\n\nconst liveRegionVariants = cva(['ds:sr-only'].join(' '));\n\n// Surface-zone affordance (plan view, interactive surface-picking). Invisible\n// until hovered (pointer) or focused (keyboard, drilled-in) — the marked\n// surface itself is shown by the photoreal per-surface overlay underneath.\nconst zoneVariants = cva(\n [\n 'ds:cursor-pointer ds:outline-none',\n 'ds:fill-transparent ds:stroke-transparent',\n 'ds:transition-[fill] ds:duration-[var(--animation-duration)]',\n 'ds:motion-reduce:transition-none',\n 'ds:hover:fill-[color-mix(in_srgb,var(--accent)_18%,transparent)]',\n 'ds:focus-visible:fill-[color-mix(in_srgb,var(--accent)_22%,transparent)]',\n 'ds:focus-visible:stroke-[var(--ring)]',\n 'ds:forced-colors:focus-visible:stroke-[CanvasText]',\n ].join(' '),\n);\n\n/* ------------------------------------------------------------------ */\n/* Helpers */\n/* ------------------------------------------------------------------ */\n\nfunction isControlled(props: Pick<ToothSchemeProps, 'value'>): boolean {\n return props.value !== undefined;\n}\n\nfunction emptyState(): ToothState {\n return { conditions: [], surfaces: [] };\n}\n\nfunction cycleCondition(\n current: ToothCondition[] | undefined,\n active?: ToothCondition,\n): ToothCondition[] {\n const list = current ?? [];\n // No tool selected → cycle caries on/off (legacy demo behaviour).\n if (!active) return list.length === 0 ? ['caries'] : [];\n // A finding tool is selected → toggle it on this tooth.\n return list.includes(active)\n ? list.filter((condition) => condition !== active)\n : [...list, active];\n}\n\n/**\n * Drill-in arrow navigation between a tooth's surface zones. 4-zone teeth map\n * each arrow straight to its zone; 5-zone teeth pivot through the occlusal\n * centre (every edge connects only to the centre, the centre to all edges).\n */\nfunction nextZone(\n zone: SurfaceZone,\n key: string,\n hasCenter: boolean,\n): SurfaceZone {\n if (!hasCenter) {\n if (key === 'ArrowUp') return 'top';\n if (key === 'ArrowDown') return 'bottom';\n if (key === 'ArrowLeft') return 'left';\n if (key === 'ArrowRight') return 'right';\n return zone;\n }\n const hub: Record<SurfaceZone, Partial<Record<string, SurfaceZone>>> = {\n top: { ArrowDown: 'center' },\n bottom: { ArrowUp: 'center' },\n left: { ArrowRight: 'center' },\n right: { ArrowLeft: 'center' },\n center: {\n ArrowUp: 'top',\n ArrowDown: 'bottom',\n ArrowLeft: 'left',\n ArrowRight: 'right',\n },\n };\n return hub[zone]?.[key] ?? zone;\n}\n\n/* ------------------------------------------------------------------ */\n/* Legend */\n/* */\n/* Each entry shows the finding EXACTLY as it renders on the chart — a */\n/* mini tooth (the upper-right molar) carrying that finding's symbol */\n/* glyph, surface ring, or missing-X — so the key actually maps to the */\n/* scheme rather than to an abstract colour swatch. */\n/* ------------------------------------------------------------------ */\n\nconst ALL_CONDITIONS: ToothCondition[] = [\n 'caries',\n 'filled',\n 'crowned',\n 'temporaryCrown',\n 'bridge',\n 'rootCanal',\n 'implant',\n 'implantExtraction',\n 'stub',\n 'destroyed',\n 'missing',\n];\n\n/** A 36×62 mini tooth rendering one finding, mirroring the chart's visuals. */\nfunction LegendSymbol({\n condition,\n assetBaseUrl,\n}: {\n condition: ToothCondition;\n assetBaseUrl: string;\n}): ReactElement {\n const meta = FDI_TO_META['16'];\n const spec = CONDITION_SIDE_SYMBOL[condition];\n const isMissing = condition === 'missing';\n const isSurface = condition === 'caries' || condition === 'filled';\n // Match the chart: the render is inset (scaled 0.9) so the surface ring and\n // missing-X never touch the tooth — the same fix applied to the live teeth.\n const w = 36 * 0.9;\n const h = 62 * 0.9;\n const x = (36 - w) / 2;\n const y = (62 - h) / 2;\n return (\n <svg\n width=\"22\"\n height=\"38\"\n viewBox=\"0 0 36 62\"\n aria-hidden=\"true\"\n className=\"ds:block ds:shrink-0\"\n >\n <image\n href={toothImageUrl(meta, assetBaseUrl)}\n x={x}\n y={y}\n width={w}\n height={h}\n preserveAspectRatio=\"xMidYMid meet\"\n opacity={isMissing ? 0.25 : 1}\n />\n {spec ? (\n <image\n href={conditionSymbolUrl(meta, assetBaseUrl, spec)}\n x={x}\n y={y}\n width={w}\n height={h}\n preserveAspectRatio=\"xMidYMid meet\"\n />\n ) : null}\n {isSurface ? (\n <rect\n x={3}\n y={2}\n width={30}\n height={58}\n fill=\"none\"\n stroke={CONDITION_COLORS[condition]}\n strokeWidth={2}\n strokeDasharray={condition === 'filled' ? '4 2' : undefined}\n rx={6}\n />\n ) : null}\n {isMissing ? (\n <path\n d=\"M7 9 L29 53 M29 9 L7 53\"\n stroke={CONDITION_COLORS.missing}\n strokeWidth={2}\n fill=\"none\"\n />\n ) : null}\n </svg>\n );\n}\n\nfunction Legend({ assetBaseUrl }: { assetBaseUrl: string }): ReactElement {\n const { t } = useTranslation();\n return (\n <section\n aria-label={t('toothScheme.legendLabel')}\n data-testid=\"tooth-scheme-legend\"\n // Matches the Card `elevated` variant exactly: --card surface, a subtle\n // --card-border, --shadow-card, and the thicker border in the accessible\n // theme. --muted-foreground on --card stays a validated pair.\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xs)] ds:rounded-[var(--radius-md)] ds:bg-[var(--card)] ds:border ds:border-[color:var(--card-border)] ds:p-[var(--spacing-sm)] ds:shadow-[var(--shadow-card)] ds:[.theme-accessible_&]:border-2\"\n >\n <span className=\"type-eyebrow ds:text-[color:var(--muted-foreground)]\">\n {t('toothScheme.legendLabel')}\n </span>\n <ul className={legendVariants()}>\n {ALL_CONDITIONS.map((condition) => (\n <li\n key={condition}\n className={legendItemVariants()}\n data-condition={condition}\n data-testid={`tooth-scheme-legend-item-${condition}`}\n >\n <LegendSymbol condition={condition} assetBaseUrl={assetBaseUrl} />\n <span>{t(`toothScheme.condition.${condition}`)}</span>\n </li>\n ))}\n </ul>\n </section>\n );\n}\n\n// One labelled Select row inside the options popover. The label stacks ABOVE a\n// full-width Select so long option values (e.g. \"Permanent dentition\") fit on\n// one line. The visible label is `aria-hidden` (sighted-only) and duplicated as\n// the Select's `aria-label`, so screen-reader users get the name once.\nfunction OptionRow({\n label,\n value,\n options,\n onValueChange,\n container,\n}: {\n label: string;\n value: string;\n options: { value: string; label: string }[];\n onValueChange: (value: string) => void;\n container?: HTMLElement | null;\n}): ReactElement {\n return (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-2xs)]\">\n <span\n aria-hidden=\"true\"\n className=\"type-label ds:text-[color:var(--muted-foreground)]\"\n >\n {label}\n </span>\n <Select\n size=\"md\"\n aria-label={label}\n value={value}\n options={options}\n onValueChange={onValueChange}\n // Portal the listbox into the popover so it renders above it (the\n // global z-scale puts --z-dropdown below --z-popover).\n container={container}\n />\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Component */\n/* ------------------------------------------------------------------ */\n\nexport const ToothScheme = forwardRef<ToothSchemeHandle, ToothSchemeProps>(\n (\n {\n id,\n dentition: dentitionProp = 'permanent',\n numbering: numberingProp = 'fdi',\n mode = 'interactive',\n projection: projectionProp = 'side',\n value,\n defaultValue,\n onChange,\n onSelect,\n conditions,\n activeCondition,\n assetBaseUrl = '/toothscheme/teeth',\n legend: legendProp = false,\n controls = false,\n ariaLabel,\n className,\n },\n ref,\n ) => {\n const { t } = useTranslation();\n\n // User-facing options (the gear popover). When `controls` is on, the\n // chart's dentition / numbering / projection / legend become\n // user-overridable: internal state is seeded from the props and the popover\n // updates it. When `controls` is off, the props drive the chart directly\n // (the component stays fully controlled for consumers who don't want it).\n const [uiDentition, setUiDentition] = useState<Dentition>(dentitionProp);\n const [uiNumbering, setUiNumbering] = useState<Numbering>(numberingProp);\n const [uiProjection, setUiProjection] = useState<ViewMode>(projectionProp);\n const [uiLegend, setUiLegend] = useState<boolean>(legendProp);\n\n // Portal target for the option selects, captured from the popover content\n // so their listboxes render above the popover (not behind it).\n const [optionsPopoverEl, setOptionsPopoverEl] =\n useState<HTMLElement | null>(null);\n\n const dentition = controls ? uiDentition : dentitionProp;\n const numbering = controls ? uiNumbering : numberingProp;\n const legend = controls ? uiLegend : legendProp;\n // The View select can pick a single projection or 'both'. The main chart\n // renders one projection; 'both' additionally renders an occlusal companion\n // (a nested ToothScheme sharing the chart data) so both stay interactive.\n const viewMode: ViewMode = controls ? uiProjection : projectionProp;\n const projection: Projection = viewMode === 'plan' ? 'plan' : 'side';\n const showCompanion = viewMode === 'both';\n\n // The `ui*` seeds above only run once (at mount). If `controls` flips on\n // later, re-seed from the current props so the popover opens from the\n // consumer's latest values rather than the stale mount-time snapshot.\n const controlsWasOn = useRef(controls);\n useEffect(() => {\n if (controls && !controlsWasOn.current) {\n setUiDentition(dentitionProp);\n setUiNumbering(numberingProp);\n setUiProjection(projectionProp);\n setUiLegend(legendProp);\n }\n controlsWasOn.current = controls;\n }, [controls, dentitionProp, numberingProp, projectionProp, legendProp]);\n\n // Chart state — controlled / uncontrolled.\n const [internalChart, setInternalChart] = useState<ToothChart>(() => {\n if (value !== undefined) return value;\n if (defaultValue !== undefined) return defaultValue;\n if (conditions) return chartFromConditions(conditions);\n return {};\n });\n\n const chart: ToothChart = isControlled({ value })\n ? (value as ToothChart)\n : internalChart;\n\n // Keep uncontrolled chart in sync when `conditions` shortcut updates.\n useEffect(() => {\n if (isControlled({ value })) return;\n if (conditions === undefined) return;\n setInternalChart(chartFromConditions(conditions));\n // Intentionally depend on a stable key — swapping the entire prop\n // snapshot is the consumer's signal to reset.\n }, [conditions, value]);\n\n // Plan (occlusal) teeth are square; facial teeth are tall (crown + root).\n const slotHeight = projection === 'plan' ? 44 : TOOTH_SLOT_HEIGHT;\n\n // Layout.\n const layout = useMemo(\n () => layoutTeeth(dentition, slotHeight),\n [dentition, slotHeight],\n );\n const visibleIds = useMemo(() => layout.teeth.map((t) => t.fdi), [layout]);\n\n // Roving tabindex: which tooth owns `tabindex=\"0\"`.\n const firstId: FdiId | undefined = visibleIds[0];\n const [focusedId, setFocusedId] = useState<FdiId | undefined>(firstId);\n useEffect(() => {\n if (!focusedId || !visibleIds.includes(focusedId)) {\n setFocusedId(firstId);\n }\n }, [firstId, focusedId, visibleIds]);\n\n // Live-region announcement text.\n const [announcement, setAnnouncement] = useState<string>('');\n\n const svgRef = useRef<SVGSVGElement>(null);\n\n const commitChart = useCallback(\n (next: ToothChart) => {\n if (!isControlled({ value })) {\n setInternalChart(next);\n }\n onChange?.(next);\n },\n [onChange, value],\n );\n\n const focusToothById = useCallback((id: FdiId) => {\n const svg = svgRef.current;\n if (!svg) return;\n const node = svg.querySelector<SVGGElement>(`g[data-fdi=\"${id}\"]`);\n if (node) {\n node.focus();\n setFocusedId(id);\n }\n }, []);\n\n /* ------------------------------------------------------------- */\n /* Per-surface picking (plan view, caries / filling) */\n /* ------------------------------------------------------------- */\n\n // Live only in the occlusal (plan) view while a surface finding\n // (caries / filling) is the active tool.\n const surfacePicking =\n projection === 'plan' &&\n mode === 'interactive' &&\n (activeCondition === 'caries' || activeCondition === 'filled');\n\n // Drill-in state: the tooth + zone the keyboard is currently inside\n // (null = tooth-level navigation, the default).\n const [drilled, setDrilled] = useState<{\n fdi: FdiId;\n zone: SurfaceZone;\n } | null>(null);\n\n // End the drill if surface-picking turns off (tool / view change) OR the\n // drilled tooth leaves the layout (e.g. the dentition changes mid-drill) —\n // a stale drill would leave every tooth tabIndex=-1 and lock out keyboard.\n useEffect(() => {\n if (!surfacePicking || (drilled && !visibleIds.includes(drilled.fdi))) {\n setDrilled(null);\n }\n }, [surfacePicking, drilled, visibleIds]);\n\n const focusZone = useCallback((fdi: FdiId, zone: SurfaceZone) => {\n const node = svgRef.current?.querySelector<SVGPathElement>(\n `path[data-fdi=\"${fdi}\"][data-zone=\"${zone}\"]`,\n );\n node?.focus();\n }, []);\n\n // Move DOM focus onto the drilled zone when it changes; restore focus to\n // the tooth when the drill ends (Escape).\n const prevDrilled = useRef<{ fdi: FdiId; zone: SurfaceZone } | null>(null);\n useEffect(() => {\n if (drilled) {\n focusZone(drilled.fdi, drilled.zone);\n } else if (prevDrilled.current) {\n focusToothById(prevDrilled.current.fdi);\n }\n prevDrilled.current = drilled;\n }, [drilled, focusZone, focusToothById]);\n\n const isSurfaceMarked = useCallback(\n (fdi: FdiId, surface: Surface): boolean => {\n const state = chart[fdi];\n return Boolean(\n state &&\n state.surfaces.includes(surface) &&\n state.conditions.includes(activeCondition as ToothCondition),\n );\n },\n [chart, activeCondition],\n );\n\n const announceTooth = useCallback(\n (fdi: FdiId, state: ToothState | undefined) => {\n if (state && state.conditions.length > 0) {\n setAnnouncement(\n t('toothScheme.selected', {\n id: fdi,\n conditions: state.conditions\n .map((c) => t(`toothScheme.condition.${c}`))\n .join(', '),\n }),\n );\n } else {\n setAnnouncement(t('toothScheme.deselected', { id: fdi }));\n }\n },\n [t],\n );\n\n const toggleSurface = useCallback(\n (fdi: FdiId, surface: Surface) => {\n if (!surfacePicking) return;\n const cond = activeCondition as ToothCondition;\n const prev = chart[fdi] ?? emptyState();\n const hadSurface = prev.surfaces.includes(surface);\n const hadCond = prev.conditions.includes(cond);\n let surfaces: Surface[];\n let conditions: ToothCondition[];\n if (hadSurface && hadCond) {\n surfaces = prev.surfaces.filter((s) => s !== surface);\n conditions =\n surfaces.length === 0\n ? prev.conditions.filter((c) => c !== cond)\n : prev.conditions;\n } else {\n surfaces = hadSurface ? prev.surfaces : [...prev.surfaces, surface];\n conditions = hadCond ? prev.conditions : [...prev.conditions, cond];\n }\n const next: ToothChart = { ...chart };\n if (conditions.length === 0 && surfaces.length === 0) {\n delete next[fdi];\n } else {\n next[fdi] = { ...prev, conditions, surfaces };\n }\n commitChart(next);\n announceTooth(fdi, next[fdi]);\n onSelect?.(fdi);\n },\n [\n surfacePicking,\n activeCondition,\n chart,\n commitChart,\n announceTooth,\n onSelect,\n ],\n );\n\n const drillInto = useCallback((fdi: FdiId) => {\n const meta = FDI_TO_META[fdi];\n if (!meta) return;\n setFocusedId(fdi);\n setDrilled({ fdi, zone: zonesForTooth(meta)[0] });\n }, []);\n\n const handleSurfaceKeyDown = useCallback(\n (event: KeyboardEvent<SVGPathElement>, fdi: FdiId, zone: SurfaceZone) => {\n const meta = FDI_TO_META[fdi];\n if (!meta) return;\n switch (event.key) {\n case 'ArrowUp':\n case 'ArrowDown':\n case 'ArrowLeft':\n case 'ArrowRight': {\n event.preventDefault();\n event.stopPropagation();\n const hasCenter = zonesForTooth(meta).includes('center');\n const nz = nextZone(zone, event.key, hasCenter);\n if (nz !== zone) setDrilled({ fdi, zone: nz });\n return;\n }\n case ' ':\n case 'Enter': {\n event.preventDefault();\n event.stopPropagation();\n toggleSurface(fdi, zoneSurface(meta, zone));\n return;\n }\n case 'Escape': {\n event.preventDefault();\n event.stopPropagation();\n setDrilled(null);\n return;\n }\n default:\n return;\n }\n },\n [toggleSurface],\n );\n\n const rootRef = useRef<HTMLDivElement>(null);\n\n const agentHandle = useMemo<ToothSchemeHandle>(\n () => ({\n focusTooth: (id: FdiId) => {\n focusToothById(id);\n },\n getChart: () => chart,\n getTooth: (id: FdiId) => chart[id]?.conditions ?? [],\n teethWith: (condition: ToothCondition) =>\n Object.entries(chart)\n .filter(([, state]) => state.conditions.includes(condition))\n .map(([fdi]) => fdi),\n setFinding: (\n id: FdiId,\n condition: ToothCondition,\n surfaces?: Surface[],\n ) => {\n const prev = chart[id] ?? emptyState();\n if (prev.conditions.includes(condition) && !surfaces) return;\n const next: ToothChart = {\n ...chart,\n [id]: {\n ...prev,\n conditions: prev.conditions.includes(condition)\n ? prev.conditions\n : [...prev.conditions, condition],\n surfaces: surfaces ?? prev.surfaces,\n },\n };\n commitChart(next);\n },\n removeFinding: (id: FdiId, condition: ToothCondition) => {\n const prev = chart[id];\n if (!prev || !prev.conditions.includes(condition)) return;\n const conditions = prev.conditions.filter((c) => c !== condition);\n const next: ToothChart = { ...chart };\n if (conditions.length === 0) {\n delete next[id];\n } else {\n next[id] = { ...prev, conditions };\n }\n commitChart(next);\n },\n clearTooth: (id: FdiId) => {\n if (!chart[id]) return;\n const next: ToothChart = { ...chart };\n delete next[id];\n commitChart(next);\n },\n }),\n [chart, commitChart, focusToothById],\n );\n useImperativeHandle(ref, () => agentHandle, [agentHandle]);\n useAgentRegistration(toothSchemeAgent, agentHandle, id);\n\n /* ------------------------------------------------------------- */\n /* Keyboard navigation */\n /* ------------------------------------------------------------- */\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<SVGGElement>, id: FdiId) => {\n if (mode !== 'interactive') return;\n const meta = FDI_TO_META[id];\n if (!meta) return;\n\n // Resolve the ordered list of visible ids for Left/Right.\n const order = visibleIds;\n const currentIndex = order.indexOf(id);\n\n switch (event.key) {\n case 'ArrowRight': {\n event.preventDefault();\n const nextIndex = Math.min(order.length - 1, currentIndex + 1);\n focusToothById(order[nextIndex]);\n return;\n }\n case 'ArrowLeft': {\n event.preventDefault();\n const nextIndex = Math.max(0, currentIndex - 1);\n focusToothById(order[nextIndex]);\n return;\n }\n case 'ArrowUp':\n case 'ArrowDown': {\n event.preventDefault();\n // Map to the opposing arch, same FDI position where possible.\n // Permanent: 1 ↔ 4, 2 ↔ 3. Primary: 5 ↔ 8, 6 ↔ 7.\n const quadrantMap: Record<number, number> = {\n 1: 4,\n 4: 1,\n 2: 3,\n 3: 2,\n 5: 8,\n 8: 5,\n 6: 7,\n 7: 6,\n };\n const targetQuadrant = quadrantMap[meta.quadrant];\n if (!targetQuadrant) return;\n const candidate = `${targetQuadrant}${meta.positionInQuadrant}`;\n if (order.includes(candidate)) {\n focusToothById(candidate);\n }\n return;\n }\n case 'Home': {\n event.preventDefault();\n // First tooth in the same quadrant (visible).\n const inQuadrant = order.filter(\n (other) => FDI_TO_META[other].quadrant === meta.quadrant,\n );\n if (inQuadrant.length > 0) {\n focusToothById(inQuadrant[0]);\n }\n return;\n }\n case 'End': {\n event.preventDefault();\n const inQuadrant = order.filter(\n (other) => FDI_TO_META[other].quadrant === meta.quadrant,\n );\n if (inQuadrant.length > 0) {\n focusToothById(inQuadrant[inQuadrant.length - 1]);\n }\n return;\n }\n case ' ':\n case 'Enter': {\n event.preventDefault();\n // In surface-picking mode, Enter drills into the tooth's surfaces\n // rather than toggling the whole tooth.\n if (surfacePicking) {\n drillInto(id);\n return;\n }\n const prev = chart[id]?.conditions;\n const nextConditions = cycleCondition(prev, activeCondition);\n const next: ToothChart = { ...chart };\n if (nextConditions.length === 0) {\n delete next[id];\n setAnnouncement(t('toothScheme.deselected', { id }));\n } else {\n next[id] = {\n ...(chart[id] ?? emptyState()),\n conditions: nextConditions,\n };\n setAnnouncement(\n t('toothScheme.selected', {\n id,\n conditions: nextConditions\n .map((c) => t(`toothScheme.condition.${c}`))\n .join(', '),\n }),\n );\n }\n commitChart(next);\n onSelect?.(id);\n return;\n }\n default:\n return;\n }\n },\n [\n activeCondition,\n chart,\n commitChart,\n drillInto,\n focusToothById,\n mode,\n onSelect,\n surfacePicking,\n t,\n visibleIds,\n ],\n );\n\n const handleClick = useCallback(\n (id: FdiId) => {\n if (mode !== 'interactive') return;\n setFocusedId(id);\n const prev = chart[id]?.conditions;\n const nextConditions = cycleCondition(prev, activeCondition);\n const next: ToothChart = { ...chart };\n if (nextConditions.length === 0) {\n delete next[id];\n setAnnouncement(t('toothScheme.deselected', { id }));\n } else {\n next[id] = {\n ...(chart[id] ?? emptyState()),\n conditions: nextConditions,\n };\n setAnnouncement(\n t('toothScheme.selected', {\n id,\n conditions: nextConditions\n .map((c) => t(`toothScheme.condition.${c}`))\n .join(', '),\n }),\n );\n }\n commitChart(next);\n onSelect?.(id);\n },\n [activeCondition, chart, commitChart, mode, onSelect, t],\n );\n\n /* ------------------------------------------------------------- */\n /* Rendering */\n /* ------------------------------------------------------------- */\n\n const viewBoxWidth = layout.width + 4; // small padding for stroke\n const viewBoxHeight = layout.height + 4;\n\n const baseLabel = ariaLabel ?? t('toothScheme.ariaLabel');\n // When both views show, name each region by its projection so the two\n // landmarks stay distinguishable (landmark-unique).\n const regionLabel = showCompanion\n ? `${baseLabel} — ${t('toothScheme.view.side')}`\n : baseLabel;\n\n // The root is horizontally scrollable (`overflow-x-auto`), so it needs\n // keyboard access. In interactive mode the roving-tabindex teeth supply\n // it; in display mode nothing inside is focusable, so the scroll\n // container itself becomes the tab stop (WCAG 2.1.1 / axe\n // scrollable-region-focusable).\n const scrollTabIndex = mode === 'interactive' ? undefined : 0;\n\n return (\n <RadixTooltip.Provider delayDuration={200}>\n <div\n ref={rootRef}\n role=\"region\"\n aria-label={regionLabel}\n tabIndex={scrollTabIndex}\n className={[rootVariants(), className].filter(Boolean).join(' ')}\n data-component=\"tooth-scheme\"\n data-component-id={id}\n data-testid=\"tooth-scheme-root\"\n data-dentition={dentition}\n data-numbering={numbering}\n data-mode={mode}\n data-projection={projection}\n data-view-mode={viewMode}\n >\n {/* User-facing options: a gear button opening a popover to toggle the\n legend and switch dentition / numbering / projection. */}\n {controls ? (\n <div\n className=\"ds:flex ds:w-full ds:justify-end\"\n data-testid=\"tooth-scheme-controls\"\n >\n <Popover.Root>\n <Popover.Trigger asChild>\n <IconButton\n size=\"sm\"\n intent=\"outline\"\n icon={<Settings aria-hidden=\"true\" />}\n aria-label={t('toothScheme.options.label')}\n tooltip={t('toothScheme.options.label')}\n />\n </Popover.Trigger>\n <Popover.Content\n ref={setOptionsPopoverEl}\n align=\"end\"\n size=\"md\"\n aria-label={t('toothScheme.options.label')}\n >\n <div className=\"ds:flex ds:w-[var(--popover-size-sm)] ds:max-w-full ds:flex-col ds:gap-[var(--spacing-md)]\">\n {/* Stretch the Switch row full-width so the toggle aligns\n to the inline-end edge like the selects below. */}\n <div className=\"ds:flex ds:border-b ds:border-[color:var(--border)] ds:pb-[var(--spacing-sm)] ds:[&>*]:w-full ds:[&>*]:justify-between\">\n <Switch\n label={t('toothScheme.options.legend')}\n labelSide=\"start\"\n checked={legend}\n onCheckedChange={setUiLegend}\n />\n </div>\n <OptionRow\n container={optionsPopoverEl}\n label={t('toothScheme.options.dentition')}\n value={dentition}\n onValueChange={(v) => setUiDentition(v as Dentition)}\n options={[\n {\n value: 'permanent',\n label: t('toothScheme.dentition.permanent'),\n },\n {\n value: 'primary',\n label: t('toothScheme.dentition.primary'),\n },\n {\n value: 'mixed',\n label: t('toothScheme.dentition.mixed'),\n },\n ]}\n />\n <OptionRow\n container={optionsPopoverEl}\n label={t('toothScheme.options.numbering')}\n value={numbering}\n onValueChange={(v) => setUiNumbering(v as Numbering)}\n options={[\n {\n value: 'fdi',\n label: t('toothScheme.numbering.fdi'),\n },\n {\n value: 'universal',\n label: t('toothScheme.numbering.universal'),\n },\n {\n value: 'palmer',\n label: t('toothScheme.numbering.palmer'),\n },\n ]}\n />\n <OptionRow\n container={optionsPopoverEl}\n label={t('toothScheme.options.view')}\n value={viewMode}\n onValueChange={(v) => setUiProjection(v as ViewMode)}\n options={[\n {\n value: 'side',\n label: t('toothScheme.view.side'),\n },\n {\n value: 'plan',\n label: t('toothScheme.view.occlusal'),\n },\n {\n value: 'both',\n label: t('toothScheme.view.both'),\n },\n ]}\n />\n </div>\n </Popover.Content>\n </Popover.Root>\n </div>\n ) : null}\n <svg\n ref={svgRef}\n viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}\n width={viewBoxWidth}\n height={viewBoxHeight}\n className={svgVariants()}\n role=\"group\"\n aria-label={regionLabel}\n // Never a tab stop itself — keyboard focus lives on the teeth\n // (interactive) or the scroll container (display). Legacy engines\n // make a labelled SVG focusable without this.\n focusable=\"false\"\n data-testid=\"tooth-scheme-svg\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n {layout.teeth.map((tooth) => {\n const state = chart[tooth.fdi];\n const primaryCondition: ToothCondition | undefined =\n state?.conditions?.[0];\n\n const anatomyLabel = t(\n `toothScheme.anatomy.${tooth.meta.anatomy}`,\n );\n const conditionSuffix = primaryCondition\n ? `, ${t(`toothScheme.condition.${primaryCondition}`)}`\n : '';\n const label = t('toothScheme.toothLabel', {\n id: labelFor(tooth.fdi, numbering),\n anatomy: anatomyLabel,\n conditionSuffix,\n });\n\n const tabIndex =\n mode === 'interactive'\n ? drilled\n ? -1\n : tooth.fdi === focusedId\n ? 0\n : -1\n : undefined;\n\n const conditions = state?.conditions ?? [];\n const isMissing = conditions.includes('missing');\n // Whole-tooth findings (crown, implant, RCT…) overlay a photoreal\n // symbol render on the facial tooth.\n const symbolOverlays = conditions\n .map((condition) => ({\n condition,\n spec: CONDITION_SIDE_SYMBOL[condition],\n }))\n .filter(\n (\n entry,\n ): entry is {\n condition: ToothCondition;\n spec: NonNullable<typeof entry.spec>;\n } => Boolean(entry.spec),\n );\n // Surface findings (caries / filled) have no side symbol — flag\n // them with a token-coloured ring until the plan view lands.\n const surfaceCondition = conditions.find(\n (condition) =>\n condition !== 'missing' && !CONDITION_SIDE_SYMBOL[condition],\n );\n const ringColour = surfaceCondition\n ? CONDITION_COLORS[surfaceCondition]\n : undefined;\n\n const tooltipContent = primaryCondition\n ? `${anatomyLabel} · ${t(`toothScheme.condition.${primaryCondition}`)}`\n : anatomyLabel;\n\n const labelText = labelFor(tooth.fdi, numbering);\n const imageUrl = toothImageUrl(\n tooth.meta,\n assetBaseUrl,\n 'default',\n projection,\n );\n // Plan (occlusal) view: per-surface caries / filling overlays —\n // the \"Stato dentale\" surface marking.\n const surfaceOverlays =\n projection === 'plan'\n ? conditions.flatMap((condition) => {\n const folder = CONDITION_PLAN_FOLDER[condition];\n if (!folder) return [];\n return (state?.surfaces ?? []).map((surface) => ({\n key: `${condition}-${surface}`,\n url: surfaceSymbolUrl(\n tooth.meta,\n assetBaseUrl,\n folder,\n surface,\n ),\n }));\n })\n : [];\n\n // Primary teeth reuse the permanent render scaled down. A small\n // inset on every tooth keeps the render off the slot edges so\n // the focus ring + labels never crowd or touch it.\n const imgScale =\n (tooth.meta.dentition === 'primary' ? PRIMARY_TOOTH_SCALE : 1) *\n 0.9;\n const imgW = TOOTH_SLOT_WIDTH * imgScale;\n const imgH = slotHeight * imgScale;\n const imgX = (TOOTH_SLOT_WIDTH - imgW) / 2;\n const imgY = (slotHeight - imgH) / 2;\n\n // Surface-picking zones (plan view). When drilled into THIS\n // tooth, its zones become the focusable buttons and the tooth\n // group steps back to a plain container (no nested interactive).\n const isDrilled = surfacePicking && drilled?.fdi === tooth.fdi;\n const toothZones = surfacePicking\n ? zonesForTooth(tooth.meta)\n : [];\n const hasCenter = toothZones.includes('center');\n\n return (\n <RadixTooltip.Root key={tooth.fdi}>\n <RadixTooltip.Trigger asChild>\n <g\n data-fdi={tooth.fdi}\n data-anatomy={tooth.meta.anatomy}\n data-arch={tooth.meta.arch}\n data-side={tooth.meta.side}\n data-quadrant={tooth.meta.quadrant}\n data-condition={primaryCondition ?? 'none'}\n data-testid={`tooth-${tooth.fdi}`}\n transform={`translate(${tooth.x}, ${tooth.y})`}\n tabIndex={tabIndex}\n // SVG `<g>` has no implicit role, so `aria-label`\n // would be rejected by axe's `aria-prohibited-attr`\n // rule. Give it an explicit role: `button` in\n // interactive mode, `img` in display mode. When drilled\n // into for surface picking it becomes a `group` so its\n // focusable surface zones aren't nested in a button.\n role={\n isDrilled\n ? 'group'\n : mode === 'interactive'\n ? 'button'\n : 'img'\n }\n aria-label={label}\n aria-pressed={\n isDrilled\n ? undefined\n : mode === 'interactive'\n ? primaryCondition !== undefined\n : undefined\n }\n className={toothGroupVariants()}\n onClick={\n isDrilled\n ? undefined\n : mode === 'interactive'\n ? surfacePicking\n ? () => drillInto(tooth.fdi)\n : () => handleClick(tooth.fdi)\n : undefined\n }\n onFocus={() => setFocusedId(tooth.fdi)}\n onKeyDown={\n isDrilled\n ? undefined\n : (event) => handleKeyDown(event, tooth.fdi)\n }\n >\n {/* Invisible ≥44px hit target (WCAG 2.5.5). */}\n <rect\n x={-TOOTH_HIT_PAD}\n y={-TOOTH_HIT_PAD}\n width={TOOTH_SLOT_WIDTH + TOOTH_HIT_PAD * 2}\n height={slotHeight + TOOTH_HIT_PAD * 2}\n fill=\"transparent\"\n />\n {/* Photoreal tooth render (side = facial, plan =\n occlusal). The upper/lower view folders are\n pre-oriented, so no mirroring is needed. Decorative —\n the parent <g> carries the accessible name. */}\n <image\n href={imageUrl}\n x={imgX}\n y={imgY}\n width={imgW}\n height={imgH}\n preserveAspectRatio=\"xMidYMid meet\"\n opacity={isMissing ? 0.25 : 1}\n aria-hidden=\"true\"\n />\n {/* Side view: whole-tooth symbol overlays (crown,\n implant, RCT, bridge…) composited on the facial\n tooth. The distinct glyph is the colour-independent\n channel; the group's accessible name names it. */}\n {projection === 'side'\n ? symbolOverlays.map(({ condition, spec }) => (\n <image\n key={condition}\n href={conditionSymbolUrl(\n tooth.meta,\n assetBaseUrl,\n spec,\n )}\n x={imgX}\n y={imgY}\n width={imgW}\n height={imgH}\n preserveAspectRatio=\"xMidYMid meet\"\n aria-hidden=\"true\"\n pointerEvents=\"none\"\n />\n ))\n : null}\n {/* Plan view: per-surface caries / filling overlays —\n the \"Stato dentale\" surface marking. */}\n {projection === 'plan'\n ? surfaceOverlays.map(({ key, url }) => (\n <image\n key={key}\n href={url}\n x={imgX}\n y={imgY}\n width={imgW}\n height={imgH}\n preserveAspectRatio=\"xMidYMid meet\"\n aria-hidden=\"true\"\n pointerEvents=\"none\"\n />\n ))\n : null}\n {/* Plan view: interactive surface-picking zones. Pointer\n clicks mark a surface directly; keyboard users drill\n in (Enter) then pick a zone (arrows), so the zones are\n focusable buttons only while THIS tooth is drilled. */}\n {surfacePicking\n ? toothZones.map((zone) => {\n const surface = zoneSurface(tooth.meta, zone);\n const marked = isSurfaceMarked(tooth.fdi, surface);\n return (\n <path\n key={zone}\n d={zonePath(\n zone,\n { x: imgX, y: imgY, w: imgW, h: imgH },\n hasCenter,\n )}\n data-fdi={tooth.fdi}\n data-zone={zone}\n data-surface={surface}\n data-testid={`tooth-${tooth.fdi}-zone-${zone}`}\n role={isDrilled ? 'button' : undefined}\n tabIndex={\n isDrilled\n ? drilled?.zone === zone\n ? 0\n : -1\n : undefined\n }\n aria-label={\n isDrilled\n ? t('toothScheme.toothLabel', {\n id: labelText,\n anatomy: t(\n `toothScheme.surface.${surface}`,\n ),\n conditionSuffix: '',\n })\n : undefined\n }\n aria-pressed={isDrilled ? marked : undefined}\n strokeWidth=\"var(--focus-ring-width)\"\n className={zoneVariants()}\n onClick={(event) => {\n event.stopPropagation();\n if (!isDrilled) setFocusedId(tooth.fdi);\n toggleSurface(tooth.fdi, surface);\n }}\n onKeyDown={\n isDrilled\n ? (event) =>\n handleSurfaceKeyDown(\n event,\n tooth.fdi,\n zone,\n )\n : undefined\n }\n onFocus={\n isDrilled\n ? () =>\n setDrilled((d) =>\n d &&\n d.fdi === tooth.fdi &&\n d.zone === zone\n ? d\n : { fdi: tooth.fdi, zone },\n )\n : undefined\n }\n />\n );\n })\n : null}\n {/* Side view: surface-finding ring (caries / filled)\n stands in until the plan view marks the surface. */}\n {projection === 'side' && ringColour ? (\n <rect\n x={3}\n y={2}\n width={TOOTH_SLOT_WIDTH - 6}\n height={slotHeight - 4}\n fill=\"none\"\n stroke={ringColour}\n strokeWidth=\"2\"\n // Distinguish caries (solid) from filled (dashed) by\n // line style, not colour alone (WCAG 1.4.1).\n strokeDasharray={\n surfaceCondition === 'filled' ? '4 2' : undefined\n }\n rx=\"6\"\n pointerEvents=\"none\"\n />\n ) : null}\n {/* Missing-tooth X glyph over the faded render. */}\n {isMissing ? (\n <path\n d={`M7 9 L${TOOTH_SLOT_WIDTH - 7} ${slotHeight - 9} M${TOOTH_SLOT_WIDTH - 7} 9 L7 ${slotHeight - 9}`}\n stroke={CONDITION_COLORS.missing}\n strokeWidth=\"2\"\n fill=\"none\"\n pointerEvents=\"none\"\n />\n ) : null}\n {/* Focus ring — drawn as a rect so it tracks the hit\n area. `group` on the parent <g> makes this\n group-focus-visible selector fire when a keyboard\n user focuses the tooth. In forced-colors mode the\n ring falls back to `CanvasText` so it stays visible\n when `var(--ring)` is remapped. */}\n <rect\n x={-TOOTH_HIT_PAD}\n y={-TOOTH_HIT_PAD}\n width={TOOTH_SLOT_WIDTH + TOOTH_HIT_PAD * 2}\n height={slotHeight + TOOTH_HIT_PAD * 2}\n fill=\"none\"\n stroke=\"var(--ring)\"\n strokeWidth=\"var(--focus-ring-width)\"\n className={[\n 'ds:opacity-0',\n 'ds:group-focus-visible:opacity-100',\n 'ds:forced-colors:stroke-[CanvasText]',\n ].join(' ')}\n data-testid={`tooth-${tooth.fdi}-focus-ring`}\n pointerEvents=\"none\"\n rx=\"4\"\n />\n {/* Tooth number label — sits below (upper arch) or above (lower) the crown. */}\n <text\n x={TOOTH_SLOT_WIDTH / 2}\n y={tooth.meta.arch === 'upper' ? slotHeight + 12 : -4}\n textAnchor=\"middle\"\n fontSize=\"10\"\n fill=\"var(--muted-foreground)\"\n className=\"ds:select-none\"\n aria-hidden=\"true\"\n >\n {labelText}\n </text>\n </g>\n </RadixTooltip.Trigger>\n <RadixTooltip.Portal>\n <RadixTooltip.Content\n side=\"top\"\n sideOffset={6}\n className=\"ds:z-[var(--z-tooltip)] ds:rounded-[var(--radius-sm)] ds:bg-[var(--foreground)] ds:text-[var(--background)] ds:ps-[var(--spacing-xs)] ds:pe-[var(--spacing-xs)] ds:pt-[calc(var(--spacing-xs)/2)] ds:pb-[calc(var(--spacing-xs)/2)] ds:text-[length:var(--font-size-xs)]\"\n >\n {tooltipContent}\n <RadixTooltip.Arrow className=\"ds:fill-[var(--foreground)]\" />\n </RadixTooltip.Content>\n </RadixTooltip.Portal>\n </RadixTooltip.Root>\n );\n })}\n </svg>\n\n {/* \"Both\" view: an occlusal companion that shares this chart's data,\n so the side chart (above) and the occlusal chart (here) are both\n interactive — edits in either propagate through `value`/`onChange`. */}\n {showCompanion ? (\n <ToothScheme\n projection=\"plan\"\n dentition={dentition}\n numbering={numbering}\n mode={mode}\n value={chart}\n onChange={commitChart}\n activeCondition={activeCondition}\n assetBaseUrl={assetBaseUrl}\n ariaLabel={`${baseLabel} — ${t('toothScheme.view.occlusal')}`}\n />\n ) : null}\n\n {legend ? <Legend assetBaseUrl={assetBaseUrl} /> : null}\n\n <div\n className={liveRegionVariants()}\n aria-live=\"polite\"\n aria-atomic=\"true\"\n data-testid=\"tooth-scheme-live\"\n >\n {announcement}\n </div>\n </div>\n </RadixTooltip.Provider>\n );\n },\n);\n\nToothScheme.displayName = 'ToothScheme';\n\n/* ------------------------------------------------------------------ */\n/* Re-exports */\n/* ------------------------------------------------------------------ */\n\nexport { rootVariants as toothSchemeVariants, PERMANENT_TEETH, PRIMARY_TEETH };\nexport type {\n Dentition,\n FdiId,\n Numbering,\n ToothChart,\n ToothCondition,\n ToothMode,\n ToothState,\n} from './tooth-data';\n"],"names":["toothSchemeAgent","handle","args","PERMANENT_TEETH","PRIMARY_TEETH","permanentAnatomy","positionInQuadrant","primaryAnatomy","palmerCode","quadrant","position","normalised","permanentUniversal","primaryUniversal","letters","index","buildMeta","fdi","isPrimary","anatomy","arch","side","universal","palmer","FDI_TO_META","acc","FDI_TO_UNIVERSAL","meta","FDI_TO_PALMER","assetFdi","PRIMARY_TOOTH_SCALE","CONDITION_TOKENS","CONDITION_COLORS","CONDITION_SIDE_SYMBOL","conditionSymbolUrl","assetBaseUrl","spec","view","base","SURFACE_ASSET_NAME","CONDITION_PLAN_FOLDER","surfaceSymbolUrl","folder","surface","zonesForTooth","zoneSurface","zone","q","leftIsDistal","r2","n","zonePath","rect","hasCenter","x","y","w","h","x1","y1","p","px","py","cx","cy","ix","iy","ix1","iy1","TOOTH_GAP","TOOTH_ROW_GAP","TOOTH_HIT_PAD","TOOTH_SLOT_WIDTH","TOOTH_SLOT_HEIGHT","toothImageUrl","symbol","projection","layoutTeeth","dentition","slotHeight","ids","upper","lower","id","slot","maxCount","maxRowWidth","positionRow","row","yOffset","rowWidth","startX","i","upperRow","lowerRow","width","height","labelFor","numbering","emptyChart","chartFromConditions","conditions","chart","list","rootVariants","cva","svgVariants","toothGroupVariants","legendVariants","legendItemVariants","liveRegionVariants","zoneVariants","isControlled","props","emptyState","cycleCondition","current","active","condition","nextZone","key","_a","ALL_CONDITIONS","LegendSymbol","isMissing","isSurface","jsxs","jsx","Legend","t","useTranslation","OptionRow","label","value","options","onValueChange","container","Select","ToothScheme","forwardRef","dentitionProp","numberingProp","mode","projectionProp","defaultValue","onChange","onSelect","activeCondition","legendProp","controls","ariaLabel","className","ref","uiDentition","setUiDentition","useState","uiNumbering","setUiNumbering","uiProjection","setUiProjection","uiLegend","setUiLegend","optionsPopoverEl","setOptionsPopoverEl","legend","viewMode","showCompanion","controlsWasOn","useRef","useEffect","internalChart","setInternalChart","layout","useMemo","visibleIds","firstId","focusedId","setFocusedId","announcement","setAnnouncement","svgRef","commitChart","useCallback","next","focusToothById","svg","node","surfacePicking","drilled","setDrilled","focusZone","prevDrilled","isSurfaceMarked","state","announceTooth","c","toggleSurface","cond","prev","hadSurface","hadCond","surfaces","s","drillInto","handleSurfaceKeyDown","event","nz","rootRef","agentHandle","useImperativeHandle","useAgentRegistration","handleKeyDown","order","currentIndex","nextIndex","targetQuadrant","candidate","inQuadrant","other","nextConditions","handleClick","viewBoxWidth","viewBoxHeight","baseLabel","regionLabel","scrollTabIndex","RadixTooltip","Popover","IconButton","Settings","Switch","v","tooth","primaryCondition","anatomyLabel","conditionSuffix","tabIndex","symbolOverlays","entry","surfaceCondition","ringColour","tooltipContent","labelText","imageUrl","surfaceOverlays","imgScale","imgW","imgH","imgX","imgY","isDrilled","toothZones","url","marked","d"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { c as Y } from "./index-D2ZczOXr.js";
|
|
|
4
4
|
import { useTranslation as j } from "react-i18next";
|
|
5
5
|
import { F as v } from "./form-field-BOm9hK35.js";
|
|
6
6
|
import { N as B } from "./number-input-Dj5L3pXK.js";
|
|
7
|
-
import { S as C } from "./select-
|
|
7
|
+
import { S as C } from "./select-CEtRcon5.js";
|
|
8
8
|
import { I as L } from "./insert-result-C1SYdueh.js";
|
|
9
9
|
const _ = {
|
|
10
10
|
// weight → base kg
|
|
@@ -193,4 +193,4 @@ export {
|
|
|
193
193
|
P as b,
|
|
194
194
|
K as c
|
|
195
195
|
};
|
|
196
|
-
//# sourceMappingURL=unit-converter-
|
|
196
|
+
//# sourceMappingURL=unit-converter-BIbXHIQA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unit-converter-3sINXO3m.js","sources":["../../src/components/unit-converter/units.ts","../../src/components/unit-converter/unit-converter.tsx"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* Generic unit conversion — pure, framework-free, unit-testable. */\n/* */\n/* Every unit is described by an affine map to its category's base */\n/* unit: base = value * factor + offset. Conversion is base-in then */\n/* base-out, so the same code handles linear scales (weight, length, */\n/* glucose) and offset scales (temperature) uniformly. */\n/* ------------------------------------------------------------------ */\n\nexport type UnitCategory = 'weight' | 'length' | 'temperature' | 'glucose';\n\nexport interface UnitDef {\n id: string;\n category: UnitCategory;\n /** Multiplier to the category base unit. */\n factor: number;\n /** Additive offset to the base unit (non-zero only for temperature). */\n offset: number;\n}\n\n/* Base units: kg, cm, °C, mmol/L. */\nexport const UNITS: Record<string, UnitDef> = {\n // weight → base kg\n kg: { id: 'kg', category: 'weight', factor: 1, offset: 0 },\n g: { id: 'g', category: 'weight', factor: 0.001, offset: 0 },\n lb: { id: 'lb', category: 'weight', factor: 0.45359237, offset: 0 },\n oz: { id: 'oz', category: 'weight', factor: 0.028349523125, offset: 0 },\n // length → base cm\n cm: { id: 'cm', category: 'length', factor: 1, offset: 0 },\n m: { id: 'm', category: 'length', factor: 100, offset: 0 },\n in: { id: 'in', category: 'length', factor: 2.54, offset: 0 },\n ft: { id: 'ft', category: 'length', factor: 30.48, offset: 0 },\n // temperature → base °C (affine: °C = °F·5/9 − 32·5/9)\n c: { id: 'c', category: 'temperature', factor: 1, offset: 0 },\n f: { id: 'f', category: 'temperature', factor: 5 / 9, offset: (-32 * 5) / 9 },\n // glucose → base mmol/L (1 mg/dL = 1/18.0156 mmol/L)\n mmol_l: { id: 'mmol_l', category: 'glucose', factor: 1, offset: 0 },\n mg_dl: { id: 'mg_dl', category: 'glucose', factor: 1 / 18.0156, offset: 0 },\n};\n\nexport const UNITS_BY_CATEGORY: Record<UnitCategory, string[]> = {\n weight: ['kg', 'g', 'lb', 'oz'],\n length: ['cm', 'm', 'in', 'ft'],\n temperature: ['c', 'f'],\n glucose: ['mmol_l', 'mg_dl'],\n};\n\nexport const CATEGORIES: UnitCategory[] = [\n 'weight',\n 'length',\n 'temperature',\n 'glucose',\n];\n\n/**\n * Convert `value` from one unit to another. Both ids must belong to the same\n * category; mismatched categories throw (a programming error, not user input).\n */\nexport function convertUnits(\n value: number,\n fromId: string,\n toId: string,\n): number {\n const from = UNITS[fromId];\n const to = UNITS[toId];\n if (!from || !to) throw new Error(`Unknown unit: ${fromId} → ${toId}`);\n if (from.category !== to.category) {\n throw new Error(\n `Cannot convert across categories: ${from.category} → ${to.category}`,\n );\n }\n const base = value * from.factor + from.offset;\n return (base - to.offset) / to.factor;\n}\n","/* ------------------------------------------------------------------ */\n/* UnitConverter — generic clinical/everyday unit conversion across */\n/* weight, length, temperature and glucose. */\n/* */\n/* Maths lives in `./units` (pure, separately tested). */\n/* ------------------------------------------------------------------ */\n\nimport { forwardRef, useEffect, useMemo, useState } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { FormField } from '../form-field';\nimport { NumberInput } from '../number-input';\nimport { Select } from '../select';\nimport {\n InsertButton,\n type InsertPayload,\n type InsertVariant,\n type InsertMode,\n} from '../_shared/insert-result';\nimport {\n type UnitCategory,\n CATEGORIES,\n UNITS_BY_CATEGORY,\n convertUnits,\n} from './units';\n\nconst rootVariants = cva('ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]', {\n variants: {\n width: { full: 'ds:w-full', auto: 'ds:inline-flex' },\n },\n defaultVariants: { width: 'full' },\n});\n\n/**\n * DS token NAME backing the result-card category chip, one per measurement\n * family. The converter shows no on-screen badge, but the inserted card carries\n * a category chip so the reader can tell at a glance which family the figure\n * belongs to; each token is a closed-palette semantic alias resolved to a\n * concrete colour at raster time by `InsertButton`.\n */\nconst CATEGORY_HIGHLIGHT_TOKEN: Record<UnitCategory, string> = {\n weight: '--info',\n length: '--success',\n temperature: '--warning-readable',\n glucose: '--accent',\n};\n\n/**\n * Category chip token. The `temperature` family resolves to `--warning-readable`,\n * which the result card's colour probe resolves in the live themed DOM: orange-600\n * in light, non-accessible mode (where bare `--warning` yellow only reaches\n * ~3.2:1 on the white card) and `--warning` elsewhere, whose deepened ramp already\n * clears contrast. Resolving the token NAME at the card means the choice tracks\n * the live theme regardless of where the theme class lives. Other families are\n * unchanged.\n */\nfunction categoryHighlightToken(category: UnitCategory): string {\n return CATEGORY_HIGHLIGHT_TOKEN[category];\n}\n\nexport interface UnitConverterProps extends VariantProps<typeof rootVariants> {\n /** Initial category. Defaults to `'weight'`. */\n defaultCategory?: UnitCategory;\n /** Fires with the converted value (and `null` when input is empty). */\n onResultChange?: (value: number | null) => void;\n /** When provided, shows an \"Insert\" button that emits the result for an editor. */\n onInsert?: (payload: InsertPayload) => void;\n /**\n * Which verb the result button performs. Defaults to `'insert'`.\n * Use `'copy'` in an app-shell surface (no editor to insert into) — the\n * button writes the result to the clipboard as a multi-format `ClipboardItem`.\n */\n insertVariant?: InsertVariant;\n /** `copy` variant only — fired after a successful clipboard write. */\n onCopy?: (mode: InsertMode) => void;\n /** `copy` variant only — fired if the clipboard write can't proceed. */\n onError?: (error: unknown) => void;\n /**\n * Brand wordmark printed in the inserted/copied result-card footer.\n * Omitted → no brand line (and no footer hairline); a string → that custom\n * brand; `false` → no brand line. Brand is opt-in.\n */\n insertBrand?: string | false;\n /** Opaque instance id, emitted as `data-component-id`. */\n id?: string;\n /** Extra class names on the wrapper. */\n className?: string;\n}\n\nexport const UnitConverter = forwardRef<HTMLDivElement, UnitConverterProps>(\n (\n {\n defaultCategory = 'weight',\n onResultChange,\n onInsert,\n insertVariant = 'insert',\n onCopy,\n onError,\n insertBrand,\n id,\n width,\n className,\n },\n ref,\n ) => {\n const { t, i18n } = useTranslation();\n\n const [category, setCategory] = useState<UnitCategory>(defaultCategory);\n const [value, setValue] = useState<number | null>(null);\n const [fromId, setFromId] = useState<string>(\n UNITS_BY_CATEGORY[defaultCategory][0],\n );\n const [toId, setToId] = useState<string>(\n UNITS_BY_CATEGORY[defaultCategory][1],\n );\n\n const handleCategoryChange = (next: string): void => {\n const cat = next as UnitCategory;\n const units = UNITS_BY_CATEGORY[cat];\n setCategory(cat);\n setFromId(units[0]);\n setToId(units[1] ?? units[0]);\n };\n\n const converted = useMemo(\n () => (value === null ? null : convertUnits(value, fromId, toId)),\n [value, fromId, toId],\n );\n\n const numberFmt = useMemo(\n () => new Intl.NumberFormat(i18n.language, { maximumFractionDigits: 3 }),\n [i18n.language],\n );\n\n useEffect(() => {\n onResultChange?.(converted);\n }, [converted, onResultChange]);\n\n const categoryOptions = CATEGORIES.map((c) => ({\n value: c,\n label: t(`unitConverter.category.${c}`),\n }));\n const unitOptions = UNITS_BY_CATEGORY[category].map((u) => ({\n value: u,\n label: t(`unitConverter.units.${u}`),\n }));\n\n return (\n <div\n ref={ref}\n data-component=\"unit-converter\"\n data-component-id={id}\n className={rootVariants({ width, className })}\n >\n <FormField label={t('unitConverter.categoryLabel')}>\n <Select\n options={categoryOptions}\n value={category}\n onValueChange={handleCategoryChange}\n />\n </FormField>\n\n <div className=\"ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:sm:grid-cols-3\">\n <FormField label={t('unitConverter.value')}>\n <NumberInput mode=\"decimal\" value={value} onChange={setValue} />\n </FormField>\n <FormField label={t('unitConverter.from')}>\n <Select\n options={unitOptions}\n value={fromId}\n onValueChange={setFromId}\n />\n </FormField>\n <FormField label={t('unitConverter.to')}>\n <Select\n options={unitOptions}\n value={toId}\n onValueChange={setToId}\n />\n </FormField>\n </div>\n\n <p className=\"ds:sr-only\" role=\"status\" aria-live=\"polite\">\n {converted !== null\n ? `${numberFmt.format(value ?? 0)} ${t(\n `unitConverter.units.${fromId}`,\n )} = ${numberFmt.format(converted)} ${t(\n `unitConverter.units.${toId}`,\n )}`\n : ''}\n </p>\n\n {converted !== null ? (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]\">\n <span className=\"type-label ds:text-muted-foreground\">\n {t('unitConverter.result')}\n </span>\n <span className=\"type-metric ds:text-foreground\">\n {numberFmt.format(converted)} {t(`unitConverter.units.${toId}`)}\n </span>\n {insertVariant === 'copy' || onInsert ? (\n <InsertButton\n variant={insertVariant}\n onInsert={onInsert}\n onCopy={onCopy}\n onError={onError}\n card={{\n title: t('insert.title.unitConverter'),\n icon: 'ruler',\n highlight: t(`unitConverter.category.${category}`),\n // Chip tints by measurement family so the inserted card\n // signals the category at a glance — with the contrast-safe\n // orange override for the `--warning` temperature family.\n highlightToken: categoryHighlightToken(category),\n brand: insertBrand,\n fields: [\n {\n // Source measurement → ruler glyph (matches the header).\n icon: 'ruler',\n label: t('unitConverter.from'),\n value: `${numberFmt.format(value ?? 0)} ${t(\n `unitConverter.units.${fromId}`,\n )}`,\n },\n {\n // Converted result → trending-up (arrow) glyph signals the\n // conversion direction to the output value.\n icon: 'trending-up',\n label: t('unitConverter.to'),\n value: `${numberFmt.format(converted)} ${t(\n `unitConverter.units.${toId}`,\n )}`,\n },\n ],\n }}\n />\n ) : null}\n </div>\n ) : (\n <p className=\"type-body ds:text-muted-foreground\">\n {t('unitConverter.empty')}\n </p>\n )}\n </div>\n );\n },\n);\n\nUnitConverter.displayName = 'UnitConverter';\n"],"names":["UNITS","UNITS_BY_CATEGORY","CATEGORIES","convertUnits","value","fromId","toId","from","to","rootVariants","cva","CATEGORY_HIGHLIGHT_TOKEN","categoryHighlightToken","category","UnitConverter","forwardRef","defaultCategory","onResultChange","onInsert","insertVariant","onCopy","onError","insertBrand","id","width","className","ref","i18n","useTranslation","setCategory","useState","setValue","setFromId","setToId","handleCategoryChange","next","cat","units","converted","useMemo","numberFmt","useEffect","categoryOptions","c","unitOptions","u","jsxs","jsx","FormField","Select","NumberInput","InsertButton"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"unit-converter-BIbXHIQA.js","sources":["../../src/components/unit-converter/units.ts","../../src/components/unit-converter/unit-converter.tsx"],"sourcesContent":["/* ------------------------------------------------------------------ */\n/* Generic unit conversion — pure, framework-free, unit-testable. */\n/* */\n/* Every unit is described by an affine map to its category's base */\n/* unit: base = value * factor + offset. Conversion is base-in then */\n/* base-out, so the same code handles linear scales (weight, length, */\n/* glucose) and offset scales (temperature) uniformly. */\n/* ------------------------------------------------------------------ */\n\nexport type UnitCategory = 'weight' | 'length' | 'temperature' | 'glucose';\n\nexport interface UnitDef {\n id: string;\n category: UnitCategory;\n /** Multiplier to the category base unit. */\n factor: number;\n /** Additive offset to the base unit (non-zero only for temperature). */\n offset: number;\n}\n\n/* Base units: kg, cm, °C, mmol/L. */\nexport const UNITS: Record<string, UnitDef> = {\n // weight → base kg\n kg: { id: 'kg', category: 'weight', factor: 1, offset: 0 },\n g: { id: 'g', category: 'weight', factor: 0.001, offset: 0 },\n lb: { id: 'lb', category: 'weight', factor: 0.45359237, offset: 0 },\n oz: { id: 'oz', category: 'weight', factor: 0.028349523125, offset: 0 },\n // length → base cm\n cm: { id: 'cm', category: 'length', factor: 1, offset: 0 },\n m: { id: 'm', category: 'length', factor: 100, offset: 0 },\n in: { id: 'in', category: 'length', factor: 2.54, offset: 0 },\n ft: { id: 'ft', category: 'length', factor: 30.48, offset: 0 },\n // temperature → base °C (affine: °C = °F·5/9 − 32·5/9)\n c: { id: 'c', category: 'temperature', factor: 1, offset: 0 },\n f: { id: 'f', category: 'temperature', factor: 5 / 9, offset: (-32 * 5) / 9 },\n // glucose → base mmol/L (1 mg/dL = 1/18.0156 mmol/L)\n mmol_l: { id: 'mmol_l', category: 'glucose', factor: 1, offset: 0 },\n mg_dl: { id: 'mg_dl', category: 'glucose', factor: 1 / 18.0156, offset: 0 },\n};\n\nexport const UNITS_BY_CATEGORY: Record<UnitCategory, string[]> = {\n weight: ['kg', 'g', 'lb', 'oz'],\n length: ['cm', 'm', 'in', 'ft'],\n temperature: ['c', 'f'],\n glucose: ['mmol_l', 'mg_dl'],\n};\n\nexport const CATEGORIES: UnitCategory[] = [\n 'weight',\n 'length',\n 'temperature',\n 'glucose',\n];\n\n/**\n * Convert `value` from one unit to another. Both ids must belong to the same\n * category; mismatched categories throw (a programming error, not user input).\n */\nexport function convertUnits(\n value: number,\n fromId: string,\n toId: string,\n): number {\n const from = UNITS[fromId];\n const to = UNITS[toId];\n if (!from || !to) throw new Error(`Unknown unit: ${fromId} → ${toId}`);\n if (from.category !== to.category) {\n throw new Error(\n `Cannot convert across categories: ${from.category} → ${to.category}`,\n );\n }\n const base = value * from.factor + from.offset;\n return (base - to.offset) / to.factor;\n}\n","/* ------------------------------------------------------------------ */\n/* UnitConverter — generic clinical/everyday unit conversion across */\n/* weight, length, temperature and glucose. */\n/* */\n/* Maths lives in `./units` (pure, separately tested). */\n/* ------------------------------------------------------------------ */\n\nimport { forwardRef, useEffect, useMemo, useState } from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { FormField } from '../form-field';\nimport { NumberInput } from '../number-input';\nimport { Select } from '../select';\nimport {\n InsertButton,\n type InsertPayload,\n type InsertVariant,\n type InsertMode,\n} from '../_shared/insert-result';\nimport {\n type UnitCategory,\n CATEGORIES,\n UNITS_BY_CATEGORY,\n convertUnits,\n} from './units';\n\nconst rootVariants = cva('ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]', {\n variants: {\n width: { full: 'ds:w-full', auto: 'ds:inline-flex' },\n },\n defaultVariants: { width: 'full' },\n});\n\n/**\n * DS token NAME backing the result-card category chip, one per measurement\n * family. The converter shows no on-screen badge, but the inserted card carries\n * a category chip so the reader can tell at a glance which family the figure\n * belongs to; each token is a closed-palette semantic alias resolved to a\n * concrete colour at raster time by `InsertButton`.\n */\nconst CATEGORY_HIGHLIGHT_TOKEN: Record<UnitCategory, string> = {\n weight: '--info',\n length: '--success',\n temperature: '--warning-readable',\n glucose: '--accent',\n};\n\n/**\n * Category chip token. The `temperature` family resolves to `--warning-readable`,\n * which the result card's colour probe resolves in the live themed DOM: orange-600\n * in light, non-accessible mode (where bare `--warning` yellow only reaches\n * ~3.2:1 on the white card) and `--warning` elsewhere, whose deepened ramp already\n * clears contrast. Resolving the token NAME at the card means the choice tracks\n * the live theme regardless of where the theme class lives. Other families are\n * unchanged.\n */\nfunction categoryHighlightToken(category: UnitCategory): string {\n return CATEGORY_HIGHLIGHT_TOKEN[category];\n}\n\nexport interface UnitConverterProps extends VariantProps<typeof rootVariants> {\n /** Initial category. Defaults to `'weight'`. */\n defaultCategory?: UnitCategory;\n /** Fires with the converted value (and `null` when input is empty). */\n onResultChange?: (value: number | null) => void;\n /** When provided, shows an \"Insert\" button that emits the result for an editor. */\n onInsert?: (payload: InsertPayload) => void;\n /**\n * Which verb the result button performs. Defaults to `'insert'`.\n * Use `'copy'` in an app-shell surface (no editor to insert into) — the\n * button writes the result to the clipboard as a multi-format `ClipboardItem`.\n */\n insertVariant?: InsertVariant;\n /** `copy` variant only — fired after a successful clipboard write. */\n onCopy?: (mode: InsertMode) => void;\n /** `copy` variant only — fired if the clipboard write can't proceed. */\n onError?: (error: unknown) => void;\n /**\n * Brand wordmark printed in the inserted/copied result-card footer.\n * Omitted → no brand line (and no footer hairline); a string → that custom\n * brand; `false` → no brand line. Brand is opt-in.\n */\n insertBrand?: string | false;\n /** Opaque instance id, emitted as `data-component-id`. */\n id?: string;\n /** Extra class names on the wrapper. */\n className?: string;\n}\n\nexport const UnitConverter = forwardRef<HTMLDivElement, UnitConverterProps>(\n (\n {\n defaultCategory = 'weight',\n onResultChange,\n onInsert,\n insertVariant = 'insert',\n onCopy,\n onError,\n insertBrand,\n id,\n width,\n className,\n },\n ref,\n ) => {\n const { t, i18n } = useTranslation();\n\n const [category, setCategory] = useState<UnitCategory>(defaultCategory);\n const [value, setValue] = useState<number | null>(null);\n const [fromId, setFromId] = useState<string>(\n UNITS_BY_CATEGORY[defaultCategory][0],\n );\n const [toId, setToId] = useState<string>(\n UNITS_BY_CATEGORY[defaultCategory][1],\n );\n\n const handleCategoryChange = (next: string): void => {\n const cat = next as UnitCategory;\n const units = UNITS_BY_CATEGORY[cat];\n setCategory(cat);\n setFromId(units[0]);\n setToId(units[1] ?? units[0]);\n };\n\n const converted = useMemo(\n () => (value === null ? null : convertUnits(value, fromId, toId)),\n [value, fromId, toId],\n );\n\n const numberFmt = useMemo(\n () => new Intl.NumberFormat(i18n.language, { maximumFractionDigits: 3 }),\n [i18n.language],\n );\n\n useEffect(() => {\n onResultChange?.(converted);\n }, [converted, onResultChange]);\n\n const categoryOptions = CATEGORIES.map((c) => ({\n value: c,\n label: t(`unitConverter.category.${c}`),\n }));\n const unitOptions = UNITS_BY_CATEGORY[category].map((u) => ({\n value: u,\n label: t(`unitConverter.units.${u}`),\n }));\n\n return (\n <div\n ref={ref}\n data-component=\"unit-converter\"\n data-component-id={id}\n className={rootVariants({ width, className })}\n >\n <FormField label={t('unitConverter.categoryLabel')}>\n <Select\n options={categoryOptions}\n value={category}\n onValueChange={handleCategoryChange}\n />\n </FormField>\n\n <div className=\"ds:grid ds:grid-cols-1 ds:gap-[var(--spacing-md)] ds:sm:grid-cols-3\">\n <FormField label={t('unitConverter.value')}>\n <NumberInput mode=\"decimal\" value={value} onChange={setValue} />\n </FormField>\n <FormField label={t('unitConverter.from')}>\n <Select\n options={unitOptions}\n value={fromId}\n onValueChange={setFromId}\n />\n </FormField>\n <FormField label={t('unitConverter.to')}>\n <Select\n options={unitOptions}\n value={toId}\n onValueChange={setToId}\n />\n </FormField>\n </div>\n\n <p className=\"ds:sr-only\" role=\"status\" aria-live=\"polite\">\n {converted !== null\n ? `${numberFmt.format(value ?? 0)} ${t(\n `unitConverter.units.${fromId}`,\n )} = ${numberFmt.format(converted)} ${t(\n `unitConverter.units.${toId}`,\n )}`\n : ''}\n </p>\n\n {converted !== null ? (\n <div className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]\">\n <span className=\"type-label ds:text-muted-foreground\">\n {t('unitConverter.result')}\n </span>\n <span className=\"type-metric ds:text-foreground\">\n {numberFmt.format(converted)} {t(`unitConverter.units.${toId}`)}\n </span>\n {insertVariant === 'copy' || onInsert ? (\n <InsertButton\n variant={insertVariant}\n onInsert={onInsert}\n onCopy={onCopy}\n onError={onError}\n card={{\n title: t('insert.title.unitConverter'),\n icon: 'ruler',\n highlight: t(`unitConverter.category.${category}`),\n // Chip tints by measurement family so the inserted card\n // signals the category at a glance — with the contrast-safe\n // orange override for the `--warning` temperature family.\n highlightToken: categoryHighlightToken(category),\n brand: insertBrand,\n fields: [\n {\n // Source measurement → ruler glyph (matches the header).\n icon: 'ruler',\n label: t('unitConverter.from'),\n value: `${numberFmt.format(value ?? 0)} ${t(\n `unitConverter.units.${fromId}`,\n )}`,\n },\n {\n // Converted result → trending-up (arrow) glyph signals the\n // conversion direction to the output value.\n icon: 'trending-up',\n label: t('unitConverter.to'),\n value: `${numberFmt.format(converted)} ${t(\n `unitConverter.units.${toId}`,\n )}`,\n },\n ],\n }}\n />\n ) : null}\n </div>\n ) : (\n <p className=\"type-body ds:text-muted-foreground\">\n {t('unitConverter.empty')}\n </p>\n )}\n </div>\n );\n },\n);\n\nUnitConverter.displayName = 'UnitConverter';\n"],"names":["UNITS","UNITS_BY_CATEGORY","CATEGORIES","convertUnits","value","fromId","toId","from","to","rootVariants","cva","CATEGORY_HIGHLIGHT_TOKEN","categoryHighlightToken","category","UnitConverter","forwardRef","defaultCategory","onResultChange","onInsert","insertVariant","onCopy","onError","insertBrand","id","width","className","ref","i18n","useTranslation","setCategory","useState","setValue","setFromId","setToId","handleCategoryChange","next","cat","units","converted","useMemo","numberFmt","useEffect","categoryOptions","c","unitOptions","u","jsxs","jsx","FormField","Select","NumberInput","InsertButton"],"mappings":"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"}
|
package/dist/agent-catalog.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"packageVersion": "0.
|
|
3
|
+
"packageVersion": "0.64.0",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"kind": "component",
|
|
@@ -4836,13 +4836,20 @@
|
|
|
4836
4836
|
"kind": "component",
|
|
4837
4837
|
"id": "tooth-scheme",
|
|
4838
4838
|
"capabilities": [
|
|
4839
|
-
"pick"
|
|
4839
|
+
"pick",
|
|
4840
|
+
"edit_inline",
|
|
4841
|
+
"view_change"
|
|
4840
4842
|
],
|
|
4841
4843
|
"state": [
|
|
4842
4844
|
{
|
|
4843
4845
|
"name": "chart",
|
|
4844
4846
|
"type": "object",
|
|
4845
|
-
"description": "Current dental chart
|
|
4847
|
+
"description": "Current dental chart — findings (and marked surfaces) keyed by FDI id."
|
|
4848
|
+
},
|
|
4849
|
+
{
|
|
4850
|
+
"name": "chartedTeeth",
|
|
4851
|
+
"type": "string[]",
|
|
4852
|
+
"description": "FDI ids of every tooth carrying at least one finding."
|
|
4846
4853
|
}
|
|
4847
4854
|
],
|
|
4848
4855
|
"actions": [
|
|
@@ -4851,6 +4858,36 @@
|
|
|
4851
4858
|
"safety": "read",
|
|
4852
4859
|
"argsType": "{ id: FdiId }",
|
|
4853
4860
|
"description": "Move focus to a specific tooth by FDI id."
|
|
4861
|
+
},
|
|
4862
|
+
{
|
|
4863
|
+
"name": "get_tooth",
|
|
4864
|
+
"safety": "read",
|
|
4865
|
+
"argsType": "{ id: FdiId }",
|
|
4866
|
+
"description": "Read the findings recorded on one tooth (FDI id)."
|
|
4867
|
+
},
|
|
4868
|
+
{
|
|
4869
|
+
"name": "teeth_with",
|
|
4870
|
+
"safety": "read",
|
|
4871
|
+
"argsType": "{ condition: ToothCondition }",
|
|
4872
|
+
"description": "List the FDI ids of every tooth carrying a given finding (e.g. all teeth with caries)."
|
|
4873
|
+
},
|
|
4874
|
+
{
|
|
4875
|
+
"name": "set_finding",
|
|
4876
|
+
"safety": "write",
|
|
4877
|
+
"argsType": "{ id: FdiId; condition: ToothCondition; surfaces?: Surface[] }",
|
|
4878
|
+
"description": "Record a finding on a tooth (crown, implant, root canal, caries, …). Optionally mark the affected surfaces for a surface finding."
|
|
4879
|
+
},
|
|
4880
|
+
{
|
|
4881
|
+
"name": "remove_finding",
|
|
4882
|
+
"safety": "write",
|
|
4883
|
+
"argsType": "{ id: FdiId; condition: ToothCondition }",
|
|
4884
|
+
"description": "Remove a single finding from a tooth."
|
|
4885
|
+
},
|
|
4886
|
+
{
|
|
4887
|
+
"name": "clear_tooth",
|
|
4888
|
+
"safety": "destructive",
|
|
4889
|
+
"argsType": "{ id: FdiId }",
|
|
4890
|
+
"description": "Clear every finding from a tooth."
|
|
4854
4891
|
}
|
|
4855
4892
|
],
|
|
4856
4893
|
"domHooks": {
|
|
@@ -4865,7 +4902,7 @@
|
|
|
4865
4902
|
},
|
|
4866
4903
|
"item": {
|
|
4867
4904
|
"attr": "data-fdi",
|
|
4868
|
-
"description": "Each rendered tooth `<g>` carries its FDI id as `data-fdi`.
|
|
4905
|
+
"description": "Each rendered tooth `<g>` carries its FDI id as `data-fdi`. The `data-projection` attribute on the root exposes the side/plan view."
|
|
4869
4906
|
}
|
|
4870
4907
|
}
|
|
4871
4908
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as l, B as r, C as t, a as s, D as n, b as d, c as i, d as o, E as C, e as R, I as F, L as g, N as T, f as b, S as u, g as c, h as D, T as m, i as p, j as x, k as f, l as y, m as E, n as S, o as h, U as A, p as L, q as N, u as k } from "../../_chunks/editable-currency-cell-renderer-
|
|
1
|
+
import { A as l, B as r, C as t, a as s, D as n, b as d, c as i, d as o, E as C, e as R, I as F, L as g, N as T, f as b, S as u, g as c, h as D, T as m, i as p, j as x, k as f, l as y, m as E, n as S, o as h, U as A, p as L, q as N, u as k } from "../../_chunks/editable-currency-cell-renderer-BQgaKFCz.js";
|
|
2
2
|
export {
|
|
3
3
|
l as ActionsCellRenderer,
|
|
4
4
|
r as BalanceCellRenderer,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as a, a as l, b as S, c, d as s, e as o, f as r, g as p, h as g, i, j as m } from "../../_chunks/select-
|
|
1
|
+
import { S as a, a as l, b as S, c, d as s, e as o, f as r, g as p, h as g, i, j as m } from "../../_chunks/select-CEtRcon5.js";
|
|
2
2
|
export {
|
|
3
3
|
a as Select,
|
|
4
4
|
l as SelectContent,
|
|
@@ -62,6 +62,14 @@ export interface SelectProps<T extends string = string> {
|
|
|
62
62
|
tone?: 'default' | 'error';
|
|
63
63
|
'aria-label'?: string;
|
|
64
64
|
className?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Portal target for the listbox. Defaults to `document.body`. Set this to a
|
|
67
|
+
* parent overlay's content element (e.g. a Popover) when the Select is nested
|
|
68
|
+
* inside one, so the listbox shares that stacking context and renders above
|
|
69
|
+
* it instead of behind it (the global z-scale puts `--z-dropdown` below
|
|
70
|
+
* `--z-popover`).
|
|
71
|
+
*/
|
|
72
|
+
container?: HTMLElement | null;
|
|
65
73
|
}
|
|
66
74
|
interface SelectComponent {
|
|
67
75
|
<T extends string = string>(props: SelectProps<T> & {
|