Skip to content

Commit 35bff8c

Browse files
authored
Merge pull request #631 from lightpanda-io/element_closest
Element closest
2 parents 7bb6506 + 0998ae7 commit 35bff8c

File tree

1 file changed

+36
-0
lines changed

1 file changed

+36
-0
lines changed

src/browser/dom/element.zig

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const NodeList = @import("nodelist.zig").NodeList;
3131
const HTMLElem = @import("../html/elements.zig");
3232
pub const Union = @import("../html/elements.zig").Union;
3333

34+
const log = std.log.scoped(.element);
35+
3436
// WEB IDL https://dom.spec.whatwg.org/#element
3537
pub const Element = struct {
3638
pub const Self = parser.Element;
@@ -135,6 +137,26 @@ pub const Element = struct {
135137
}
136138
}
137139

140+
// The closest() method of the Element interface traverses the element and its parents (heading toward the document root) until it finds a node that matches the specified CSS selector.
141+
// Returns the closest ancestor Element or itself, which matches the selectors. If there are no such element, null.
142+
pub fn _closest(self: *parser.Element, selector: []const u8, state: *SessionState) !?*parser.Element {
143+
const cssParse = @import("../css/css.zig").parse;
144+
const CssNodeWrap = @import("../css/libdom.zig").Node;
145+
const select = try cssParse(state.call_arena, selector, .{});
146+
147+
var current: CssNodeWrap = .{ .node = parser.elementToNode(self) };
148+
while (true) {
149+
if (try select.match(current)) {
150+
if (!current.isElement()) {
151+
log.err("closest: is not an element: {s}", .{try current.tag()});
152+
return null;
153+
}
154+
return parser.nodeToElement(current.node);
155+
}
156+
current = try current.parent() orelse return null;
157+
}
158+
}
159+
138160
pub fn _hasAttributes(self: *parser.Element) !bool {
139161
return try parser.nodeHasAttributes(parser.elementToNode(self));
140162
}
@@ -401,6 +423,20 @@ test "Browser.DOM.Element" {
401423
.{ "cl.length", "2" },
402424
}, .{});
403425

426+
try runner.testCases(&.{
427+
.{ "const el2 = document.createElement('div');", "undefined" },
428+
.{ "el2.id = 'closest'; el2.className = 'ok';", "ok" },
429+
.{ "el2.closest('#closest')", "[object HTMLDivElement]" },
430+
.{ "el2.closest('.ok')", "[object HTMLDivElement]" },
431+
.{ "el2.closest('#9000')", "null" },
432+
.{ "el2.closest('.notok')", "null" },
433+
434+
.{ "const sp = document.createElement('span');", "undefined" },
435+
.{ "el2.appendChild(sp);", "[object HTMLSpanElement]" },
436+
.{ "sp.closest('#closest')", "[object HTMLDivElement]" },
437+
.{ "sp.closest('#9000')", "null" },
438+
}, .{});
439+
404440
try runner.testCases(&.{
405441
.{ "let a = document.getElementById('content')", "undefined" },
406442
.{ "a.hasAttributes()", "true" },

0 commit comments

Comments
 (0)