focus.rs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. use crate::Dom;
  2. use dioxus_core::ElementId;
  3. use dioxus_native_core::utils::{ElementProduced, PersistantElementIter};
  4. use std::num::NonZeroU16;
  5. use dioxus_native_core::{
  6. node_ref::{AttributeMask, NodeMask, NodeView},
  7. real_dom::NodeType,
  8. state::NodeDepState,
  9. };
  10. #[derive(Clone, Copy, PartialEq, Eq, Debug, Ord)]
  11. pub(crate) enum FocusLevel {
  12. Unfocusable,
  13. Focusable,
  14. Ordered(std::num::NonZeroU16),
  15. }
  16. impl FocusLevel {
  17. pub fn focusable(&self) -> bool {
  18. match self {
  19. FocusLevel::Unfocusable => false,
  20. FocusLevel::Focusable => true,
  21. FocusLevel::Ordered(_) => true,
  22. }
  23. }
  24. }
  25. impl PartialOrd for FocusLevel {
  26. fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
  27. match (self, other) {
  28. (FocusLevel::Unfocusable, FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Equal),
  29. (FocusLevel::Unfocusable, FocusLevel::Focusable) => Some(std::cmp::Ordering::Less),
  30. (FocusLevel::Unfocusable, FocusLevel::Ordered(_)) => Some(std::cmp::Ordering::Less),
  31. (FocusLevel::Focusable, FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Greater),
  32. (FocusLevel::Focusable, FocusLevel::Focusable) => Some(std::cmp::Ordering::Equal),
  33. (FocusLevel::Focusable, FocusLevel::Ordered(_)) => Some(std::cmp::Ordering::Greater),
  34. (FocusLevel::Ordered(_), FocusLevel::Unfocusable) => Some(std::cmp::Ordering::Greater),
  35. (FocusLevel::Ordered(_), FocusLevel::Focusable) => Some(std::cmp::Ordering::Less),
  36. (FocusLevel::Ordered(a), FocusLevel::Ordered(b)) => a.partial_cmp(b),
  37. }
  38. }
  39. }
  40. impl Default for FocusLevel {
  41. fn default() -> Self {
  42. FocusLevel::Unfocusable
  43. }
  44. }
  45. #[derive(Clone, PartialEq, Debug, Default)]
  46. pub(crate) struct Focus {
  47. pub pass_focus: bool,
  48. pub level: FocusLevel,
  49. }
  50. impl NodeDepState for Focus {
  51. type Ctx = ();
  52. type DepState = ();
  53. const NODE_MASK: NodeMask =
  54. NodeMask::new_with_attrs(AttributeMask::Static(FOCUS_ATTRIBUTES)).with_listeners();
  55. fn reduce(&mut self, node: NodeView<'_>, _sibling: &Self::DepState, _: &Self::Ctx) -> bool {
  56. let new = Focus {
  57. pass_focus: !node
  58. .attributes()
  59. .any(|a| a.name == "dioxus-prevent-default" && a.value.trim() == "true"),
  60. level: if let Some(a) = node.attributes().find(|a| a.name == "tabindex") {
  61. if let Ok(index) = a.value.parse::<i32>() {
  62. if index < 0 {
  63. FocusLevel::Unfocusable
  64. } else if index == 0 {
  65. FocusLevel::Focusable
  66. } else {
  67. FocusLevel::Ordered(NonZeroU16::new(index as u16).unwrap())
  68. }
  69. } else {
  70. FocusLevel::Unfocusable
  71. }
  72. } else {
  73. if node
  74. .listeners()
  75. .iter()
  76. .any(|l| FOCUS_EVENTS.binary_search(&l.event).is_ok())
  77. {
  78. FocusLevel::Focusable
  79. } else {
  80. FocusLevel::Unfocusable
  81. }
  82. },
  83. };
  84. if *self != new {
  85. *self = new;
  86. true
  87. } else {
  88. false
  89. }
  90. }
  91. }
  92. // must be sorted
  93. const FOCUS_EVENTS: &[&str] = &["keydown", "keypress", "keyup"];
  94. const FOCUS_ATTRIBUTES: &[&str] = &["dioxus-prevent-default", "tabindex"];
  95. #[derive(Default)]
  96. pub(crate) struct FocusState {
  97. pub(crate) focus_iter: PersistantElementIter,
  98. pub(crate) last_focused_id: Option<ElementId>,
  99. pub(crate) focus_level: FocusLevel,
  100. }
  101. impl FocusState {
  102. // returns true if the focus has changed
  103. pub fn progress(&mut self, rdom: &mut Dom, forward: bool) -> bool {
  104. if let Some(last) = self.last_focused_id {
  105. if !rdom[last].state.focus.pass_focus {
  106. return false;
  107. }
  108. }
  109. let mut loop_marker_id = self.last_focused_id;
  110. let focus_level = &mut self.focus_level;
  111. let mut next_focus = None;
  112. let starting_focus_level = *focus_level;
  113. loop {
  114. let new = if forward {
  115. self.focus_iter.next(&rdom)
  116. } else {
  117. self.focus_iter.prev(&rdom)
  118. };
  119. let new_id = new.id();
  120. let current_level = rdom[new_id].state.focus.level;
  121. if let ElementProduced::Looped(_) = new {
  122. let mut closest_level = None;
  123. if forward {
  124. // find the closest focusable element after the current level
  125. rdom.traverse_depth_first(|n| {
  126. let current_level = n.state.focus.level;
  127. if current_level != *focus_level {
  128. if current_level > *focus_level {
  129. if let Some(level) = &mut closest_level {
  130. if current_level < *level {
  131. *level = current_level;
  132. }
  133. } else {
  134. closest_level = Some(current_level);
  135. }
  136. }
  137. }
  138. });
  139. } else {
  140. // find the closest focusable element before the current level
  141. rdom.traverse_depth_first(|n| {
  142. let current_level = n.state.focus.level;
  143. if current_level != *focus_level {
  144. if current_level < *focus_level {
  145. if let Some(level) = &mut closest_level {
  146. if current_level > *level {
  147. *level = current_level;
  148. }
  149. } else {
  150. closest_level = Some(current_level);
  151. }
  152. }
  153. }
  154. });
  155. }
  156. // extend the loop_marker_id to allow for another pass
  157. loop_marker_id = None;
  158. if let Some(level) = closest_level {
  159. *focus_level = level;
  160. } else {
  161. if forward {
  162. *focus_level = FocusLevel::Unfocusable;
  163. } else {
  164. *focus_level = FocusLevel::Focusable;
  165. }
  166. }
  167. // if the focus level looped, we are done
  168. if *focus_level == starting_focus_level {
  169. break;
  170. }
  171. }
  172. // once we have looked at all the elements exit the loop
  173. if let Some(last) = loop_marker_id {
  174. if new_id == last {
  175. break;
  176. }
  177. } else {
  178. loop_marker_id = Some(new_id);
  179. }
  180. let after_previous_focused = if forward {
  181. current_level >= *focus_level
  182. } else {
  183. current_level <= *focus_level
  184. };
  185. if after_previous_focused && current_level.focusable() {
  186. if current_level == *focus_level {
  187. next_focus = Some((new_id, current_level));
  188. break;
  189. }
  190. }
  191. }
  192. if let Some((id, order)) = next_focus {
  193. if order.focusable() {
  194. rdom[id].state.focused = true;
  195. if let Some(old) = self.last_focused_id.replace(id) {
  196. rdom[old].state.focused = false;
  197. }
  198. // reset the position to the currently focused element
  199. while if forward {
  200. self.focus_iter.next(&rdom).id()
  201. } else {
  202. self.focus_iter.prev(&rdom).id()
  203. } != id
  204. {}
  205. return true;
  206. }
  207. }
  208. false
  209. }
  210. pub(crate) fn prune(&mut self, mutations: &dioxus_core::Mutations, rdom: &Dom) {
  211. fn remove_children(
  212. to_prune: &mut [&mut Option<ElementId>],
  213. rdom: &Dom,
  214. removed: ElementId,
  215. ) {
  216. for opt in to_prune.iter_mut() {
  217. if let Some(id) = opt {
  218. if *id == removed {
  219. **opt = None;
  220. }
  221. }
  222. }
  223. if let NodeType::Element { children, .. } = &rdom[removed].node_type {
  224. for child in children {
  225. remove_children(to_prune, rdom, *child);
  226. }
  227. }
  228. }
  229. for m in &mutations.edits {
  230. match m {
  231. dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
  232. &mut [&mut self.last_focused_id],
  233. rdom,
  234. ElementId(*root as usize),
  235. ),
  236. dioxus_core::DomEdit::Remove { root } => remove_children(
  237. &mut [&mut self.last_focused_id],
  238. rdom,
  239. ElementId(*root as usize),
  240. ),
  241. _ => (),
  242. }
  243. }
  244. }
  245. }