diff --git a/src/browser/tests/document/focus.html b/src/browser/tests/document/focus.html new file mode 100644 index 00000000..5b7b7c07 --- /dev/null +++ b/src/browser/tests/document/focus.html @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index bbdd267c..5d58d3ab 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -24,6 +24,7 @@ _location: ?*Location = null, _ready_state: ReadyState = .loading, _current_script: ?*Element.Html.Script = null, _elements_by_id: std.StringHashMapUnmanaged(*Element) = .empty, +_active_element: ?*Element = null, pub const Type = union(enum) { generic, @@ -155,6 +156,22 @@ pub fn getReadyState(self: *const Document) []const u8 { return @tagName(self._ready_state); } +pub fn getActiveElement(self: *Document) ?*Element { + if (self._active_element) |el| { + return el; + } + + // Default to body if it exists + if (self.is(HTMLDocument)) |html_doc| { + if (html_doc.getBody()) |body| { + return body.asElement(); + } + } + + // Fallback to document element + return self.getDocumentElement(); +} + const ReadyState = enum { loading, interactive, @@ -182,6 +199,7 @@ pub const JsApi = struct { pub const documentElement = bridge.accessor(Document.getDocumentElement, null, .{}); pub const readyState = bridge.accessor(Document.getReadyState, null, .{}); pub const implementation = bridge.accessor(Document.getImplementation, null, .{}); + pub const activeElement = bridge.accessor(Document.getActiveElement, null, .{}); pub const createElement = bridge.function(Document.createElement, .{}); pub const createElementNS = bridge.function(Document.createElementNS, .{}); diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index c29ad4c7..1c55e6f3 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -82,6 +82,10 @@ pub fn asNode(self: *Element) *Node { return self._proto; } +pub fn asEventTarget(self: *Element) *@import("EventTarget.zig") { + return self._proto.asEventTarget(); +} + pub fn asConstNode(self: *const Element) *const Node { return self._proto; } @@ -390,6 +394,32 @@ pub fn remove(self: *Element, page: *Page) void { page.removeNode(parent, node, .{ .will_be_reconnected = false }); } +pub fn focus(self: *Element, page: *Page) !void { + const Event = @import("Event.zig"); + + if (page.document._active_element) |old| { + if (old == self) return; + + const blur_event = try Event.init("blur", null, page); + try page._event_manager.dispatch(old.asEventTarget(), blur_event); + } + + page.document._active_element = self; + + const focus_event = try Event.init("focus", null, page); + try page._event_manager.dispatch(self.asEventTarget(), focus_event); +} + +pub fn blur(self: *Element, page: *Page) !void { + if (page.document._active_element != self) return; + + page.document._active_element = null; + + const Event = @import("Event.zig"); + const blur_event = try Event.init("blur", null, page); + try page._event_manager.dispatch(self.asEventTarget(), blur_event); +} + pub fn getChildren(self: *Element, page: *Page) !collections.NodeLive(.child_elements) { return collections.NodeLive(.child_elements).init(null, self.asNode(), {}, page); } @@ -831,6 +861,8 @@ pub const JsApi = struct { pub const getElementsByTagName = bridge.function(Element.getElementsByTagName, .{}); pub const getElementsByClassName = bridge.function(Element.getElementsByClassName, .{}); pub const children = bridge.accessor(Element.getChildren, null, .{}); + pub const focus = bridge.function(Element.focus, .{}); + pub const blur = bridge.function(Element.blur, .{}); }; pub const Build = struct {