mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 06:23:45 +00:00
add remaining functions to Selection
This commit is contained in:
@@ -406,3 +406,165 @@
|
|||||||
testing.expectEqual("Range", sel.type);
|
testing.expectEqual("Range", sel.type);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=selectAllChildren>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const nested = document.getElementById("nested");
|
||||||
|
const s1 = document.getElementById("s1");
|
||||||
|
const s2 = document.getElementById("s2");
|
||||||
|
|
||||||
|
// Select all children of nested div
|
||||||
|
sel.selectAllChildren(nested);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Range", sel.type);
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
|
||||||
|
// Anchor and focus should be on the parent node
|
||||||
|
testing.expectEqual(nested, sel.anchorNode);
|
||||||
|
testing.expectEqual(nested, sel.focusNode);
|
||||||
|
|
||||||
|
// Should start at offset 0 (before first child)
|
||||||
|
testing.expectEqual(0, sel.anchorOffset);
|
||||||
|
|
||||||
|
const childrenCount = nested.childNodes.length;
|
||||||
|
|
||||||
|
// Should end at offset equal to number of children (after last child)
|
||||||
|
testing.expectEqual(childrenCount, sel.focusOffset);
|
||||||
|
|
||||||
|
// Direction should be none
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
|
||||||
|
// Should contain the children (fully)
|
||||||
|
testing.expectEqual(true, sel.containsNode(s1, false));
|
||||||
|
testing.expectEqual(true, sel.containsNode(s2, false));
|
||||||
|
|
||||||
|
// Should not fully contain the parent itself
|
||||||
|
testing.expectEqual(false, sel.containsNode(nested, false));
|
||||||
|
|
||||||
|
// But should partially contain the parent
|
||||||
|
testing.expectEqual(true, sel.containsNode(nested, true));
|
||||||
|
|
||||||
|
// Verify the range
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
testing.expectEqual(nested, range.startContainer);
|
||||||
|
testing.expectEqual(nested, range.endContainer);
|
||||||
|
testing.expectEqual(0, range.startOffset);
|
||||||
|
testing.expectEqual(childrenCount, range.endOffset);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=selectAllChildrenEmpty>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
// Create an empty element
|
||||||
|
const empty = document.createElement("div");
|
||||||
|
document.body.appendChild(empty);
|
||||||
|
|
||||||
|
// Select all children of empty element
|
||||||
|
sel.selectAllChildren(empty);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Caret", sel.type); // Collapsed because no children
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(empty, sel.anchorNode);
|
||||||
|
testing.expectEqual(0, sel.anchorOffset);
|
||||||
|
testing.expectEqual(0, sel.focusOffset);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
document.body.removeChild(empty);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=selectAllChildrenReplacesSelection>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
// Start with an existing selection
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
sel.selectAllChildren(p1);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(p1, sel.anchorNode);
|
||||||
|
|
||||||
|
// selectAllChildren should replace the existing selection
|
||||||
|
const p2 = document.getElementById("p2");
|
||||||
|
sel.selectAllChildren(p2);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(p2, sel.anchorNode);
|
||||||
|
testing.expectEqual(p2, sel.focusNode);
|
||||||
|
|
||||||
|
// Verify old selection is gone
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
testing.expectEqual(p2, range.startContainer);
|
||||||
|
testing.expectEqual(false, p1 == range.startContainer);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=setBaseAndExtent>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
|
||||||
|
// Forward selection (anchor before focus)
|
||||||
|
sel.setBaseAndExtent(textNode, 4, textNode, 15);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Range", sel.type);
|
||||||
|
testing.expectEqual(false, sel.isCollapsed);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(4, sel.anchorOffset);
|
||||||
|
testing.expectEqual(textNode, sel.focusNode);
|
||||||
|
testing.expectEqual(15, sel.focusOffset);
|
||||||
|
testing.expectEqual("forward", sel.direction);
|
||||||
|
|
||||||
|
// Backward selection (anchor after focus)
|
||||||
|
sel.setBaseAndExtent(textNode, 15, textNode, 4);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Range", sel.type);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(15, sel.anchorOffset);
|
||||||
|
testing.expectEqual(textNode, sel.focusNode);
|
||||||
|
testing.expectEqual(4, sel.focusOffset);
|
||||||
|
testing.expectEqual("backward", sel.direction);
|
||||||
|
|
||||||
|
// Collapsed selection (anchor equals focus)
|
||||||
|
sel.setBaseAndExtent(textNode, 10, textNode, 10);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual("Caret", sel.type);
|
||||||
|
testing.expectEqual(true, sel.isCollapsed);
|
||||||
|
testing.expectEqual(10, sel.anchorOffset);
|
||||||
|
testing.expectEqual(10, sel.focusOffset);
|
||||||
|
testing.expectEqual("none", sel.direction);
|
||||||
|
|
||||||
|
// Across different nodes
|
||||||
|
const p2 = document.getElementById("p2");
|
||||||
|
const textNode2 = p2.firstChild;
|
||||||
|
|
||||||
|
sel.setBaseAndExtent(textNode, 4, textNode2, 5);
|
||||||
|
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(textNode, sel.anchorNode);
|
||||||
|
testing.expectEqual(4, sel.anchorOffset);
|
||||||
|
testing.expectEqual(textNode2, sel.focusNode);
|
||||||
|
testing.expectEqual(5, sel.focusOffset);
|
||||||
|
testing.expectEqual("forward", sel.direction);
|
||||||
|
|
||||||
|
// Should replace existing selection
|
||||||
|
sel.setBaseAndExtent(textNode, 0, textNode, 3);
|
||||||
|
testing.expectEqual(1, sel.rangeCount);
|
||||||
|
testing.expectEqual(0, sel.anchorOffset);
|
||||||
|
testing.expectEqual(3, sel.focusOffset);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -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._ranges.clearRetainingCapacity();
|
||||||
|
if (reset_direction) {
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeAllRanges(self: *Selection) void {
|
||||||
|
self.removeAllRangesInner(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collapseToEnd(self: *Selection, page: *Page) !void {
|
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.setStart(last_node, last_offset);
|
||||||
try range.setEnd(last_node, last_offset);
|
try range.setEnd(last_node, last_offset);
|
||||||
|
|
||||||
self.removeAllRanges();
|
self.removeAllRangesInner(true);
|
||||||
try self._ranges.append(page.arena, range);
|
try self._ranges.append(page.arena, range);
|
||||||
self._direction = .none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collapseToStart(self: *Selection, page: *Page) !void {
|
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);
|
||||||
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);
|
try self._ranges.append(page.arena, range);
|
||||||
self._direction = .none;
|
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 {
|
pub fn getRangeAt(self: *Selection, index: u32) !*Range {
|
||||||
if (index >= self.getRangeCount()) {
|
if (index >= self.getRangeCount()) {
|
||||||
return error.IndexSizeError;
|
return error.IndexSizeError;
|
||||||
@@ -219,15 +222,129 @@ pub fn getRangeAt(self: *Selection, index: u32) !*Range {
|
|||||||
return self._ranges.items[index];
|
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 {
|
pub fn setPosition(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !void {
|
||||||
const node = _node orelse {
|
const node = _node orelse {
|
||||||
self.removeAllRanges();
|
self.removeAllRangesInner(true);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,9 +358,8 @@ pub fn setPosition(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page)
|
|||||||
try range.setStart(node, offset);
|
try range.setStart(node, offset);
|
||||||
try range.setEnd(node, offset);
|
try range.setEnd(node, offset);
|
||||||
|
|
||||||
self.removeAllRanges();
|
self.removeAllRangesInner(true);
|
||||||
try self._ranges.append(page.arena, range);
|
try self._ranges.append(page.arena, range);
|
||||||
self._direction = .none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const JsApi = struct {
|
pub const JsApi = struct {
|
||||||
@@ -272,13 +388,13 @@ pub const JsApi = struct {
|
|||||||
pub const deleteFromDocument = bridge.function(Selection.deleteFromDocument, .{});
|
pub const deleteFromDocument = bridge.function(Selection.deleteFromDocument, .{});
|
||||||
pub const empty = bridge.function(Selection.removeAllRanges, .{});
|
pub const empty = bridge.function(Selection.removeAllRanges, .{});
|
||||||
pub const extend = bridge.function(Selection.extend, .{ .dom_exception = true });
|
pub const extend = bridge.function(Selection.extend, .{ .dom_exception = true });
|
||||||
// getComposedRanges
|
// unimplemented: getComposedRanges
|
||||||
pub const getRangeAt = bridge.function(Selection.getRangeAt, .{ .dom_exception = true });
|
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 removeAllRanges = bridge.function(Selection.removeAllRanges, .{});
|
||||||
pub const removeRange = bridge.function(Selection.removeRange, .{});
|
pub const removeRange = bridge.function(Selection.removeRange, .{});
|
||||||
// selectAllChildren
|
pub const selectAllChildren = bridge.function(Selection.selectAllChildren, .{});
|
||||||
// setBaseAndExtent
|
pub const setBaseAndExtent = bridge.function(Selection.setBaseAndExtent, .{ .dom_exception = true });
|
||||||
pub const setPosition = bridge.function(Selection.setPosition, .{});
|
pub const setPosition = bridge.function(Selection.setPosition, .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user