1 use webcore::value::Reference; 2 use webcore::try_from::TryInto; 3 use webapi::node::{INode, Node}; 4 use webapi::dom_exception::{IndexSizeError, NotFoundError, InvalidStateError}; 5 6 /// Possible values are: 7 /// 8 /// * `None`: No selection has currently been made. 9 /// * `Caret`: The selection is collapsed (i.e. the caret is placed on some text, but no 10 /// range has been selected). 11 /// * `Range`: A range has been selected. 12 /// 13 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/type) 14 #[derive(Copy, Clone, PartialEq, Eq, Debug)] 15 pub enum SelectionType { 16 None, 17 Caret, 18 Range 19 } 20 21 /// Represents the range of text selected by the user or the current position of the caret. To 22 /// obtain a Selection object for examination or modification, call 23 /// [Window.get_selection()](struct.Window.html#method.get_selection). 24 /// 25 /// A user may make a selection from left to right (in document order) or right to left (reverse of 26 /// document order). The anchor is where the user began the selection and the focus is where the 27 /// user ends the selection. If you make a selection with a desktop mouse, the anchor is placed 28 /// where you pressed the mouse button and the focus is placed where you released the mouse button. 29 /// Anchor and focus should not be confused with the start and end positions of a selection, since 30 /// anchor can be placed before the focus or vice versa, depending on the direction you made your 31 /// selection. 32 /// 33 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection) 34 // https://w3c.github.io/selection-api/#selection-interface 35 #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] 36 #[reference(instance_of = "Selection")] 37 pub struct Selection(Reference); 38 39 impl Selection { 40 /// Returns the [Node](struct.Node.html) in which the selection begins. 41 /// 42 /// A user may make a selection from left to right (in document order) or right to left 43 /// (reverse of document order). The anchor is where the user began the selection. This can be 44 /// visualized by holding the Shift key and pressing the arrow keys on your keyboard. The 45 /// selection's anchor does not move, but the selection's focus, the other end of the 46 /// selection, does move. 47 /// 48 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/anchorNode) anchor_node(&self) -> Option<Node>49 pub fn anchor_node(&self) -> Option<Node> { 50 js! ( 51 return @{self}.anchorNode; 52 ).try_into().unwrap() 53 } 54 55 /// Returns the number of characters that the selection's anchor is offset within the 56 /// [anchor_node](struct.Selection.html#method.anchor_node). 57 /// 58 /// This number is zero-based. If the selection begins with the first character in the 59 /// [anchor_node](struct.Selection.html#method.anchor_node), 0 is returned. 60 /// 61 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/anchorOffset) anchor_offset(&self) -> u3262 pub fn anchor_offset(&self) -> u32 { 63 js! ( 64 return @{self}.anchorOffset; 65 ).try_into().unwrap() 66 } 67 68 /// Returns the [Node](struct.Node.html) in which the selection ends. 69 /// 70 /// A user may make a selection from left to right (in document order) or right to left 71 /// (reverse of document order). The focus is where the user ended the selection. This can be 72 /// visualized by holding the Shift key and pressing the arrow keys on your keyboard to modify 73 /// the current selection. The selection's focus moves, but the selection's anchor, the other 74 /// end of the selection, does not move. 75 /// 76 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/focusNode) focus_node(&self) -> Option<Node>77 pub fn focus_node(&self) -> Option<Node> { 78 js! ( 79 return @{self}.focusNode; 80 ).try_into().unwrap() 81 } 82 83 /// Returns the number of characters that the selection's anchor is offset within the 84 /// [focus_node](struct.Selection.html#method.focus_node). 85 /// 86 /// This number is zero-based. If the selection begins with the first character in the 87 /// [focus_node](struct.Selection.html#method.focus_node), 0 is returned. 88 /// 89 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/focusOffset) focus_offset(&self) -> u3290 pub fn focus_offset(&self) -> u32 { 91 js! ( 92 return @{self}.focusOffset; 93 ).try_into().unwrap() 94 } 95 96 /// Returns a boolean which indicates whether or not there is currently any text selected; That 97 /// is to say that the selection's start and end points are at the same position in the 98 /// content. 99 /// 100 /// Keep in mind that a collapsed selection may still have one (or more, in Gecko) 101 /// [Range](struct.Range.html)s, so [range_count](struct.Selection.html#method.range_count) may 102 /// not be zero. In that scenario, calling a [Selection](struct.Selection.html) object's 103 /// [get_range_at](struct.Selection.html#method.get_range_at) method may return a 104 /// [Range](struct.Range.html) object which is collapsed. 105 /// 106 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/isCollapsed) is_collapsed(&self) -> bool107 pub fn is_collapsed(&self) -> bool { 108 js! ( 109 return @{self}.isCollapsed; 110 ).try_into().unwrap() 111 } 112 113 /// Returns the number of ranges in the selection. 114 /// 115 /// Before the user has clicked a freshly loaded page, the 116 /// [range_count](struct.Selection.html#method.range_count) is 0. After the user 117 /// clicks on the page, [range_count](struct.Selection.html#method.range_count) even if no 118 /// selection is visible. 119 /// 120 /// A user can normally only select one range at a time, so the 121 /// [range_count](struct.Selection.html#method.range_count) will usually be 1. 122 /// Scripting can be used to make the selection contain more than 1 range. 123 /// 124 /// Gecko browsers allow multiple selections across table cells. 125 /// 126 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/rangeCount) range_count(&self) -> u32127 pub fn range_count(&self) -> u32 { 128 js! ( 129 return @{self}.rangeCount; 130 ).try_into().unwrap() 131 } 132 133 /// Returns the type of the current selection. 134 /// 135 /// Possible values are: 136 /// 137 /// * `None`: No selection has currently been made. 138 /// * `Caret`: The selection is collapsed (i.e. the caret is placed on some text, but no 139 /// range has been selected). 140 /// * `Range`: A range has been selected. 141 /// 142 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/type) kind(&self) -> SelectionType143 pub fn kind(&self) -> SelectionType { 144 let selection: String = js! ( 145 return @{self}.type; 146 ).try_into().unwrap(); 147 148 match selection.as_ref() { 149 "None" => SelectionType::None, 150 "Caret" => SelectionType::Caret, 151 "Range" => SelectionType::Range, 152 _ => panic!("Selection Type invalid!"), 153 } 154 } 155 156 /// Returns a [Range](struct.Range.html) object representing one of the ranges currently 157 /// selected. 158 /// 159 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt) get_range_at(&self, index: u32) -> Result<Range, IndexSizeError>160 pub fn get_range_at(&self, index: u32) -> Result<Range, IndexSizeError> { 161 js_try! ( 162 return @{self}.getRangeAt(@{index}); 163 ).unwrap() 164 } 165 166 /// Adds a [Range](struct.Range.html) to the [Selection](struct.Selection.html). 167 /// 168 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/addRange) add_range(&self, range: &Range)169 pub fn add_range(&self, range: &Range) { 170 js! { @(no_return) 171 @{self}.addRange(@{range}); 172 }; 173 } 174 175 /// Removes a [Range](struct.Range.html) from the [Selection](struct.Selection.html). 176 /// 177 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/removeRange) remove_range(&self, range: &Range) -> Result<(), NotFoundError>178 pub fn remove_range(&self, range: &Range) -> Result<(), NotFoundError> { 179 js_try! ( @(no_return) 180 @{self}.removeRange(@{range}); 181 ).unwrap() 182 } 183 184 /// Removes all ranges from the [Selection](struct.Selection.html), leaving the 185 /// [anchor_node](struct.Selection.html#method.anchor_node) and 186 /// [focus_node](struct.Selection.html#method.focus_node) properties equal to 187 /// `None` and leaving nothing selected. 188 /// 189 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/removeRange) remove_all_ranges(&self)190 pub fn remove_all_ranges(&self) { 191 js! { @(no_return) 192 @{self}.removeAllRanges(); 193 }; 194 } 195 196 /// Collapses the [Selection](struct.Selection.html) to a single point. The document is not 197 /// modified. If the content is focused or editable, the caret will blink there. 198 /// 199 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/collapse) collapse<N: INode>(&self, node: &N)200 pub fn collapse<N: INode>(&self, node: &N) { 201 js! { @(no_return) 202 @{self}.collapse(@{node.as_ref()}); 203 } 204 } 205 206 /// Collapses the [Selection](struct.Selection.html) to a single point. The document is not 207 /// modified. If the content is focused or editable, the caret will blink there. 208 /// 209 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/collapse) collapse_with_offset<N: INode>(&self, node: &N, offset: Option<u32>) -> Result<(), IndexSizeError>210 pub fn collapse_with_offset<N: INode>(&self, node: &N, offset: Option<u32>) -> Result<(), IndexSizeError> { 211 js_try! ( @(no_return) 212 @{self}.collapse(@{node.as_ref()}, @{offset}); 213 ).unwrap() 214 } 215 216 /// Collapses the [Selection](struct.Selection.html) to the start of the first range in the 217 /// selection. If the content is focused or editable, the caret will blink there. 218 /// 219 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/collapseToStart) collapse_to_start(&self) -> Result<(), InvalidStateError>220 pub fn collapse_to_start(&self) -> Result<(), InvalidStateError> { 221 js_try! ( @(no_return) 222 @{self}.collapseToStart(); 223 ).unwrap() 224 } 225 226 /// Collapses the [Selection](struct.Selection.html) to the end of the last range in the 227 /// selection. If the content is focused or editable, the caret will blink there. 228 /// 229 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/collapseToEnd) collapse_to_end(&self) -> Result<(), InvalidStateError>230 pub fn collapse_to_end(&self) -> Result<(), InvalidStateError> { 231 js_try! ( @(no_return) 232 @{self}.collapseToEnd(); 233 ).unwrap() 234 } 235 236 /// Moves the focus of the selection to a specified point. The anchor of the selection does not 237 /// move. The selection will be from the anchor node to the new focus regardless of direction. 238 /// 239 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/extend) extend<N: INode>(&self, node: &N, offset: Option<u32>) -> Result<(), InvalidStateError>240 pub fn extend<N: INode>(&self, node: &N, offset: Option<u32>) -> Result<(), InvalidStateError> { 241 js_try! ( @(no_return) 242 @{self}.extend(@{node.as_ref()}, @{offset}); 243 ).unwrap() 244 } 245 246 /// Sets the selection to be a range including all or parts of the two specified 247 /// [Node](struct.Node.html)s, and any content located between them. 248 /// 249 /// [(Javascript 250 /// docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/setBaseAndExtent) set_base_and_extent<N: INode, M: INode>(&self, anchor_node: &N, anchor_offset: u32, focus_node: &M, focus_offset: u32) -> Result<(), IndexSizeError>251 pub fn set_base_and_extent<N: INode, M: INode>(&self, anchor_node: &N, anchor_offset: u32, focus_node: &M, focus_offset: u32) -> Result<(), IndexSizeError> { 252 js_try! ( @(no_return) 253 @{self}.setBaseAndExtent(@{anchor_node.as_ref()}, @{anchor_offset}, @{focus_node.as_ref()}, @{focus_offset}); 254 ).unwrap() 255 } 256 257 /// Adds all the children of the specified [Node](struct.Node.html) to the selection. Previous 258 /// selection is lost. 259 /// 260 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/selectAllChildren) select_all_children<N: INode>(&self, node: &N)261 pub fn select_all_children<N: INode>(&self, node: &N) { 262 js! { @(no_return) 263 @{self}.selectAllChildren(@{node.as_ref()}); 264 }; 265 } 266 267 /// Deletes the actual text being represented by the [Selection](struct.Selection.html) from 268 /// the document's DOM. 269 /// 270 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/deleteFromDocument) delete_from_document(&self)271 pub fn delete_from_document(&self) { 272 js! { @(no_return) 273 @{self}.deleteFromDocument(); 274 }; 275 } 276 277 /// Indicates if the entire node is part of the selection. 278 /// 279 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/containsNode) contains_whole<N: INode>(&self, node: &N) -> bool280 pub fn contains_whole<N: INode>(&self, node: &N) -> bool { 281 js! ( 282 return @{self}.containsNode(@{node.as_ref()}, false); 283 ).try_into().unwrap() 284 } 285 286 /// Indicates if atleast some of the node is part of the selection. 287 /// 288 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Selection/containsNode) contains_part_of<N: INode>(&self, node: &N) -> bool289 pub fn contains_part_of<N: INode>(&self, node: &N) -> bool { 290 js! ( 291 return @{self}.containsNode(@{node.as_ref()}, true); 292 ).try_into().unwrap() 293 } 294 } 295 296 /// The Range interface represents a fragment of a document that can contain nodes and parts of 297 /// text nodes. 298 /// 299 /// A range can be created using the [create_range()](struct.Document.html#method.create_range) method 300 /// of the Document object. Range objects can also be retrieved by using the 301 /// [get_range_at()](struct.Selection.html#method.get_range_at) method of the [Selection](struct.Selection.html) 302 /// object or the [caret_range_from_point()](struct.Document.html#method.caret_range_from_point) method of 303 /// the [Document](struct.Document.html] object. 304 // https://dom.spec.whatwg.org/#range 305 #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)] 306 #[reference(instance_of = "Range")] 307 pub struct Range(Reference); 308 309 impl Range { 310 /// Returns a boolean indicating whether the range's start and end points are at the same 311 /// position. 312 /// 313 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Range/collapsed) collapsed(&self) -> bool314 pub fn collapsed(&self) -> bool { 315 js! ( 316 return @{self}.collapsed; 317 ).try_into().unwrap() 318 } 319 320 /// Returns the deepest [Node](struct.Node.html) that contains the startContainer and 321 /// endContainer nodes. 322 /// 323 /// [(Javascript 324 /// docs)](https://developer.mozilla.org/en-US/docs/Web/API/Range/commonAncestorContainer) common_ancestor_container(&self) -> Node325 pub fn common_ancestor_container(&self) -> Node { 326 js! ( 327 return @{self}.commonAncestorContainer; 328 ).try_into().unwrap() 329 } 330 331 /// Returns the [Node](struct.Node.html) within which the [Range](struct.Range.html) ends. 332 /// 333 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Range/endContainer) end_container(&self) -> Node334 pub fn end_container(&self) -> Node { 335 js! ( 336 return @{self}.endContainer; 337 ).try_into().unwrap() 338 } 339 340 /// Returns a number representing where in the endContainer the [Range](struct.Range.html) ends. 341 /// 342 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset) end_offset(&self) -> u32343 pub fn end_offset(&self) -> u32 { 344 js! ( 345 return @{self}.endOffset; 346 ).try_into().unwrap() 347 } 348 349 /// Returns the [Node](struct.Node.html) within which the [Range](struct.Range.html) starts. 350 /// 351 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Range/startContainer) start_container(&self) -> Node352 pub fn start_container(&self) -> Node { 353 js! ( 354 return @{self}.startContainer; 355 ).try_into().unwrap() 356 } 357 358 /// Returns a number representing where in the startContainer the [Range](struct.Range.html) starts. 359 /// 360 /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset) start_offset(&self) -> u32361 pub fn start_offset(&self) -> u32 { 362 js! ( 363 return @{self}.startOffset; 364 ).try_into().unwrap() 365 } 366 } 367 368 #[cfg(all(test, feature = "web_test"))] 369 mod tests { 370 use super::*; 371 use webapi::document::document; 372 use webapi::window::window; 373 div() -> Node374 fn div() -> Node { 375 let node = js!( 376 return document.createElement("div"); 377 ).try_into().unwrap(); 378 document().body().unwrap().append_child(&node); 379 node 380 } 381 text(text: &str) -> Node382 fn text(text: &str) -> Node { 383 js!( 384 return new Text(@{text}); 385 ).try_into().unwrap() 386 } 387 selection() -> Selection388 fn selection() -> Selection { 389 window().get_selection().unwrap() 390 } 391 392 #[test] test_set_base_and_extent()393 fn test_set_base_and_extent() { 394 let parent = div(); 395 parent.append_child(&text("ab")); 396 397 assert!(selection().set_base_and_extent(&parent, 0, &parent, 0).is_ok()); 398 } 399 400 #[test] test_anchor()401 fn test_anchor() { 402 let parent = div(); 403 parent.append_child(&text("ab")); 404 assert!(selection().set_base_and_extent(&parent, 0, &parent, 0).is_ok()); 405 assert_eq!(selection().anchor_node().unwrap().as_ref(), parent.as_ref()); 406 assert_eq!(selection().anchor_offset(), 0); 407 } 408 409 #[test] test_focus()410 fn test_focus() { 411 let parent = div(); 412 parent.append_child(&text("ab")); 413 assert!(selection().set_base_and_extent(&parent, 0, &parent, 0).is_ok()); 414 assert_eq!(selection().focus_node().unwrap().as_ref(), parent.as_ref()); 415 assert_eq!(selection().focus_offset(), 0); 416 } 417 418 #[test] test_is_collapsed()419 fn test_is_collapsed() { 420 let parent = div(); 421 parent.append_child(&text("ab")); 422 assert!(selection().set_base_and_extent(&parent, 0, &parent, 0).is_ok()); 423 assert!(selection().is_collapsed()); 424 } 425 426 #[test] test_contains_part_of()427 fn test_contains_part_of() { 428 let parent = div(); 429 parent.append_child(&text("ab")); 430 assert!(selection().set_base_and_extent(&parent, 0, &parent, 0).is_ok()); 431 assert!(selection().contains_part_of(&parent)); 432 } 433 434 #[test] test_contains_whole()435 fn test_contains_whole() { 436 let parent = div(); 437 let text_node = text("ab"); 438 parent.append_child(&text_node); 439 selection().select_all_children(&parent); 440 assert!(selection().contains_whole(&text_node)); 441 } 442 } 443