From 8dd4567828e31697e5bed12c852edbed2f470661 Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 3 Mar 2026 15:12:02 +0300 Subject: [PATCH 1/2] `HTMLDetailsElement`: implement `HTMLDetailsElement` --- src/browser/Page.zig | 4 +- src/browser/js/bridge.zig | 1 + src/browser/webapi/Element.zig | 3 + .../webapi/css/CSSStyleDeclaration.zig | 2 +- src/browser/webapi/element/Html.zig | 2 + src/browser/webapi/element/html/Details.zig | 58 +++++++++++++++++++ 6 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 src/browser/webapi/element/html/Details.zig diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 6e80c2de..a4565c7b 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -1942,10 +1942,10 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const .{ ._proto = undefined, ._tag_name = String.init(undefined, "article", .{}) catch unreachable, ._tag = .article }, ), asUint("details") => return self.createHtmlElementT( - Element.Html.Generic, + Element.Html.Details, namespace, attribute_iterator, - .{ ._proto = undefined, ._tag_name = String.init(undefined, "details", .{}) catch unreachable, ._tag = .details }, + .{ ._proto = undefined }, ), asUint("summary") => return self.createHtmlElementT( Element.Html.Generic, diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 8d1daba2..af4df968 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -767,6 +767,7 @@ pub const JsApis = flattenTypes(&.{ @import("../webapi/element/html/Custom.zig"), @import("../webapi/element/html/Data.zig"), @import("../webapi/element/html/DataList.zig"), + @import("../webapi/element/html/Details.zig"), @import("../webapi/element/html/Dialog.zig"), @import("../webapi/element/html/Directory.zig"), @import("../webapi/element/html/DList.zig"), diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 9424e355..b922eb4c 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -209,6 +209,7 @@ pub fn getTagNameLower(self: *const Element) []const u8 { .custom => |e| e._tag_name.str(), .data => "data", .datalist => "datalist", + .details => "details", .dialog => "dialog", .directory => "dir", .div => "div", @@ -287,6 +288,7 @@ pub fn getTagNameSpec(self: *const Element, buf: []u8) []const u8 { .custom => |e| upperTagName(&e._tag_name, buf), .data => "DATA", .datalist => "DATALIST", + .details => "DETAILS", .dialog => "DIALOG", .directory => "DIR", .div => "DIV", @@ -1385,6 +1387,7 @@ pub fn getTag(self: *const Element) Tag { .custom => .custom, .data => .data, .datalist => .datalist, + .details => .details, .dialog => .dialog, .directory => .directory, .iframe => .iframe, diff --git a/src/browser/webapi/css/CSSStyleDeclaration.zig b/src/browser/webapi/css/CSSStyleDeclaration.zig index 5f61c607..a8eb92e4 100644 --- a/src/browser/webapi/css/CSSStyleDeclaration.zig +++ b/src/browser/webapi/css/CSSStyleDeclaration.zig @@ -255,7 +255,7 @@ fn getDefaultDisplay(element: *const Element) []const u8 { .html => |html| { return switch (html._type) { .anchor, .br, .span, .label, .time, .font, .mod, .quote => "inline", - .body, .div, .dl, .p, .heading, .form, .button, .canvas, .dialog, .embed, .head, .html, .hr, .iframe, .img, .input, .li, .link, .meta, .ol, .option, .script, .select, .slot, .style, .template, .textarea, .title, .ul, .media, .area, .base, .datalist, .directory, .fieldset, .legend, .map, .meter, .object, .optgroup, .output, .param, .picture, .pre, .progress, .source, .table, .table_caption, .table_cell, .table_col, .table_row, .table_section, .track => "block", + .body, .div, .dl, .p, .heading, .form, .button, .canvas, .details, .dialog, .embed, .head, .html, .hr, .iframe, .img, .input, .li, .link, .meta, .ol, .option, .script, .select, .slot, .style, .template, .textarea, .title, .ul, .media, .area, .base, .datalist, .directory, .fieldset, .legend, .map, .meter, .object, .optgroup, .output, .param, .picture, .pre, .progress, .source, .table, .table_caption, .table_cell, .table_col, .table_row, .table_section, .track => "block", .generic, .custom, .unknown, .data => blk: { const tag = element.getTagNameLower(); if (isInlineTag(tag)) break :blk "inline"; diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index a4e6484d..ea962c9e 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -39,6 +39,7 @@ pub const Canvas = @import("html/Canvas.zig"); pub const Custom = @import("html/Custom.zig"); pub const Data = @import("html/Data.zig"); pub const DataList = @import("html/DataList.zig"); +pub const Details = @import("html/Details.zig"); pub const Dialog = @import("html/Dialog.zig"); pub const Directory = @import("html/Directory.zig"); pub const Div = @import("html/Div.zig"); @@ -119,6 +120,7 @@ pub const Type = union(enum) { custom: *Custom, data: *Data, datalist: *DataList, + details: *Details, dialog: *Dialog, directory: *Directory, div: *Div, diff --git a/src/browser/webapi/element/html/Details.zig b/src/browser/webapi/element/html/Details.zig new file mode 100644 index 00000000..1cc00ae7 --- /dev/null +++ b/src/browser/webapi/element/html/Details.zig @@ -0,0 +1,58 @@ +const js = @import("../../../js/js.zig"); +const Page = @import("../../../Page.zig"); + +const Node = @import("../../Node.zig"); +const Element = @import("../../Element.zig"); +const HtmlElement = @import("../Html.zig"); + +const Details = @This(); + +_proto: *HtmlElement, + +pub fn asElement(self: *Details) *Element { + return self._proto._proto; +} +pub fn asConstElement(self: *const Details) *const Element { + return self._proto._proto; +} +pub fn asNode(self: *Details) *Node { + return self.asElement().asNode(); +} + +pub fn getOpen(self: *const Details) bool { + return self.asConstElement().getAttributeSafe(comptime .wrap("open")) != null; +} + +pub fn setOpen(self: *Details, open: bool, page: *Page) !void { + if (open) { + try self.asElement().setAttributeSafe(comptime .wrap("open"), .wrap(""), page); + } else { + try self.asElement().removeAttribute(comptime .wrap("open"), page); + } +} + +pub fn getName(self: *const Details) []const u8 { + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; +} + +pub fn setName(self: *Details, value: []const u8, page: *Page) !void { + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(value), page); +} + +pub const JsApi = struct { + pub const bridge = js.Bridge(Details); + + pub const Meta = struct { + pub const name = "HTMLDetailsElement"; + pub const prototype_chain = bridge.prototypeChain(); + pub var class_id: bridge.ClassId = undefined; + }; + + pub const open = bridge.accessor(Details.getOpen, Details.setOpen, .{}); + pub const name = bridge.accessor(Details.getName, Details.setName, .{}); +}; + +const testing = @import("../../../../testing.zig"); +test "WebApi: HTML.Details" { + try testing.htmlRunner("element/html/details.html", .{}); +} From 50896bdc9d0ecb9cbb1cc768402d8e1c5f7c831a Mon Sep 17 00:00:00 2001 From: Halil Durak Date: Tue, 3 Mar 2026 15:12:12 +0300 Subject: [PATCH 2/2] `HTMLDetailsElement`: add tests --- src/browser/tests/element/html/details.html | 63 +++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/browser/tests/element/html/details.html diff --git a/src/browser/tests/element/html/details.html b/src/browser/tests/element/html/details.html new file mode 100644 index 00000000..09d82cea --- /dev/null +++ b/src/browser/tests/element/html/details.html @@ -0,0 +1,63 @@ + + + + +
+ Summary + Content +
+
+ Open Summary + Content +
+ + + + + + + + + + + +