focus.rs 9.7 KB

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