Document.activeElement, focus and blur

This commit is contained in:
Karl Seguin
2025-11-13 20:37:00 +08:00
parent 7a5cade510
commit 6cf01631ad
3 changed files with 131 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<body>
<input id="input1" type="text">
<input id="input2" type="text">
<button id="btn1">Button</button>
</body>
<script id="activeElement_default">
{
const body = document.querySelector('body');
body.focus();
testing.expectEqual('BODY', document.activeElement.tagName);
}
</script>
<script id="focus_method">
{
const input1 = $('#input1');
input1.focus();
testing.expectEqual(input1, document.activeElement);
}
</script>
<script id="focus_events">
{
const input1 = $('#input1');
const input2 = $('#input2');
// Explicitly blur any focused element
if (document.activeElement) {
document.activeElement.blur();
}
let focusCount = 0;
let blurCount = 0;
input1.addEventListener('focus', () => focusCount++);
input1.addEventListener('blur', () => blurCount++);
input2.addEventListener('focus', () => focusCount++);
input1.focus();
testing.expectEqual(1, focusCount);
testing.expectEqual(0, blurCount);
input2.focus();
testing.expectEqual(2, focusCount);
testing.expectEqual(1, blurCount);
}
</script>
<script id="blur_method">
{
const btn = $('#btn1');
btn.focus();
testing.expectEqual(btn, document.activeElement);
btn.blur();
testing.expectEqual('BODY', document.activeElement.tagName);
}
</script>
<script id="focus_already_focused">
{
const input1 = $('#input1');
// Explicitly blur any focused element
if (document.activeElement) {
document.activeElement.blur();
}
let focusCount = 0;
input1.addEventListener('focus', () => focusCount++);
input1.focus();
testing.expectEqual(1, focusCount);
input1.focus();
testing.expectEqual(1, focusCount);
}
</script>

View File

@@ -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, .{});

View File

@@ -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 {