mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-28 15:40:04 +00:00
webapi.Element: centralize disabled state logic
This commit is contained in:
@@ -179,15 +179,13 @@ fn collectFormFields(
|
|||||||
page: *Page,
|
page: *Page,
|
||||||
) ![]FormField {
|
) ![]FormField {
|
||||||
var fields: std.ArrayList(FormField) = .empty;
|
var fields: std.ArrayList(FormField) = .empty;
|
||||||
const form_node = form.asNode();
|
|
||||||
|
|
||||||
var elements = try form.getElements(page);
|
var elements = try form.getElements(page);
|
||||||
var it = try elements.iterator();
|
var it = try elements.iterator();
|
||||||
while (it.next()) |el| {
|
while (it.next()) |el| {
|
||||||
const node = el.asNode();
|
const node = el.asNode();
|
||||||
|
|
||||||
const is_disabled = el.getAttributeSafe(comptime .wrap("disabled")) != null or
|
const is_disabled = el.isDisabled();
|
||||||
isDisabledByFieldset(el, form_node);
|
|
||||||
|
|
||||||
if (el.is(Element.Html.Input)) |input| {
|
if (el.is(Element.Html.Input)) |input| {
|
||||||
if (input._input_type == .hidden) continue;
|
if (input._input_type == .hidden) continue;
|
||||||
@@ -267,38 +265,6 @@ fn collectSelectOptions(
|
|||||||
return options.items;
|
return options.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if `element` is disabled by an ancestor <fieldset disabled>,
|
|
||||||
/// stopping at the form boundary.
|
|
||||||
/// Per spec, elements inside the first <legend> child of a disabled fieldset
|
|
||||||
/// are NOT disabled by that fieldset.
|
|
||||||
fn isDisabledByFieldset(element: *Element, form_node: *Node) bool {
|
|
||||||
const element_node = element.asNode();
|
|
||||||
var current: ?*Node = element_node._parent;
|
|
||||||
while (current) |node| {
|
|
||||||
if (node == form_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = node._parent;
|
|
||||||
const el = node.is(Element) orelse continue;
|
|
||||||
|
|
||||||
if (el.getTag() == .fieldset and el.getAttributeSafe(comptime .wrap("disabled")) != null) {
|
|
||||||
var child = el.firstElementChild();
|
|
||||||
while (child) |c| {
|
|
||||||
if (c.getTag() == .legend) {
|
|
||||||
if (c.asNode().contains(element_node)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
child = c.nextElementSibling();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const testing = @import("../testing.zig");
|
const testing = @import("../testing.zig");
|
||||||
|
|
||||||
fn testForms(html: []const u8) ![]FormInfo {
|
fn testForms(html: []const u8) ![]FormInfo {
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ pub fn collectInteractiveElements(
|
|||||||
.name = try getAccessibleName(el, arena),
|
.name = try getAccessibleName(el, arena),
|
||||||
.interactivity_type = itype,
|
.interactivity_type = itype,
|
||||||
.listener_types = listener_types,
|
.listener_types = listener_types,
|
||||||
.disabled = isDisabled(el),
|
.disabled = el.isDisabled(),
|
||||||
.tab_index = html_el.getTabIndex(),
|
.tab_index = html_el.getTabIndex(),
|
||||||
.id = el.getAttributeSafe(comptime .wrap("id")),
|
.id = el.getAttributeSafe(comptime .wrap("id")),
|
||||||
.class = el.getAttributeSafe(comptime .wrap("class")),
|
.class = el.getAttributeSafe(comptime .wrap("class")),
|
||||||
@@ -412,36 +412,6 @@ fn getTextContent(node: *Node, arena: Allocator) !?[]const u8 {
|
|||||||
// strip out trailing space
|
// strip out trailing space
|
||||||
return arr.items[0 .. arr.items.len - 1];
|
return arr.items[0 .. arr.items.len - 1];
|
||||||
}
|
}
|
||||||
fn isDisabled(el: *Element) bool {
|
|
||||||
if (el.getAttributeSafe(comptime .wrap("disabled")) != null) return true;
|
|
||||||
return isDisabledByFieldset(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if an element is disabled by an ancestor <fieldset disabled>.
|
|
||||||
/// Per spec, elements inside the first <legend> child of a disabled fieldset
|
|
||||||
/// are NOT disabled by that fieldset.
|
|
||||||
fn isDisabledByFieldset(el: *Element) bool {
|
|
||||||
const element_node = el.asNode();
|
|
||||||
var current: ?*Node = element_node._parent;
|
|
||||||
while (current) |node| {
|
|
||||||
current = node._parent;
|
|
||||||
const ancestor = node.is(Element) orelse continue;
|
|
||||||
|
|
||||||
if (ancestor.getTag() == .fieldset and ancestor.getAttributeSafe(comptime .wrap("disabled")) != null) {
|
|
||||||
// Check if element is inside the first <legend> child of this fieldset
|
|
||||||
var child = ancestor.firstElementChild();
|
|
||||||
while (child) |c| {
|
|
||||||
if (c.getTag() == .legend) {
|
|
||||||
if (c.asNode().contains(element_node)) return false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
child = c.nextElementSibling();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getInputType(el: *Element) ?[]const u8 {
|
fn getInputType(el: *Element) ?[]const u8 {
|
||||||
if (el.is(Element.Html.Input)) |input| {
|
if (el.is(Element.Html.Input)) |input| {
|
||||||
|
|||||||
@@ -573,6 +573,32 @@ pub fn hasAttributeSafe(self: *const Element, name: String) bool {
|
|||||||
return attributes.hasSafe(name);
|
return attributes.hasSafe(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn isDisabled(self: *Element) bool {
|
||||||
|
if (self.getAttributeSafe(comptime .wrap("disabled")) != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const element_node = self.asNode();
|
||||||
|
var current: ?*Node = element_node._parent;
|
||||||
|
while (current) |node| {
|
||||||
|
current = node._parent;
|
||||||
|
const ancestor = node.is(Element) orelse continue;
|
||||||
|
|
||||||
|
if (ancestor.getTag() == .fieldset and ancestor.getAttributeSafe(comptime .wrap("disabled")) != null) {
|
||||||
|
var child = ancestor.firstElementChild();
|
||||||
|
while (child) |c| {
|
||||||
|
if (c.getTag() == .legend) {
|
||||||
|
if (c.asNode().contains(element_node)) return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
child = c.nextElementSibling();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hasAttributes(self: *const Element) bool {
|
pub fn hasAttributes(self: *const Element) bool {
|
||||||
const attributes = self._attributes orelse return false;
|
const attributes = self._attributes orelse return false;
|
||||||
return attributes.isEmpty() == false;
|
return attributes.isEmpty() == false;
|
||||||
|
|||||||
@@ -125,15 +125,10 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, page: *Pa
|
|||||||
var list: KeyValueList = .empty;
|
var list: KeyValueList = .empty;
|
||||||
const form = form_ orelse return list;
|
const form = form_ orelse return list;
|
||||||
|
|
||||||
const form_node = form.asNode();
|
|
||||||
|
|
||||||
var elements = try form.getElements(page);
|
var elements = try form.getElements(page);
|
||||||
var it = try elements.iterator();
|
var it = try elements.iterator();
|
||||||
while (it.next()) |element| {
|
while (it.next()) |element| {
|
||||||
if (element.getAttributeSafe(comptime .wrap("disabled")) != null) {
|
if (element.isDisabled()) {
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (isDisabledByFieldset(element, form_node)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,41 +197,6 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, page: *Pa
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if `element` is disabled by an ancestor <fieldset disabled>,
|
|
||||||
// stopping the upward walk when the form node is reached.
|
|
||||||
// Per spec, elements inside the first <legend> child of a disabled fieldset
|
|
||||||
// are NOT disabled by that fieldset.
|
|
||||||
fn isDisabledByFieldset(element: *Element, form_node: *Node) bool {
|
|
||||||
const element_node = element.asNode();
|
|
||||||
var current: ?*Node = element_node._parent;
|
|
||||||
while (current) |node| {
|
|
||||||
// Stop at the form boundary (common case optimisation)
|
|
||||||
if (node == form_node) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
current = node._parent;
|
|
||||||
const el = node.is(Element) orelse continue;
|
|
||||||
|
|
||||||
if (el.getTag() == .fieldset and el.getAttributeSafe(comptime .wrap("disabled")) != null) {
|
|
||||||
// Check if `element` is inside the first <legend> child of this fieldset
|
|
||||||
var child = el.firstElementChild();
|
|
||||||
while (child) |c| {
|
|
||||||
if (c.getTag() == .legend) {
|
|
||||||
// Found the first legend; exempt if element is a descendant
|
|
||||||
if (c.asNode().contains(element_node)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
child = c.nextElementSibling();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
pub const bridge = js.Bridge(FormData);
|
pub const bridge = js.Bridge(FormData);
|
||||||
|
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ pub const Writer = struct {
|
|||||||
},
|
},
|
||||||
.input => {
|
.input => {
|
||||||
const input = el.as(DOMNode.Element.Html.Input);
|
const input = el.as(DOMNode.Element.Html.Input);
|
||||||
const is_disabled = el.hasAttributeSafe(comptime .wrap("disabled"));
|
const is_disabled = el.isDisabled();
|
||||||
|
|
||||||
switch (input._input_type) {
|
switch (input._input_type) {
|
||||||
.text, .email, .tel, .url, .search, .password, .number => {
|
.text, .email, .tel, .url, .search, .password, .number => {
|
||||||
@@ -332,7 +332,7 @@ pub const Writer = struct {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.textarea => {
|
.textarea => {
|
||||||
const is_disabled = el.hasAttributeSafe(comptime .wrap("disabled"));
|
const is_disabled = el.isDisabled();
|
||||||
|
|
||||||
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
|
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
|
||||||
if (!is_disabled) {
|
if (!is_disabled) {
|
||||||
@@ -347,7 +347,7 @@ pub const Writer = struct {
|
|||||||
try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe(comptime .wrap("required")) } }, w);
|
try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe(comptime .wrap("required")) } }, w);
|
||||||
},
|
},
|
||||||
.select => {
|
.select => {
|
||||||
const is_disabled = el.hasAttributeSafe(comptime .wrap("disabled"));
|
const is_disabled = el.isDisabled();
|
||||||
|
|
||||||
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
|
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
|
||||||
if (!is_disabled) {
|
if (!is_disabled) {
|
||||||
@@ -391,7 +391,7 @@ pub const Writer = struct {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.button => {
|
.button => {
|
||||||
const is_disabled = el.hasAttributeSafe(comptime .wrap("disabled"));
|
const is_disabled = el.isDisabled();
|
||||||
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
|
try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w);
|
||||||
if (!is_disabled) {
|
if (!is_disabled) {
|
||||||
try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w);
|
try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w);
|
||||||
|
|||||||
Reference in New Issue
Block a user