teksilo_core/focus.rs
1// SPDX-License-Identifier: MPL-2.0
2// SPDX-FileCopyrightText: 2026 FernTech
3
4/// How focus was acquired — used for `:focus-visible` behavior.
5/// Only show focus ring when focus was gained via keyboard, not pointer click.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum FocusOrigin {
8 /// Focus gained via Tab/Shift-Tab keyboard navigation.
9 Keyboard,
10 /// Focus gained via pointer click.
11 Pointer,
12 /// Focus set programmatically by the application.
13 Programmatic,
14}
15
16/// Policy for a focus **traversal scope**, declared via the `FocusScope`
17/// wrapper widget. Controls what Tab / Shift+Tab does when it reaches the
18/// scope's ends.
19///
20/// A scope groups + scopes the `tab_index` numbering of its descendants:
21/// two sibling scopes that both number their children `1, 2, 3` never
22/// interleave — each scope is an independent, ordered unit within its
23/// parent. This is Teksilo's analogue of Flutter `FocusTraversalGroup` /
24/// WPF `KeyboardNavigation.TabNavigation`.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum TraversalScopePolicy {
27 /// Tab flows *out* of the scope at its ends into the enclosing scope's
28 /// next member. The scope groups `tab_index` numbering without trapping
29 /// focus — use for logical regions in a continuous Tab order (e.g. dock
30 /// panels, where each panel numbers its own controls without colliding
31 /// with sibling panels).
32 Continue,
33 /// Tab *wraps* within the scope and never exits via keyboard navigation.
34 /// Use for modal dialogs — the one surface whose pattern (ARIA's Dialog
35 /// (Modal)) actually calls for containing focus.
36 ///
37 /// **Not for popovers or menus.** Those implement Disclosure and Menu,
38 /// which mandate the opposite: Tab is an exit gesture there, and the
39 /// framework already answers it by dismissing the overlay focus leaves
40 /// rather than by trapping focus inside it. Wrapping such an overlay in a
41 /// `Cycle` scope defeats that — focus can no longer leave, so the
42 /// dismissal never fires and the panel becomes keyboard-inescapable except
43 /// via Escape.
44 Cycle,
45}