LibWeb: Add textarea placeholder

This commit is contained in:
Bastiaan van der Plaat 2023-12-21 18:13:16 +01:00 committed by Andreas Kling
parent 091faf1aae
commit 52397d01bd
6 changed files with 85 additions and 33 deletions

View file

@ -1,5 +1,8 @@
<!doctype html>
<textarea>
<textarea cols="80" rows="5">
This is some text
inside a &lt;textarea&gt;
</textarea>
<textarea placeholder="Textarea placeholder!" cols="80" rows="5"></textarea>
<textarea placeholder="Textarea placeholder!
That is multiline!" cols="80" rows="5"></textarea>

View file

@ -1,38 +1,38 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x56.9375 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x40.9375 children: inline
line 0 width: 403.75, height: 40.9375, bottom: 40.9375, baseline: 13.53125
frag 0 from BlockContainer start: 0, length: 0, rect: [11,11 191.875x34.9375]
frag 1 from TextNode start: 0, length: 1, rect: [206,8 8x17.46875]
BlockContainer <html> at (0,0) content-size 800x65.6875 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x49.6875 children: inline
line 0 width: 502, height: 49.6875, bottom: 49.6875, baseline: 16.921875
frag 0 from BlockContainer start: 0, length: 0, rect: [11,11 240x43.6875]
frag 1 from TextNode start: 0, length: 1, rect: [254,8 10x21.84375]
" "
frag 2 from BlockContainer start: 0, length: 0, rect: [217,11 191.875x34.9375]
frag 2 from BlockContainer start: 0, length: 0, rect: [267,11 240x43.6875]
TextNode <#text>
BlockContainer <textarea> at (11,11) content-size 191.875x34.9375 inline-block [BFC] children: not-inline
BlockContainer <div> at (11,11) content-size 191.875x17.46875 children: not-inline
BlockContainer <div> at (11,11) content-size 191.875x17.46875 children: inline
line 0 width: 152.203125, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 17, rect: [11,11 152.203125x17.46875]
BlockContainer <textarea> at (11,11) content-size 240x43.6875 inline-block [BFC] children: not-inline
BlockContainer <div> at (11,11) content-size 240x21.84375 children: not-inline
BlockContainer <div> at (11,11) content-size 240x21.84375 children: inline
line 0 width: 190.265625, height: 21.84375, bottom: 21.84375, baseline: 16.921875
frag 0 from TextNode start: 0, length: 17, rect: [11,11 190.265625x21.84375]
"Bonjour mon amis!"
TextNode <#text>
TextNode <#text>
BlockContainer <textarea> at (217,11) content-size 191.875x34.9375 inline-block [BFC] children: not-inline
BlockContainer <div> at (217,11) content-size 191.875x17.46875 children: not-inline
BlockContainer <div> at (217,11) content-size 191.875x17.46875 children: inline
line 0 width: 142.140625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
frag 0 from TextNode start: 0, length: 19, rect: [217,11 142.140625x17.46875]
BlockContainer <textarea> at (267,11) content-size 240x43.6875 inline-block [BFC] children: not-inline
BlockContainer <div> at (267,11) content-size 240x21.84375 children: not-inline
BlockContainer <div> at (267,11) content-size 240x21.84375 children: inline
line 0 width: 177.6875, height: 21.84375, bottom: 21.84375, baseline: 16.921875
frag 0 from TextNode start: 0, length: 19, rect: [267,11 177.6875x21.84375]
"Well hello friends!"
TextNode <#text>
TextNode <#text>
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x56.9375]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x40.9375]
PaintableWithLines (BlockContainer<TEXTAREA>) [8,8 197.875x40.9375]
PaintableWithLines (BlockContainer<DIV>) [11,11 191.875x17.46875]
PaintableWithLines (BlockContainer<DIV>) [11,11 191.875x17.46875]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x65.6875]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x49.6875]
PaintableWithLines (BlockContainer<TEXTAREA>) [8,8 246x49.6875]
PaintableWithLines (BlockContainer<DIV>) [11,11 240x21.84375]
PaintableWithLines (BlockContainer<DIV>) [11,11 240x21.84375]
TextPaintable (TextNode<#text>)
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer<TEXTAREA>) [214,8 197.875x40.9375]
PaintableWithLines (BlockContainer<DIV>) [217,11 191.875x17.46875]
PaintableWithLines (BlockContainer<DIV>) [217,11 191.875x17.46875]
PaintableWithLines (BlockContainer<TEXTAREA>) [264,8 246x49.6875]
PaintableWithLines (BlockContainer<DIV>) [267,11 240x21.84375]
PaintableWithLines (BlockContainer<DIV>) [267,11 240x21.84375]
TextPaintable (TextNode<#text>)

View file

@ -1,9 +1,11 @@
<!doctype html>
<body>
<!DOCTYPE html><html><head><style>
* {
font: 20px 'SerenitySans';
}
</style></head><body>
<textarea>Bonjour mon amis!</textarea>
<script>
const textarea = document.createElement("textarea");
textarea.innerText = "Well hello friends!";
document.body.appendChild(textarea);
</script>
</body>

View file

@ -43,7 +43,7 @@ textarea {
height: attr(rows lh, 2lh);
}
input::placeholder {
input::placeholder, textarea::placeholder {
color: GrayText;
}

View file

@ -47,6 +47,8 @@ void HTMLTextAreaElement::initialize(JS::Realm& realm)
void HTMLTextAreaElement::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_placeholder_element);
visitor.visit(m_placeholder_text_node);
visitor.visit(m_inner_text_element);
visitor.visit(m_text_node);
}
@ -86,6 +88,8 @@ void HTMLTextAreaElement::reset_algorithm()
m_dirty = false;
// and set the raw value of element to its child text content.
m_raw_value = child_text_content();
update_placeholder_visibility();
}
void HTMLTextAreaElement::form_associated_element_was_inserted()
@ -181,9 +185,22 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed()
return;
auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
set_shadow_root(shadow_root);
auto element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
MUST(shadow_root->append_child(element));
m_placeholder_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
m_placeholder_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Placeholder);
MUST(element->append_child(*m_placeholder_element));
m_placeholder_text_node = heap().allocate<DOM::Text>(realm(), document(), String {});
m_placeholder_text_node->set_data(get_attribute(HTML::AttributeNames::placeholder).value_or(String {}));
m_placeholder_text_node->set_editable_text_node_owner(Badge<HTMLTextAreaElement> {}, *this);
MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
m_inner_text_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
MUST(element->append_child(*m_inner_text_element));
m_text_node = heap().allocate<DOM::Text>(realm(), document(), String {});
m_text_node->set_always_editable(true);
@ -191,11 +208,23 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed()
// NOTE: If `children_changed()` was called before now, `m_raw_value` will hold the text content.
// Otherwise, it will get filled in whenever that does get called.
m_text_node->set_text_content(m_raw_value);
MUST(m_inner_text_element->append_child(*m_text_node));
MUST(element->append_child(*m_inner_text_element));
MUST(shadow_root->append_child(element));
set_shadow_root(shadow_root);
update_placeholder_visibility();
}
void HTMLTextAreaElement::update_placeholder_visibility()
{
if (!m_placeholder_element)
return;
if (!m_text_node)
return;
auto placeholder_text = get_attribute(AttributeNames::placeholder);
if (placeholder_text.has_value() && m_text_node->data().is_empty()) {
MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"sv));
} else {
MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"sv));
}
}
// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:children-changed-steps
@ -207,6 +236,16 @@ void HTMLTextAreaElement::children_changed()
m_raw_value = child_text_content();
if (m_text_node)
m_text_node->set_text_content(m_raw_value);
update_placeholder_visibility();
}
}
void HTMLTextAreaElement::attribute_changed(FlyString const& name, Optional<String> const& value)
{
HTMLElement::attribute_changed(name, value);
if (name == HTML::AttributeNames::placeholder) {
if (m_placeholder_text_node)
m_placeholder_text_node->set_data(value.value_or(String {}));
}
}
@ -214,6 +253,8 @@ void HTMLTextAreaElement::did_edit_text_node(Badge<Web::HTML::BrowsingContext>)
{
// A textarea element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the raw value.
m_dirty = true;
update_placeholder_visibility();
}
}

View file

@ -56,6 +56,8 @@ public:
virtual bool is_auto_capitalize_inheriting() const override { return true; }
// ^HTMLElement
virtual void attribute_changed(FlyString const&, Optional<String> const&) override;
// https://html.spec.whatwg.org/multipage/forms.html#category-label
virtual bool is_labelable() const override { return true; }
@ -94,6 +96,10 @@ private:
void create_shadow_tree_if_needed();
void update_placeholder_visibility();
JS::GCPtr<DOM::Element> m_placeholder_element;
JS::GCPtr<DOM::Text> m_placeholder_text_node;
JS::GCPtr<DOM::Element> m_inner_text_element;
JS::GCPtr<DOM::Text> m_text_node;