mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 11:18:11 +00:00
LibWeb: Create a shadow tree for <input type=file> elements
This creates a button to prompt users to select a file, and a label to show information about the selected file(s). Clicking either shadow element will activate the input element.
This commit is contained in:
parent
8319c7cfb8
commit
435c2c24d1
6 changed files with 119 additions and 3 deletions
54
Tests/LibWeb/Layout/expected/input-file.txt
Normal file
54
Tests/LibWeb/Layout/expected/input-file.txt
Normal file
|
@ -0,0 +1,54 @@
|
|||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
|
||||
BlockContainer <body> at (8,8) content-size 784x21 children: inline
|
||||
frag 0 from BlockContainer start: 0, length: 0, rect: [8,8 236.65625x21] baseline: 13.296875
|
||||
frag 1 from TextNode start: 0, length: 1, rect: [245,8 8x17] baseline: 13.296875
|
||||
" "
|
||||
frag 2 from BlockContainer start: 0, length: 0, rect: [253,8 255.34375x21] baseline: 13.296875
|
||||
BlockContainer <input> at (8,8) content-size 236.65625x21 inline-block [BFC] children: inline
|
||||
frag 0 from BlockContainer start: 0, length: 0, rect: [13,10 94.375x17] baseline: 13.296875
|
||||
frag 1 from Label start: 0, length: 0, rect: [116,8 128.28125x17] baseline: 13.296875
|
||||
BlockContainer <button> at (13,10) content-size 94.375x17 inline-block [BFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (13,10) content-size 94.375x17 flex-container(column) [FFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (13,10) content-size 94.375x17 flex-item [BFC] children: inline
|
||||
frag 0 from TextNode start: 0, length: 14, rect: [13,10 94.375x17] baseline: 13.296875
|
||||
"Select file..."
|
||||
TextNode <#text>
|
||||
Label <label> at (116,8) content-size 128.28125x17 inline-block [BFC] children: inline
|
||||
frag 0 from TextNode start: 0, length: 17, rect: [116,8 128.28125x17] baseline: 13.296875
|
||||
"No file selected."
|
||||
TextNode <#text>
|
||||
TextNode <#text>
|
||||
BlockContainer <input> at (253,8) content-size 255.34375x21 inline-block [BFC] children: inline
|
||||
frag 0 from BlockContainer start: 0, length: 0, rect: [258,10 103.71875x17] baseline: 13.296875
|
||||
frag 1 from Label start: 0, length: 0, rect: [371,8 137.625x17] baseline: 13.296875
|
||||
BlockContainer <button> at (258,10) content-size 103.71875x17 inline-block [BFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (258,10) content-size 103.71875x17 flex-container(column) [FFC] children: not-inline
|
||||
BlockContainer <(anonymous)> at (258,10) content-size 103.71875x17 flex-item [BFC] children: inline
|
||||
frag 0 from TextNode start: 0, length: 15, rect: [258,10 103.71875x17] baseline: 13.296875
|
||||
"Select files..."
|
||||
TextNode <#text>
|
||||
Label <label> at (371,8) content-size 137.625x17 inline-block [BFC] children: inline
|
||||
frag 0 from TextNode start: 0, length: 18, rect: [371,8 137.625x17] baseline: 13.296875
|
||||
"No files selected."
|
||||
TextNode <#text>
|
||||
TextNode <#text>
|
||||
|
||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
||||
PaintableWithLines (BlockContainer<INPUT>) [8,8 236.65625x21]
|
||||
PaintableWithLines (BlockContainer<BUTTON>) [8,8 104.375x21]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [13,10 94.375x17]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [13,10 94.375x17]
|
||||
TextPaintable (TextNode<#text>)
|
||||
PaintableWithLines (Label<LABEL>) [112,8 132.28125x17]
|
||||
TextPaintable (TextNode<#text>)
|
||||
TextPaintable (TextNode<#text>)
|
||||
PaintableWithLines (BlockContainer<INPUT>) [253,8 255.34375x21] overflow: [253,8 255.625x21]
|
||||
PaintableWithLines (BlockContainer<BUTTON>) [253,8 113.71875x21]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [258,10 103.71875x17]
|
||||
PaintableWithLines (BlockContainer(anonymous)) [258,10 103.71875x17]
|
||||
TextPaintable (TextNode<#text>)
|
||||
PaintableWithLines (Label<LABEL>) [367,8 141.625x17]
|
||||
TextPaintable (TextNode<#text>)
|
2
Tests/LibWeb/Layout/input/input-file.html
Normal file
2
Tests/LibWeb/Layout/input/input-file.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<input type="file" />
|
||||
<input type="file" multiple />
|
|
@ -1,4 +1,4 @@
|
|||
pass text: "pass"
|
||||
pass Select file...No file selected. text: "pass"
|
||||
hidden: "pass"
|
||||
button: "pass"
|
||||
checkbox: "pass"
|
||||
|
|
|
@ -26,7 +26,7 @@ label {
|
|||
}
|
||||
|
||||
/* FIXME: This is a temporary hack until we can render a native-looking frame for these. */
|
||||
input:not([type=submit], input[type=button], input[type=image], input[type=reset], input[type=color], input[type=checkbox], input[type=radio], input[type=range]), textarea {
|
||||
input:not([type=submit], input[type=button], input[type=image], input[type=reset], input[type=color], input[type=checkbox], input[type=file], input[type=radio], input[type=range]), textarea {
|
||||
border: 1px solid ButtonBorder;
|
||||
min-height: 16px;
|
||||
width: attr(size ch, 20ch);
|
||||
|
|
|
@ -69,6 +69,8 @@ void HTMLInputElement::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(m_placeholder_element);
|
||||
visitor.visit(m_placeholder_text_node);
|
||||
visitor.visit(m_color_well_element);
|
||||
visitor.visit(m_file_button);
|
||||
visitor.visit(m_file_label);
|
||||
visitor.visit(m_legacy_pre_activation_behavior_checked_element_in_group);
|
||||
visitor.visit(m_selected_files);
|
||||
visitor.visit(m_slider_thumb);
|
||||
|
@ -80,7 +82,7 @@ JS::GCPtr<Layout::Node> HTMLInputElement::create_layout_node(NonnullRefPtr<CSS::
|
|||
if (type_state() == TypeAttributeState::Hidden)
|
||||
return nullptr;
|
||||
|
||||
if (type_state() == TypeAttributeState::SubmitButton || type_state() == TypeAttributeState::Button || type_state() == TypeAttributeState::ResetButton || type_state() == TypeAttributeState::FileUpload)
|
||||
if (type_state() == TypeAttributeState::SubmitButton || type_state() == TypeAttributeState::Button || type_state() == TypeAttributeState::ResetButton)
|
||||
return heap().allocate_without_realm<Layout::ButtonBox>(document(), *this, move(style));
|
||||
|
||||
if (type_state() == TypeAttributeState::ImageButton)
|
||||
|
@ -600,6 +602,9 @@ void HTMLInputElement::create_shadow_tree_if_needed()
|
|||
case TypeAttributeState::Color:
|
||||
create_color_input_shadow_tree();
|
||||
break;
|
||||
case TypeAttributeState::FileUpload:
|
||||
create_file_input_shadow_tree();
|
||||
break;
|
||||
case TypeAttributeState::Range:
|
||||
create_range_input_shadow_tree();
|
||||
break;
|
||||
|
@ -616,6 +621,9 @@ void HTMLInputElement::update_shadow_tree()
|
|||
case TypeAttributeState::Color:
|
||||
update_color_well_element();
|
||||
break;
|
||||
case TypeAttributeState::FileUpload:
|
||||
update_file_input_shadow_tree();
|
||||
break;
|
||||
case TypeAttributeState::Range:
|
||||
update_slider_thumb_element();
|
||||
break;
|
||||
|
@ -752,6 +760,53 @@ void HTMLInputElement::update_color_well_element()
|
|||
MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value));
|
||||
}
|
||||
|
||||
void HTMLInputElement::create_file_input_shadow_tree()
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
|
||||
auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm, document(), *this, Bindings::ShadowRootMode::Closed);
|
||||
|
||||
m_file_button = DOM::create_element(document(), HTML::TagNames::button, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
|
||||
m_file_label = DOM::create_element(document(), HTML::TagNames::label, Namespace::HTML).release_value_but_fixme_should_propagate_errors();
|
||||
MUST(m_file_label->set_attribute(HTML::AttributeNames::style, "padding-left: 4px;"_string));
|
||||
|
||||
auto on_button_click = [this](JS::VM&) {
|
||||
show_the_picker_if_applicable(*this);
|
||||
return JS::js_undefined();
|
||||
};
|
||||
|
||||
auto on_button_click_function = JS::NativeFunction::create(realm, move(on_button_click), 0, "", &realm);
|
||||
auto on_button_click_callback = realm.heap().allocate_without_realm<WebIDL::CallbackType>(on_button_click_function, Bindings::host_defined_environment_settings_object(realm));
|
||||
m_file_button->add_event_listener_without_options(UIEvents::EventNames::click, DOM::IDLEventListener::create(realm, on_button_click_callback));
|
||||
|
||||
update_file_input_shadow_tree();
|
||||
|
||||
MUST(shadow_root->append_child(*m_file_button));
|
||||
MUST(shadow_root->append_child(*m_file_label));
|
||||
|
||||
set_shadow_root(shadow_root);
|
||||
}
|
||||
|
||||
void HTMLInputElement::update_file_input_shadow_tree()
|
||||
{
|
||||
if (!m_file_button || !m_file_label)
|
||||
return;
|
||||
|
||||
auto files_label = has_attribute(HTML::AttributeNames::multiple) ? "files"sv : "file"sv;
|
||||
m_file_button->set_text_content(MUST(String::formatted("Select {}...", files_label)));
|
||||
|
||||
if (m_selected_files && m_selected_files->length() > 0) {
|
||||
if (m_selected_files->length() == 1)
|
||||
m_file_label->set_text_content(m_selected_files->item(0)->name());
|
||||
else
|
||||
m_file_label->set_text_content(MUST(String::formatted("{} files selected.", m_selected_files->length())));
|
||||
} else {
|
||||
m_file_label->set_text_content(MUST(String::formatted("No {} selected.", files_label)));
|
||||
}
|
||||
|
||||
document().invalidate_layout();
|
||||
}
|
||||
|
||||
void HTMLInputElement::create_range_input_shadow_tree()
|
||||
{
|
||||
auto shadow_root = heap().allocate<DOM::ShadowRoot>(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
|
||||
|
|
|
@ -226,6 +226,7 @@ private:
|
|||
void update_shadow_tree();
|
||||
void create_text_input_shadow_tree();
|
||||
void create_color_input_shadow_tree();
|
||||
void create_file_input_shadow_tree();
|
||||
void create_range_input_shadow_tree();
|
||||
WebIDL::ExceptionOr<void> run_input_activation_behavior(DOM::Event const&);
|
||||
void set_checked_within_group();
|
||||
|
@ -255,6 +256,10 @@ private:
|
|||
void update_color_well_element();
|
||||
JS::GCPtr<DOM::Element> m_color_well_element;
|
||||
|
||||
void update_file_input_shadow_tree();
|
||||
JS::GCPtr<DOM::Element> m_file_button;
|
||||
JS::GCPtr<DOM::Element> m_file_label;
|
||||
|
||||
void update_slider_thumb_element();
|
||||
JS::GCPtr<DOM::Element> m_slider_thumb;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue