/** * Who holds focus, and who gets it back. * * Moving focus is easy and losing it is silent. A widget opens an overlay, focus goes into it, the * overlay closes — and if nothing takes focus at that moment the user is standing on ``, at * the top of the document, with no way back to the field they were in. Nothing throws, nothing * logs, and every attribute is still correct. * * This module makes the handover a contract with two halves: * * **Focus is borrowed, not taken.** {@link MdyFocusCustodian.remember} records who holds it before * the widget moves it, so there is always somewhere to hand it back to. A widget that never asks is * a widget that cannot return it. * * **A move that is not taken did not happen.** `focus()` on a detached, hidden or disabled element * does nothing at all and reports nothing — so every candidate is *verified* afterwards against * `activeElement`, and a candidate that did not take it falls through to the next. This is the half * that matters: the reason focus ends up on `` is almost never that nobody asked, it is that * the element asked was gone by the time the question reached it. * * The chain is: the part the caller prefers, then whoever held focus before, then the widget itself. * Only when a widget has left the document entirely does focus legitimately go nowhere. */ export interface MdyFocusCustodian { /** * Record the current focus owner, to be handed back by a later {@link restore}. * * Call it *before* moving focus — on open, on expand, before anything is mounted over the top. * Calling it twice without an intervening restore keeps the first answer: the owner worth * returning to is the one from outside the interaction, not an element the widget focused itself * one step ago. */ remember(): void; /** * Hand focus to `preferred`, or to the best thing that will take it. * * Only while the custodian is holding focus it borrowed. A widget that closes and is then disposed * restores twice, and the second call must not pull focus back out of wherever the first one put * it — focus is borrowed, not taken. A `preferred` element is always honoured: naming one is the * caller placing focus deliberately rather than asking for what was borrowed. * * Returns the element that ended up with focus, or `null` when nothing did — because the widget * and its remembered owner have both left the document, or because there was nothing to give back. */ restore(preferred?: Element | null): HTMLElement | null; /** * End the borrow. For teardown, so a destroyed widget holds no reference and owes no restore. * * A {@link restore} after this places no focus and returns `null` unless it names a `preferred` * element — the widget has said it is done, and taking focus afterwards is what this module exists * to prevent. */ release(): void; } /** * Keeps the keyboard somewhere when the control it was standing on leaves play. * * Disabling a focused element blurs it — that is the platform. What follows is this library's: the * person who was typing is on ``, their next Tab starts at the top of the document, and * nothing says where they went. A document's rule can do this without anyone clicking: a value * arrives from a fetch, a condition turns false, and the field under the cursor goes out of play * mid-word. * * Read-only is the proof that it need not cost them their place — a read-only field keeps the * keyboard — so this puts a disabled one somewhere too: the next thing that can take focus after it, * the previous one otherwise, and the widget's own root as the last resort so the next Tab starts * from where they were rather than from the top of the page. * * Call it *before* taking the control out of play: afterwards the element is already blurred and * there is nothing left to say where the keyboard was. */ export declare function keepKeyboardInPlay(leaving: Element, scope?: Element | null, options?: { /** * Whether the caller is asking *after* the platform has already blurred the control. * * A renderer that takes a control out of play itself calls before, with the keyboard still on * it. A renderer that hears about it afterwards — lit binds `?disabled` while rendering — has * only the fact that focus is now nowhere, and says so here. Without the distinction, "nowhere" * matches a field nobody was ever standing in, and putting focus on such a widget's root moves * the keyboard to a control the person never visited. */ readonly afterBlur?: boolean; }): void; /** * A focus custodian for one widget. * * `root` is a function because a widget's root can be replaced — a re-render, a remount — and a * custodian holding a stale element would restore focus into a detached tree, which looks exactly * like restoring it nowhere. */ export declare function createFocusCustodian(root: () => Element | null): MdyFocusCustodian; /** * Puts the keyboard on an element that may not be showing yet, and checks that it took. * * A popup rendered into the top layer — a `popover`, a portalled panel — exists in the document a * frame before it is shown, and `focus()` on an element that is not being rendered is a no-op that * reports nothing. A renderer that focuses on the render it triggered therefore leaves the keyboard * where it was, and the arrows it just enabled have nothing to move. * * So the attempt is verified rather than assumed, and tried again on the next frame while `still` * holds — bounded, because a panel that never draws is a different defect and a retry that never * stops would hide it. * * `schedule` is the caller's frame: a renderer that already has one — a scheduler, an update * promise — passes it rather than growing a second clock. */ export declare function focusWhenShown(target: () => Element | null | undefined, options?: { readonly attempts?: number; readonly still?: () => boolean; readonly schedule?: (run: () => void) => void; }): void;