add selection api to HTMLTextAreaElement

This commit is contained in:
Muki Kiboigo
2026-01-19 18:37:52 -08:00
parent d7015fa3b6
commit d5bfe74e1a

View File

@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const js = @import("../../../js/js.zig");
const Page = @import("../../../Page.zig");
@@ -23,12 +24,17 @@ const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const HtmlElement = @import("../Html.zig");
const Form = @import("Form.zig");
const Selection = @import("../../Selection.zig");
const TextArea = @This();
_proto: *HtmlElement,
_value: ?[]const u8 = null,
_selection_start: u32 = 0,
_selection_end: u32 = 0,
_selection_direction: Selection.SelectionDirection = .none,
pub fn asElement(self: *TextArea) *Element {
return self._proto._proto;
}
@@ -109,6 +115,108 @@ pub fn setRequired(self: *TextArea, required: bool, page: *Page) !void {
}
}
pub fn select(self: *TextArea) !void {
const len = if (self._value) |v| @as(u32, @intCast(v.len)) else 0;
try self.setSelectionRange(0, len, null);
}
const HowSelected = union(enum) { partial: struct { u32, u32 }, full, none };
fn howSelected(self: *const TextArea) HowSelected {
const value = self._value orelse return .none;
if (self._selection_start == self._selection_end) return .none;
if (self._selection_start == 0 and self._selection_end == value.len) return .full;
return .{ .partial = .{ self._selection_start, self._selection_end } };
}
pub fn innerInsert(self: *TextArea, str: []const u8, page: *Page) !void {
const arena = page.arena;
switch (self.howSelected()) {
.full => {
// if the text area is fully selected, replace the content.
const new_value = try arena.dupe(u8, str);
try self.setValue(new_value, page);
self._selection_start = 1;
self._selection_end = 1;
self._selection_direction = .none;
},
.partial => |range| {
// if the text area is partially selected, replace the selected content.
const current_value = self.getValue();
const before = current_value[0..range[0]];
const remaining = current_value[range[1]..];
const new_value = try std.mem.concat(
arena,
u8,
&.{ before, str, remaining },
);
try self.setValue(new_value, page);
const new_pos = range[0] + str.len;
self._selection_start = @intCast(new_pos);
self._selection_end = @intCast(new_pos);
self._selection_direction = .none;
},
.none => {
// if the text area is not selected, just insert at cursor.
const current_value = self.getValue();
const new_value = try std.mem.concat(arena, u8, &.{ current_value, str });
try self.setValue(new_value, page);
},
}
}
pub fn getSelectionDirection(self: *const TextArea) []const u8 {
return @tagName(self._selection_direction);
}
pub fn getSelectionStart(self: *const TextArea) u32 {
return self._selection_start;
}
pub fn setSelectionStart(self: *TextArea, value: u32) void {
self._selection_start = value;
}
pub fn getSelectionEnd(self: *const TextArea) u32 {
return self._selection_end;
}
pub fn setSelectionEnd(self: *TextArea, value: u32) void {
self._selection_end = value;
}
pub fn setSelectionRange(self: *TextArea, selection_start: u32, selection_end: u32, selection_dir: ?[]const u8) !void {
const direction = blk: {
if (selection_dir) |sd| {
break :blk std.meta.stringToEnum(Selection.SelectionDirection, sd) orelse .none;
} else break :blk .none;
};
const value = self._value orelse {
self._selection_start = 0;
self._selection_end = 0;
self._selection_direction = .none;
return;
};
const len_u32: u32 = @intCast(value.len);
var start: u32 = if (selection_start > len_u32) len_u32 else selection_start;
const end: u32 = if (selection_end > len_u32) len_u32 else selection_end;
// If end is less than start, both are equal to end.
if (end < start) {
start = end;
}
self._selection_direction = direction;
self._selection_start = start;
self._selection_end = end;
}
pub fn getForm(self: *TextArea, page: *Page) ?*Form {
const element = self.asElement();