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 };