diff --git a/src/browser/webapi/element/html/TextArea.zig b/src/browser/webapi/element/html/TextArea.zig index e9a1dcb1..b0b0ae1c 100644 --- a/src/browser/webapi/element/html/TextArea.zig +++ b/src/browser/webapi/element/html/TextArea.zig @@ -16,6 +16,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +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();