@@ -31,6 +31,8 @@ const NodeList = @import("nodelist.zig").NodeList;
3131const HTMLElem = @import ("../html/elements.zig" );
3232pub 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
3537pub 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