From 505e0799daf839d9cebc37b1a7140b6d14ac68c6 Mon Sep 17 00:00:00 2001 From: Muki Kiboigo Date: Wed, 14 Jan 2026 07:27:13 -0800 Subject: [PATCH] add remaining functions to Selection --- src/browser/tests/selection.html | 162 +++++++++++++++++++++++++++++++ src/browser/webapi/Selection.zig | 150 ++++++++++++++++++++++++---- 2 files changed, 295 insertions(+), 17 deletions(-) diff --git a/src/browser/tests/selection.html b/src/browser/tests/selection.html index ceae479f..9fd614f0 100644 --- a/src/browser/tests/selection.html +++ b/src/browser/tests/selection.html @@ -406,3 +406,165 @@ testing.expectEqual("Range", sel.type); } + + + + + + + + diff --git a/src/browser/webapi/Selection.zig b/src/browser/webapi/Selection.zig index 164a51a2..593ba4b9 100644 --- a/src/browser/webapi/Selection.zig +++ b/src/browser/webapi/Selection.zig @@ -108,9 +108,15 @@ pub fn removeRange(self: *Selection, range: *Range) void { } } -pub fn removeAllRanges(self: *Selection) void { +fn removeAllRangesInner(self: *Selection, reset_direction: bool) void { self._ranges.clearRetainingCapacity(); - self._direction = .none; + if (reset_direction) { + self._direction = .none; + } +} + +pub fn removeAllRanges(self: *Selection) void { + self.removeAllRangesInner(true); } pub fn collapseToEnd(self: *Selection, page: *Page) !void { @@ -124,9 +130,8 @@ pub fn collapseToEnd(self: *Selection, page: *Page) !void { try range.setStart(last_node, last_offset); try range.setEnd(last_node, last_offset); - self.removeAllRanges(); + self.removeAllRangesInner(true); try self._ranges.append(page.arena, range); - self._direction = .none; } pub fn collapseToStart(self: *Selection, page: *Page) !void { @@ -140,7 +145,7 @@ pub fn collapseToStart(self: *Selection, page: *Page) !void { try range.setStart(first_node, first_offset); try range.setStart(first_node, first_offset); - self.removeAllRanges(); + self.removeAllRangesInner(true); try self._ranges.append(page.arena, range); self._direction = .none; } @@ -209,8 +214,6 @@ pub fn extend(self: *Selection, node: *Node, _offset: ?u32) !void { } } -// TODO: getComposedRanges - pub fn getRangeAt(self: *Selection, index: u32) !*Range { if (index >= self.getRangeCount()) { return error.IndexSizeError; @@ -219,15 +222,129 @@ pub fn getRangeAt(self: *Selection, index: u32) !*Range { return self._ranges.items[index]; } -// TODO: modify +const ModifyAlter = enum { + move, + extend, -// TODO: selectAllChildren + pub fn fromString(str: []const u8) ?ModifyAlter { + return std.meta.stringToEnum(ModifyAlter, str); + } +}; -// TODO: setBaseAndExtent +const ModifyDirection = enum { + forward, + backward, + left, + right, + + pub fn fromString(str: []const u8) ?ModifyDirection { + return std.meta.stringToEnum(ModifyDirection, str); + } +}; + +const ModifyGranularity = enum { + character, + word, + line, + paragraph, + lineboundary, + // Firefox doesn't implement: + // - sentence + // - paragraph + // - sentenceboundary + // - paragraphboundary + // - documentboundary + // so we won't either for now. + + pub fn fromString(str: []const u8) ?ModifyGranularity { + return std.meta.stringToEnum(ModifyGranularity, str); + } +}; + +pub fn modify( + self: *Selection, + alter_str: []const u8, + direction_str: []const u8, + granularity_str: []const u8, +) !void { + const alter = ModifyAlter.fromString(alter_str) orelse return error.InvalidParams; + const direction = ModifyDirection.fromString(direction_str) orelse return error.InvalidParams; + const granularity = ModifyGranularity.fromString(granularity_str) orelse return error.InvalidParams; + + if (self._ranges.items.len == 0) return; + + log.warn(.not_implemented, "Selection.modify", .{ + .alter = alter, + .direction = direction, + .granularity = granularity, + }); +} + +pub fn selectAllChildren(self: *Selection, parent: *Node, page: *Page) !void { + if (parent._type == .document_type) { + return error.InvalidNodeType; + } + + const range = try Range.init(page); + try range.setStart(parent, 0); + + const child_count = parent.getLength(); + try range.setEnd(parent, @intCast(child_count)); + + self.removeAllRangesInner(true); + try self._ranges.append(page.arena, range); +} + +pub fn setBaseAndExtent( + self: *Selection, + anchor_node: *Node, + anchor_offset: u32, + focus_node: *Node, + focus_offset: u32, + page: *Page, +) !void { + if (anchor_offset > anchor_node.getLength()) { + return error.IndexSizeError; + } + + if (focus_offset > focus_node.getLength()) { + return error.IndexSizeError; + } + + const cmp = AbstractRange.compareBoundaryPoints( + anchor_node, + anchor_offset, + focus_node, + focus_offset, + ); + + const range = try Range.init(page); + + switch (cmp) { + .before => { + try range.setStart(anchor_node, anchor_offset); + try range.setEnd(focus_node, focus_offset); + self._direction = .forward; + }, + .after => { + try range.setStart(focus_node, focus_offset); + try range.setEnd(anchor_node, anchor_offset); + self._direction = .backward; + }, + .equal => { + try range.setStart(anchor_node, anchor_offset); + try range.setEnd(anchor_node, anchor_offset); + self._direction = .none; + }, + } + + self.removeAllRangesInner(false); + try self._ranges.append(page.arena, range); +} pub fn setPosition(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !void { const node = _node orelse { - self.removeAllRanges(); + self.removeAllRangesInner(true); return; }; @@ -241,9 +358,8 @@ pub fn setPosition(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) try range.setStart(node, offset); try range.setEnd(node, offset); - self.removeAllRanges(); + self.removeAllRangesInner(true); try self._ranges.append(page.arena, range); - self._direction = .none; } pub const JsApi = struct { @@ -272,13 +388,13 @@ pub const JsApi = struct { pub const deleteFromDocument = bridge.function(Selection.deleteFromDocument, .{}); pub const empty = bridge.function(Selection.removeAllRanges, .{}); pub const extend = bridge.function(Selection.extend, .{ .dom_exception = true }); - // getComposedRanges + // unimplemented: getComposedRanges pub const getRangeAt = bridge.function(Selection.getRangeAt, .{ .dom_exception = true }); - // modify + pub const modify = bridge.function(Selection.modify, .{}); pub const removeAllRanges = bridge.function(Selection.removeAllRanges, .{}); pub const removeRange = bridge.function(Selection.removeRange, .{}); - // selectAllChildren - // setBaseAndExtent + pub const selectAllChildren = bridge.function(Selection.selectAllChildren, .{}); + pub const setBaseAndExtent = bridge.function(Selection.setBaseAndExtent, .{ .dom_exception = true }); pub const setPosition = bridge.function(Selection.setPosition, .{}); };