mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1606 from lightpanda-io/form_selectors
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled
More pseudo-seletors
This commit is contained in:
@@ -376,3 +376,67 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<form id="form-validity-test">
|
||||
<input id="vi-required-empty" type="text" required>
|
||||
<input id="vi-optional" type="text">
|
||||
<input id="vi-hidden-required" type="hidden" required>
|
||||
<fieldset id="vi-fieldset">
|
||||
<input id="vi-nested-required" type="text" required>
|
||||
<select id="vi-select-required" required>
|
||||
<option value="">Pick one</option>
|
||||
<option value="a">A</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
</form>
|
||||
<input id="vi-checkbox" type="checkbox">
|
||||
|
||||
<script id=invalidPseudo>
|
||||
{
|
||||
// Inputs with required + empty value are :invalid
|
||||
testing.expectEqual(true, document.getElementById('vi-required-empty').matches(':invalid'));
|
||||
testing.expectEqual(false, document.getElementById('vi-required-empty').matches(':valid'));
|
||||
|
||||
// Inputs without required are :valid
|
||||
testing.expectEqual(false, document.getElementById('vi-optional').matches(':invalid'));
|
||||
testing.expectEqual(true, document.getElementById('vi-optional').matches(':valid'));
|
||||
|
||||
// hidden inputs are not candidates for constraint validation
|
||||
testing.expectEqual(false, document.getElementById('vi-hidden-required').matches(':invalid'));
|
||||
testing.expectEqual(false, document.getElementById('vi-hidden-required').matches(':valid'));
|
||||
|
||||
// select with required and empty selected value is :invalid
|
||||
testing.expectEqual(true, document.getElementById('vi-select-required').matches(':invalid'));
|
||||
testing.expectEqual(false, document.getElementById('vi-select-required').matches(':valid'));
|
||||
|
||||
// fieldset containing invalid controls is :invalid
|
||||
testing.expectEqual(true, document.getElementById('vi-fieldset').matches(':invalid'));
|
||||
testing.expectEqual(false, document.getElementById('vi-fieldset').matches(':valid'));
|
||||
|
||||
// form containing invalid controls is :invalid
|
||||
testing.expectEqual(true, document.getElementById('form-validity-test').matches(':invalid'));
|
||||
testing.expectEqual(false, document.getElementById('form-validity-test').matches(':valid'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=validAfterValueSet>
|
||||
{
|
||||
// After setting a value, a required input becomes :valid
|
||||
const input = document.getElementById('vi-required-empty');
|
||||
input.value = 'hello';
|
||||
testing.expectEqual(false, input.matches(':invalid'));
|
||||
testing.expectEqual(true, input.matches(':valid'));
|
||||
input.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=indeterminatePseudo>
|
||||
{
|
||||
const cb = document.getElementById('vi-checkbox');
|
||||
testing.expectEqual(false, cb.matches(':indeterminate'));
|
||||
cb.indeterminate = true;
|
||||
testing.expectEqual(true, cb.matches(':indeterminate'));
|
||||
cb.indeterminate = false;
|
||||
testing.expectEqual(false, cb.matches(':indeterminate'));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -958,7 +958,7 @@ pub fn closest(self: *Element, selector: []const u8, page: *Page) !?*Element {
|
||||
|
||||
var current: ?*Element = self;
|
||||
while (current) |el| {
|
||||
if (try el.matches(selector, page)) {
|
||||
if (try Selector.matchesWithScope(el, selector, self, page)) {
|
||||
return el;
|
||||
}
|
||||
|
||||
|
||||
@@ -531,11 +531,45 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N
|
||||
.enabled => {
|
||||
return el.getAttributeSafe(comptime .wrap("disabled")) == null;
|
||||
},
|
||||
.indeterminate => return false,
|
||||
.indeterminate => {
|
||||
const input = el.is(Node.Element.Html.Input) orelse return false;
|
||||
return switch (input._input_type) {
|
||||
.checkbox => input.getIndeterminate(),
|
||||
else => false,
|
||||
};
|
||||
},
|
||||
|
||||
// Form validation
|
||||
.valid => return false,
|
||||
.invalid => return false,
|
||||
.valid => {
|
||||
if (el.is(Node.Element.Html.Input)) |input| {
|
||||
return switch (input._input_type) {
|
||||
.hidden, .submit, .reset, .button => false,
|
||||
else => !input.getRequired() or input.getValue().len > 0,
|
||||
};
|
||||
}
|
||||
if (el.is(Node.Element.Html.Select)) |select| {
|
||||
return !select.getRequired() or select.getValue(page).len > 0;
|
||||
}
|
||||
if (el.is(Node.Element.Html.Form) != null or el.is(Node.Element.Html.FieldSet) != null) {
|
||||
return !hasInvalidDescendant(node, page);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
.invalid => {
|
||||
if (el.is(Node.Element.Html.Input)) |input| {
|
||||
return switch (input._input_type) {
|
||||
.hidden, .submit, .reset, .button => false,
|
||||
else => input.getRequired() and input.getValue().len == 0,
|
||||
};
|
||||
}
|
||||
if (el.is(Node.Element.Html.Select)) |select| {
|
||||
return select.getRequired() and select.getValue(page).len == 0;
|
||||
}
|
||||
if (el.is(Node.Element.Html.Form) != null or el.is(Node.Element.Html.FieldSet) != null) {
|
||||
return hasInvalidDescendant(node, page);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
.required => {
|
||||
return el.getAttributeSafe(comptime .wrap("required")) != null;
|
||||
},
|
||||
@@ -684,6 +718,26 @@ fn matchesHasDescendant(el: *Node.Element, selector: Selector.Selector, scope: *
|
||||
return false;
|
||||
}
|
||||
|
||||
fn hasInvalidDescendant(parent: *Node, page: *Page) bool {
|
||||
var child = parent.firstChild();
|
||||
while (child) |c| {
|
||||
if (c.is(Node.Element)) |child_el| {
|
||||
if (child_el.is(Node.Element.Html.Input)) |input| {
|
||||
const invalid = switch (input._input_type) {
|
||||
.hidden, .submit, .reset, .button => false,
|
||||
else => input.getRequired() and input.getValue().len == 0,
|
||||
};
|
||||
if (invalid) return true;
|
||||
} else if (child_el.is(Node.Element.Html.Select)) |select| {
|
||||
if (select.getRequired() and select.getValue(page).len == 0) return true;
|
||||
}
|
||||
}
|
||||
if (hasInvalidDescendant(c, page)) return true;
|
||||
child = c.nextSibling();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fn isFirstChild(el: *Node.Element) bool {
|
||||
const node = el.asNode();
|
||||
var sibling = node.previousSibling();
|
||||
|
||||
@@ -92,6 +92,24 @@ pub fn matches(el: *Node.Element, input: []const u8, page: *Page) !bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Like matches, but allows the caller to specify a scope node distinct from el.
|
||||
// Used by closest() so that :scope always refers to the original context element.
|
||||
pub fn matchesWithScope(el: *Node.Element, input: []const u8, scope: *Node.Element, page: *Page) !bool {
|
||||
if (input.len == 0) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
const arena = page.call_arena;
|
||||
const selectors = try Parser.parseList(arena, input, page);
|
||||
|
||||
for (selectors) |selector| {
|
||||
if (List.matches(el.asNode(), selector, scope.asNode(), page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn classAttributeContains(class_attr: []const u8, class_name: []const u8) bool {
|
||||
if (class_name.len == 0 or class_name.len > class_attr.len) return false;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user