Skip to main content

rdom_core/
content_editable.rs

1//! The `contenteditable` enumerated attribute — HTML §6.8.1. Attribute
2//! semantics only; what editing does lives in the backend.
3//!
4//! Keywords match ASCII case-insensitively (§2.3.3): `true` and the
5//! empty string are the *true* state, `false` the *false* state,
6//! `plaintext-only` the *plaintext-only* state. A missing or invalid
7//! value is the *inherit* state (both defaults are inherit), which
8//! [`Dom::content_editable_state`] reports as `None`.
9
10use crate::dom::Dom;
11use crate::node_id::NodeId;
12
13/// A `contenteditable` attribute's explicit state (HTML §6.8.1). The
14/// inherit state is the absence of one (`None`).
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum ContentEditableState {
18    /// `true` or `""`: the element is an editing host.
19    True,
20    /// `false`: the element is not editable, whatever its ancestors are.
21    False,
22    /// `plaintext-only`: an editing host whose edits are plain text.
23    PlaintextOnly,
24}
25
26impl ContentEditableState {
27    /// Parse an attribute value: the keyword, ASCII case-insensitively;
28    /// `None` (inherit) for anything else.
29    pub fn from_attribute(value: &str) -> Option<Self> {
30        if value.is_empty() || value.eq_ignore_ascii_case("true") {
31            Some(Self::True)
32        } else if value.eq_ignore_ascii_case("false") {
33            Some(Self::False)
34        } else if value.eq_ignore_ascii_case("plaintext-only") {
35            Some(Self::PlaintextOnly)
36        } else {
37            None
38        }
39    }
40
41    /// The canonical keyword — what the `contentEditable` IDL getter
42    /// returns for this state.
43    pub fn keyword(self) -> &'static str {
44        match self {
45            Self::True => "true",
46            Self::False => "false",
47            Self::PlaintextOnly => "plaintext-only",
48        }
49    }
50
51    /// `true` for the two states that make an element an editing host.
52    pub fn is_editing_host(self) -> bool {
53        matches!(self, Self::True | Self::PlaintextOnly)
54    }
55}
56
57impl<Ext> Dom<Ext> {
58    /// `id`'s explicit `contenteditable` state, or `None` for the
59    /// inherit state (attribute missing or invalid) and for non-elements.
60    pub fn content_editable_state(&self, id: NodeId) -> Option<ContentEditableState> {
61        self.get_attribute(id, "contenteditable")
62            .and_then(ContentEditableState::from_attribute)
63    }
64}