webapi: reflect HTMLElement.contentEditable

Only isContentEditable existed, so el.contentEditable = 'true' created
an expando and never set the attribute. isContentEditable is unchanged.
This commit is contained in:
Adrià Arrufat committed 2026-09-01 10:13:35 +02:00
1 parent a43db3cbd2
commit 7ef384c636
2 files changed
+42

No files matched your search

@@ -58,3 +58,22 @@
testing.expectEqual(false, el.isContentEditable);
}
</script>
<script id="reflected-attribute">
{
testing.expectEqual('true', document.getElementById('own-true').contentEditable);
testing.expectEqual('true', document.getElementById('own-empty').contentEditable);
testing.expectEqual('false', document.getElementById('own-false').contentEditable);
testing.expectEqual('plaintext-only', document.getElementById('own-plaintext').contentEditable);
testing.expectEqual('inherit', document.getElementById('plain').contentEditable);
testing.expectEqual('inherit', document.getElementById('child-of-true').contentEditable);
const el = document.getElementById('plain');
el.contentEditable = 'TRUE';
testing.expectEqual('true', el.getAttribute('contenteditable'));
testing.expectEqual(false, el.isContentEditable);
el.contentEditable = 'inherit';
testing.expectEqual(false, el.hasAttribute('contenteditable'));
testing.expectError('SyntaxError', () => { el.contentEditable = 'maybe'; });
}
</script>
+23
View File
@@ -611,6 +611,28 @@ pub fn setTitle(self: *HtmlElement, value: []const u8, frame: *Frame) !void {
//
// "contenteditable" is 15 bytes — past the comptime SSO limit — so the
// String wrap runs at runtime, mirroring the pattern in interactive.zig.
/// Reflects the attribute only; `isContentEditable` stays false regardless.
pub fn getContentEditable(self: *HtmlElement) []const u8 {
const raw = self.asElement().getAttributeSafe(.wrap("contenteditable")) orelse return "inherit";
if (raw.len == 0 or std.ascii.eqlIgnoreCase(raw, "true")) return "true";
if (std.ascii.eqlIgnoreCase(raw, "false")) return "false";
if (std.ascii.eqlIgnoreCase(raw, "plaintext-only")) return "plaintext-only";
return "inherit";
}
pub fn setContentEditable(self: *HtmlElement, value: []const u8, frame: *Frame) !void {
const el = self.asElement();
if (std.ascii.eqlIgnoreCase(value, "inherit")) {
return el.removeAttribute(.wrap("contenteditable"), frame);
}
inline for (.{ "true", "false", "plaintext-only" }) |keyword| {
if (std.ascii.eqlIgnoreCase(value, keyword)) {
return el.setAttributeSafe(.wrap("contenteditable"), .wrap(keyword), frame);
}
}
return error.SyntaxError;
}
pub fn getIsContentEditable(self: *HtmlElement) bool {
var current: ?*Element = self.asElement();
while (current) |el| : (current = el.parentElement()) {
@@ -1839,6 +1861,7 @@ pub const JsApi = struct {
pub const showPopover = bridge.function(HtmlElement.showPopover, .{});
pub const hidePopover = bridge.function(HtmlElement.hidePopover, .{});
pub const togglePopover = bridge.function(HtmlElement.togglePopover, .{});
pub const contentEditable = bridge.accessor(HtmlElement.getContentEditable, HtmlElement.setContentEditable, .{ .ce_reactions = true });
pub const isContentEditable = bridge.accessor(HtmlElement.getIsContentEditable, null, .{});
pub const lang = bridge.accessor(HtmlElement.getLang, HtmlElement.setLang, .{ .ce_reactions = true });
pub const nonce = bridge.accessor(HtmlElement.getNonce, HtmlElement.setNonce, .{ .ce_reactions = true });