Преглед изворни кода

LibWeb: Start implementing <input type=text> using a shadow DOM

Text <input> fields will now generate a basic shadow DOM and attach it
to the input element.

The shadow DOM contains a <div> with some inline style, and an always-
editable text node inside it. Accessing the "value" attribute on such
an input element will get/set the value from that text node.

This is really cool, although not super stable since HTML editing is
not super stable. But it's a start! :^)
Andreas Kling пре 4 година
родитељ
комит
29a2aac89a

+ 7 - 0
Userland/Libraries/LibWeb/CSS/Default.css

@@ -194,3 +194,10 @@ ul,
 ol {
     padding-left: 20px;
 }
+
+/* FIXME: This is a temporary hack until we can render a native-looking frame for these. */
+input[type=text] {
+    border: 1px solid black;
+    min-width: 80px;
+    min-height: 16px;
+}

+ 42 - 2
Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp

@@ -27,10 +27,13 @@
 #include <LibGfx/FontDatabase.h>
 #include <LibWeb/DOM/Document.h>
 #include <LibWeb/DOM/Event.h>
+#include <LibWeb/DOM/ShadowRoot.h>
+#include <LibWeb/DOM/Text.h>
 #include <LibWeb/HTML/EventNames.h>
 #include <LibWeb/HTML/HTMLFormElement.h>
 #include <LibWeb/HTML/HTMLInputElement.h>
 #include <LibWeb/InProcessWebView.h>
+#include <LibWeb/Layout/BlockBox.h>
 #include <LibWeb/Layout/ButtonBox.h>
 #include <LibWeb/Layout/CheckBox.h>
 #include <LibWeb/Page/Frame.h>
@@ -74,8 +77,10 @@ RefPtr<Layout::Node> HTMLInputElement::create_layout_node()
     if (type() == "checkbox")
         return adopt(*new Layout::CheckBox(document(), *this, move(style)));
 
-    // FIXME: Implement <input type=text> in terms of LibWeb primitives.
-    return nullptr;
+    create_shadow_tree_if_needed();
+    auto layout_node = adopt(*new Layout::BlockBox(document(), this, move(style)));
+    layout_node->set_inline(true);
+    return layout_node;
 }
 
 void HTMLInputElement::set_checked(bool checked)
@@ -94,4 +99,39 @@ bool HTMLInputElement::enabled() const
     return !has_attribute(HTML::AttributeNames::disabled);
 }
 
+String HTMLInputElement::value() const
+{
+    if (m_text_node)
+        return m_text_node->data();
+    return default_value();
+}
+
+void HTMLInputElement::set_value(String value)
+{
+    if (m_text_node) {
+        m_text_node->set_data(move(value));
+        return;
+    }
+    set_attribute(HTML::AttributeNames::value, move(value));
+}
+
+void HTMLInputElement::create_shadow_tree_if_needed()
+{
+    if (shadow_root())
+        return;
+
+    // FIXME: This assumes that we want a text box. Is that always true?
+    auto shadow_root = adopt(*new DOM::ShadowRoot(document(), *this));
+    auto initial_value = attribute(HTML::AttributeNames::value);
+    if (initial_value.is_null())
+        initial_value = String::empty();
+    auto element = document().create_element(HTML::TagNames::div);
+    element->set_attribute(HTML::AttributeNames::style, "white-space: pre");
+    m_text_node = adopt(*new DOM::Text(document(), initial_value));
+    m_text_node->set_always_editable(true);
+    element->append_child(*m_text_node);
+    shadow_root->append_child(move(element));
+    set_shadow_root(move(shadow_root));
+}
+
 }

+ 7 - 1
Userland/Libraries/LibWeb/HTML/HTMLInputElement.h

@@ -40,9 +40,12 @@ public:
     virtual RefPtr<Layout::Node> create_layout_node() override;
 
     String type() const { return attribute(HTML::AttributeNames::type); }
-    String value() const { return attribute(HTML::AttributeNames::value); }
+    String default_value() const { return attribute(HTML::AttributeNames::value); }
     String name() const { return attribute(HTML::AttributeNames::name); }
 
+    String value() const;
+    void set_value(String);
+
     bool checked() const { return m_checked; }
     void set_checked(bool);
 
@@ -51,6 +54,9 @@ public:
     void did_click_button(Badge<Layout::ButtonBox>);
 
 private:
+    void create_shadow_tree_if_needed();
+
+    RefPtr<DOM::Text> m_text_node;
     bool m_checked { false };
 };
 

+ 2 - 0
Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl

@@ -11,6 +11,8 @@ interface HTMLInputElement : HTMLElement {
     [Reflect=dirname] attribute DOMString dirName;
     [Reflect=value] attribute DOMString defaultValue;
 
+    [LegacyNullToEmptyString] attribute DOMString value;
+
     attribute boolean checked;
 
     [Reflect] attribute boolean disabled;