diff options
Diffstat (limited to 'Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp')
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp | 80 |
1 files changed, 72 insertions, 8 deletions
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 7cc0d5ba35..2ec5e4c762 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org> * Copyright (c) 2022, Adam Hodgen <ant1441@gmail.com> * Copyright (c) 2022, Andrew Kaster <akaster@serenityos.org> * @@ -15,6 +15,7 @@ #include <LibWeb/DOM/Text.h> #include <LibWeb/HTML/BrowsingContext.h> #include <LibWeb/HTML/EventNames.h> +#include <LibWeb/HTML/HTMLDivElement.h> #include <LibWeb/HTML/HTMLFormElement.h> #include <LibWeb/HTML/HTMLInputElement.h> #include <LibWeb/HTML/Scripting/Environments.h> @@ -56,7 +57,10 @@ JS::ThrowCompletionOr<void> HTMLInputElement::initialize(JS::Realm& realm) void HTMLInputElement::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); - visitor.visit(m_text_node.ptr()); + visitor.visit(m_inner_text_element); + visitor.visit(m_text_node); + visitor.visit(m_placeholder_element); + visitor.visit(m_placeholder_text_node); visitor.visit(m_legacy_pre_activation_behavior_checked_element_in_group.ptr()); visitor.visit(m_selected_files); } @@ -284,6 +288,8 @@ void HTMLInputElement::did_edit_text_node(Badge<BrowsingContext>) m_value = value_sanitization_algorithm(m_text_node->data()); m_dirty_value = true; + update_placeholder_visibility(); + // NOTE: This is a bit ad-hoc, but basically implements part of "4.10.5.5 Common event behaviors" // https://html.spec.whatwg.org/multipage/input.html#common-input-element-events queue_an_element_task(HTML::Task::Source::UserInteraction, [this] { @@ -342,12 +348,26 @@ WebIDL::ExceptionOr<void> HTMLInputElement::set_value(DeprecatedString value) // 5. If the element's value (after applying the value sanitization algorithm) is different from oldValue, // and the element has a text entry cursor position, move the text entry cursor position to the end of the // text control, unselecting any selected text and resetting the selection direction to "none". - if (m_text_node && (m_value != old_value)) + if (m_text_node && (m_value != old_value)) { m_text_node->set_data(m_value); + update_placeholder_visibility(); + } return {}; } +void HTMLInputElement::update_placeholder_visibility() +{ + if (!m_placeholder_element) + return; + auto placeholder_text = this->placeholder_value(); + if (placeholder_text.has_value()) { + 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/input.html#the-input-element:attr-input-placeholder-3 static bool is_allowed_to_have_placeholder(HTML::HTMLInputElement::TypeAttributeState state) { @@ -389,6 +409,17 @@ Optional<DeprecatedString> HTMLInputElement::placeholder_value() const return placeholder; } +class PlaceholderElement final : public HTMLDivElement { + JS_CELL(PlaceholderElement, HTMLDivElement); + +public: + PlaceholderElement(DOM::Document& document) + : HTMLDivElement(document, DOM::QualifiedName { HTML::TagNames::div, ""sv, Namespace::HTML }) + { + } + virtual Optional<CSS::Selector::PseudoElement> pseudo_element() const override { return CSS::Selector::PseudoElement::Placeholder; } +}; + void HTMLInputElement::create_shadow_tree_if_needed() { if (shadow_root_internal()) @@ -412,7 +443,27 @@ void HTMLInputElement::create_shadow_tree_if_needed() if (initial_value.is_null()) initial_value = DeprecatedString::empty(); auto element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors(); - MUST(element->set_attribute(HTML::AttributeNames::style, "white-space: pre; padding-top: 1px; padding-bottom: 1px; padding-left: 2px; padding-right: 2px; height: 1lh;")); + MUST(element->set_attribute(HTML::AttributeNames::style, R"~~~( + display: flex; + height: 100%; + align-items: center; + white-space: pre; + border: none; + padding: 1px 2px; +)~~~")); + + m_placeholder_element = heap().allocate<PlaceholderElement>(realm(), document()).release_allocated_value_but_fixme_should_propagate_errors(); + MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Height, "1lh"sv)); + + m_placeholder_text_node = heap().allocate<DOM::Text>(realm(), document(), initial_value).release_allocated_value_but_fixme_should_propagate_errors(); + m_placeholder_text_node->set_data(attribute(HTML::AttributeNames::placeholder)); + m_placeholder_text_node->set_owner_input_element({}, *this); + MUST(m_placeholder_element->append_child(*m_placeholder_text_node)); + MUST(element->append_child(*m_placeholder_element)); + + m_inner_text_element = DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML).release_value_but_fixme_should_propagate_errors(); + MUST(m_inner_text_element->style_for_bindings()->set_property(CSS::PropertyID::Height, "1lh"sv)); + m_text_node = heap().allocate<DOM::Text>(realm(), document(), initial_value).release_allocated_value_but_fixme_should_propagate_errors(); m_text_node->set_always_editable(m_type != TypeAttributeState::FileUpload); m_text_node->set_owner_input_element({}, *this); @@ -420,7 +471,8 @@ void HTMLInputElement::create_shadow_tree_if_needed() if (m_type == TypeAttributeState::Password) m_text_node->set_is_password_input({}, true); - MUST(element->append_child(*m_text_node)); + 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); } @@ -446,8 +498,13 @@ void HTMLInputElement::parse_attribute(DeprecatedFlyString const& name, Deprecat } else if (name == HTML::AttributeNames::type) { m_type = parse_type_attribute(value); } else if (name == HTML::AttributeNames::value) { - if (!m_dirty_value) + if (!m_dirty_value) { m_value = value_sanitization_algorithm(value); + update_placeholder_visibility(); + } + } else if (name == HTML::AttributeNames::placeholder) { + if (m_placeholder_text_node) + m_placeholder_text_node->set_data(value); } } @@ -474,8 +531,13 @@ void HTMLInputElement::did_remove_attribute(DeprecatedFlyString const& name) if (!m_dirty_checkedness) set_checked(false, ChangeSource::Programmatic); } else if (name == HTML::AttributeNames::value) { - if (!m_dirty_value) + if (!m_dirty_value) { m_value = DeprecatedString::empty(); + update_placeholder_visibility(); + } + } else if (name == HTML::AttributeNames::placeholder) { + if (m_placeholder_text_node) + m_placeholder_text_node->set_data({}); } } @@ -785,8 +847,10 @@ void HTMLInputElement::reset_algorithm() // and then invoke the value sanitization algorithm, if the type attribute's current state defines one. m_value = value_sanitization_algorithm(m_value); - if (m_text_node) + if (m_text_node) { m_text_node->set_data(m_value); + update_placeholder_visibility(); + } } void HTMLInputElement::form_associated_element_was_inserted() |