Keep Tab inside a modal surface, and put focus back where it came from on close.
A deliberate widening of the published surface (MICA-172), and a one-way door.
Three of the SDK's own primitives already use it — ConfirmDialog, ReportDialog and
PhotoPickerModal — and an add-on that needs a modal the kit does not ship had no way to
reach it. The alternative was for every add-on author to write their own trap, and a
hand-rolled focus trap is the kind of thing that is quietly got wrong: the surface still
looks right, Tab still moves, and the only person who finds out is a keyboard or
screen-reader user who was told "dialog" and handed the screen behind it. That is an
accessibility bug with no route back to its cause, which is exactly the class of failure
this repo would rather make impossible than document.
Exported here rather than from a barrel line of its own because it is the same kind of
thing as everything above: DOM behaviour with no micaOS state behind it, implemented in
sdk/lib/ and therefore bundle-safe for a sandboxed add-on. Going through utils.ts is
also what gives it index/addon parity for free — both barrels export * from this file,
so it cannot end up resolvable to the typechecker and missing at vite build, which is
the divergence publicSurface.test.ts's parity arm exists to catch.
FocusTrapOptions travels with it. An add-on wrapping this in a component of its own has
to name the options object to pass enabled or returnFocusTo through, and an action
whose parameter type is unnameable is an action you can only use in its default shape.
Keep Tab inside a modal surface, and put focus back where it came from on close.
A deliberate widening of the published surface (MICA-172), and a one-way door. Three of the SDK's own primitives already use it —
ConfirmDialog,ReportDialogandPhotoPickerModal— and an add-on that needs a modal the kit does not ship had no way to reach it. The alternative was for every add-on author to write their own trap, and a hand-rolled focus trap is the kind of thing that is quietly got wrong: the surface still looks right, Tab still moves, and the only person who finds out is a keyboard or screen-reader user who was told "dialog" and handed the screen behind it. That is an accessibility bug with no route back to its cause, which is exactly the class of failure this repo would rather make impossible than document.Exported here rather than from a barrel line of its own because it is the same kind of thing as everything above: DOM behaviour with no micaOS state behind it, implemented in
sdk/lib/and therefore bundle-safe for a sandboxed add-on. Going throughutils.tsis also what gives it index/addon parity for free — both barrelsexport *from this file, so it cannot end up resolvable to the typechecker and missing atvite build, which is the divergencepublicSurface.test.ts's parity arm exists to catch.FocusTrapOptionstravels with it. An add-on wrapping this in a component of its own has to name the options object to passenabledorreturnFocusTothrough, and an action whose parameter type is unnameable is an action you can only use in its default shape.