diff --git a/Base/res/html/misc/input-range.html b/Base/res/html/misc/input-range.html new file mode 100644 index 00000000000..78acbd1895e --- /dev/null +++ b/Base/res/html/misc/input-range.html @@ -0,0 +1,68 @@ + + + + + + Input range showcase + + + +

Input range showcase

+

+ + Value: ? +

+

+ + Value: ? +

+

+ + Value: ? +

+

+ + Value: ? +

+ + diff --git a/Base/res/html/misc/input.html b/Base/res/html/misc/input.html index 773843e227c..f4d3d3fb542 100644 --- a/Base/res/html/misc/input.html +++ b/Base/res/html/misc/input.html @@ -29,7 +29,7 @@


-
+



diff --git a/Userland/Libraries/LibWeb/CSS/Default.css b/Userland/Libraries/LibWeb/CSS/Default.css index 6fd4d66b533..8e3d3f67e51 100644 --- a/Userland/Libraries/LibWeb/CSS/Default.css +++ b/Userland/Libraries/LibWeb/CSS/Default.css @@ -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=reset], input[type=color], input[type=checkbox], input[type=radio]), textarea { +input:not([type=submit], input[type=button], input[type=reset], input[type=color], input[type=checkbox], input[type=radio], input[type=range]), textarea { border: 1px solid ButtonBorder; min-height: 16px; width: attr(size ch, 20ch); @@ -70,6 +70,30 @@ option { display: none; } +/* Custom styles */ +input[type=range] { + display: inline-block; + width: 20ch; + height: 16px; +} +input[type=range]::-webkit-slider-runnable-track, input[type=range]::-webkit-slider-thumb { + display: block; +} +input[type=range]::-webkit-slider-runnable-track { + height: 4px; + margin-top: 6px; + background-color: hsl(217, 71%, 53%); + border: 1px solid rgba(0, 0, 0, 0.5); +} +input[type=range]::-webkit-slider-thumb { + margin-top: -6px; + width: 16px; + height: 16px; + border-radius: 50%; + background-color: hsl(0, 0%, 96%); + outline: 1px solid rgba(0, 0, 0, 0.5); +} + /* Custom styles */ meter { display: inline-block; diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp index 764ca3fd1a0..2b8dc05757a 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.cpp +++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp @@ -390,6 +390,10 @@ StringView Selector::PseudoElement::name(Selector::PseudoElement::Type pseudo_el return "placeholder"sv; case Selector::PseudoElement::Type::Selection: return "selection"sv; + case Selector::PseudoElement::Type::SliderRunnableTrack: + return "-webkit-slider-runnable-track"sv; + case Selector::PseudoElement::Type::SliderThumb: + return "-webkit-slider-thumb"sv; case Selector::PseudoElement::Type::KnownPseudoElementCount: break; case Selector::PseudoElement::Type::UnknownWebKit: @@ -426,6 +430,10 @@ Optional Selector::PseudoElement::from_string(FlyString return Selector::PseudoElement { Selector::PseudoElement::Type::Placeholder }; } else if (name.equals_ignoring_ascii_case("selection"sv)) { return Selector::PseudoElement { Selector::PseudoElement::Type::Selection }; + } else if (name.equals_ignoring_ascii_case("-webkit-slider-runnable-track"sv)) { + return Selector::PseudoElement { Selector::PseudoElement::Type::SliderRunnableTrack }; + } else if (name.equals_ignoring_ascii_case("-webkit-slider-thumb"sv)) { + return Selector::PseudoElement { Selector::PseudoElement::Type::SliderThumb }; } return {}; } diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index 5490f3fd417..76a7dfbcb23 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -37,6 +37,8 @@ public: ProgressBar, Placeholder, Selection, + SliderRunnableTrack, + SliderThumb, // Keep this last. KnownPseudoElementCount, diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp index 65583849380..aef3e4714f0 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -3,6 +3,7 @@ * Copyright (c) 2022, Adam Hodgen * Copyright (c) 2022, Andrew Kaster * Copyright (c) 2023, Shannon Booth + * Copyright (c) 2023, Bastiaan van der Plaat * * SPDX-License-Identifier: BSD-2-Clause */ @@ -62,6 +63,7 @@ void HTMLInputElement::visit_edges(Cell::Visitor& visitor) visitor.visit(m_color_well_element); visitor.visit(m_legacy_pre_activation_behavior_checked_element_in_group); visitor.visit(m_selected_files); + visitor.visit(m_slider_thumb); } JS::GCPtr HTMLInputElement::create_layout_node(NonnullRefPtr style) @@ -547,6 +549,9 @@ void HTMLInputElement::create_shadow_tree_if_needed() case TypeAttributeState::Color: create_color_input_shadow_tree(); break; + case TypeAttributeState::Range: + create_range_input_shadow_tree(); + break; // FIXME: This could be better factored. Everything except the above types becomes a text input. default: create_text_input_shadow_tree(); @@ -674,6 +679,38 @@ void HTMLInputElement::create_color_input_shadow_tree() set_shadow_root(shadow_root); } +void HTMLInputElement::create_range_input_shadow_tree() +{ + auto shadow_root = heap().allocate(realm(), document(), *this, Bindings::ShadowRootMode::Closed); + set_shadow_root(shadow_root); + + auto slider_runnable_track = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); + slider_runnable_track->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::SliderRunnableTrack); + MUST(shadow_root->append_child(slider_runnable_track)); + + m_slider_thumb = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML)); + m_slider_thumb->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::SliderThumb); + MUST(slider_runnable_track->append_child(*m_slider_thumb)); + update_slider_thumb_element(); +} + +void HTMLInputElement::update_slider_thumb_element() +{ + double minimum = *min(); + double maximum = *max(); + + double default_value = minimum + (maximum - minimum) / 2; + if (maximum < minimum) + default_value = minimum; + + double value = MUST(value_as_number()); + if (!isfinite(value)) + value = default_value; + + double position = (value - minimum) / (maximum - minimum) * 100; + MUST(m_slider_thumb->style_for_bindings()->set_property(CSS::PropertyID::MarginLeft, MUST(String::formatted("{}%", position)))); +} + void HTMLInputElement::did_receive_focus() { auto* browsing_context = document().browsing_context(); @@ -714,6 +751,9 @@ void HTMLInputElement::attribute_changed(FlyString const& name, Optional if (type_state() == TypeAttributeState::Color && m_color_well_element) MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value)); + + if (type_state() == TypeAttributeState::Range && m_slider_thumb) + update_slider_thumb_element(); } } else { if (!m_dirty_value) { @@ -722,6 +762,9 @@ void HTMLInputElement::attribute_changed(FlyString const& name, Optional if (type_state() == TypeAttributeState::Color && m_color_well_element) MUST(m_color_well_element->style_for_bindings()->set_property(CSS::PropertyID::BackgroundColor, m_value)); + + if (type_state() == TypeAttributeState::Range && m_slider_thumb) + update_slider_thumb_element(); } } } else if (name == HTML::AttributeNames::placeholder) { @@ -1051,6 +1094,10 @@ Optional HTMLInputElement::convert_string_to_number(StringView input) co if (type_state() == TypeAttributeState::Number) return parse_floating_point_number(input); + // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-value-string-number + if (type_state() == TypeAttributeState::Range) + return parse_floating_point_number(input); + dbgln("HTMLInputElement::convert_string_to_number() not implemented for input type {}", type()); return {}; } @@ -1062,6 +1109,10 @@ String HTMLInputElement::covert_number_to_string(double input) const if (type_state() == TypeAttributeState::Number) return MUST(String::number(input)); + // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-value-number-string + if (type_state() == TypeAttributeState::Range) + return MUST(String::number(input)); + dbgln("HTMLInputElement::covert_number_to_string() not implemented for input type {}", type()); return {}; } @@ -1109,6 +1160,10 @@ double HTMLInputElement::default_step() const if (type_state() == TypeAttributeState::Number) return 1; + // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-step-default + if (type_state() == TypeAttributeState::Range) + return 1; + dbgln("HTMLInputElement::default_step() not implemented for input type {}", type()); return 0; } @@ -1116,10 +1171,14 @@ double HTMLInputElement::default_step() const // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-scale double HTMLInputElement::step_scale_factor() const { - // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):concept-input-step-default + // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):concept-input-step-scale if (type_state() == TypeAttributeState::Number) return 1; + // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-step-scale + if (type_state() == TypeAttributeState::Range) + return 1; + dbgln("HTMLInputElement::step_scale_factor() not implemented for input type {}", type()); return 0; } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h index c3b1717f4ec..135207d3570 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -1,6 +1,7 @@ /* * Copyright (c) 2018-2022, Andreas Kling * Copyright (c) 2022, Adam Hodgen + * Copyright (c) 2023, Bastiaan van der Plaat * * SPDX-License-Identifier: BSD-2-Clause */ @@ -202,6 +203,7 @@ private: void create_shadow_tree_if_needed(); void create_text_input_shadow_tree(); void create_color_input_shadow_tree(); + void create_range_input_shadow_tree(); WebIDL::ExceptionOr run_input_activation_behavior(); void set_checked_within_group(); @@ -219,6 +221,9 @@ private: JS::GCPtr m_text_node; bool m_checked { false }; + void update_slider_thumb_element(); + JS::GCPtr m_slider_thumb; + // https://html.spec.whatwg.org/multipage/input.html#dom-input-indeterminate bool m_indeterminate { false };