Full-mouth perio-chart. The 32-tooth x 6-site grid (~450+ interactive
cells) is built with plain DOM (buildArch), NOT JSX/React state, and
updated via targeted syncToothCells calls rather than a full React
re-render — see syncOneTooth/fullResync below. Only the compact summary
bar is React-controlled (useState), since re-rendering ~4 numbers on every
edit is cheap. suppressResyncRef prevents the grid's own edits from ALSO
triggering a redundant full resync via the onStateChange subscription
(setPerioSite/setToothMobility both fire it synchronously) — external edits
(dual-state chart-mode switch, or another consumer editing perio data while
the overlay is open) still trigger the full resync normally. All writes go
through the data core (setPerioSite/getToothPerio/getToothCal/
getPerioSummary/getPerioChart); keyboard auto-advance between cells is
handled by handleGridKeyDown.
Layers OVER the odontogram, which it never unmounts: position: fixed,
full-screen, high z-index (.perio-overlay in index.css). Mirrors
SettingsModal's dialog contract — role="dialog" + aria-modal, Esc
closes, backdrop click closes, focus trap + focus-restore on close — on a
single element (#perioOverlay itself is the dialog; there is no separate
backdrop element, unlike SettingsModal).
The optional inline prop selects a second chrome for the SAME body (grid +
summary bar) — a plain panel (#perioInlinePanel) meant to fill the chart
area in place of the hidden-but-mounted odontogram, instead of the
fixed-position modal dialog. open/onClose are the MODAL chrome's
contract and are ignored when inline is true (the caller controls
mount/unmount of an inline instance directly via conditional rendering) —
there is nothing to "close" in an embedded panel. Dialog-only concerns
(focus trap/restore, Esc-to-close, backdrop click, role="dialog") do not
apply to the inline chrome at all.
Full-mouth perio-chart. The 32-tooth x 6-site grid (~450+ interactive cells) is built with plain DOM (
buildArch), NOT JSX/React state, and updated via targetedsyncToothCellscalls rather than a full React re-render — seesyncOneTooth/fullResyncbelow. Only the compact summary bar is React-controlled (useState), since re-rendering ~4 numbers on every edit is cheap.suppressResyncRefprevents the grid's own edits from ALSO triggering a redundant full resync via theonStateChangesubscription (setPerioSite/setToothMobility both fire it synchronously) — external edits (dual-state chart-mode switch, or another consumer editing perio data while the overlay is open) still trigger the full resync normally. All writes go through the data core (setPerioSite/getToothPerio/getToothCal/getPerioSummary/getPerioChart); keyboard auto-advance between cells is handled byhandleGridKeyDown.Layers OVER the odontogram, which it never unmounts:
position: fixed, full-screen, high z-index (.perio-overlayinindex.css). MirrorsSettingsModal's dialog contract —role="dialog"+aria-modal, Esc closes, backdrop click closes, focus trap + focus-restore on close — on a single element (#perioOverlayitself is the dialog; there is no separate backdrop element, unlikeSettingsModal).The optional
inlineprop selects a second chrome for the SAME body (grid + summary bar) — a plain panel (#perioInlinePanel) meant to fill the chart area in place of the hidden-but-mounted odontogram, instead of the fixed-position modal dialog.open/onCloseare the MODAL chrome's contract and are ignored wheninlineis true (the caller controls mount/unmount of an inline instance directly via conditional rendering) — there is nothing to "close" in an embedded panel. Dialog-only concerns (focus trap/restore, Esc-to-close, backdrop click,role="dialog") do not apply to the inline chrome at all.