mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1508 from lightpanda-io/selectionchange-event
Support `selectionchange` Event
This commit is contained in:
@@ -157,7 +157,18 @@ pub fn remove(self: *EventManager, target: *EventTarget, typ: []const u8, callba
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) !void {
|
// Dispatching can be recursive from the compiler's point of view, so we need to
|
||||||
|
// give it an explicit error set so that other parts of the code can use and
|
||||||
|
// inferred error.
|
||||||
|
const DispatchError = error{
|
||||||
|
OutOfMemory,
|
||||||
|
StringTooLarge,
|
||||||
|
JSExecCallback,
|
||||||
|
CompilationError,
|
||||||
|
ExecutionError,
|
||||||
|
JsException,
|
||||||
|
};
|
||||||
|
pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) DispatchError!void {
|
||||||
if (comptime IS_DEBUG) {
|
if (comptime IS_DEBUG) {
|
||||||
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
|
log.debug(.event, "eventManager.dispatch", .{ .type = event._type_string.str(), .bubbles = event._bubbles });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,6 +183,44 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id="selectionchange_event">
|
||||||
|
{
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.value = 'Hello World';
|
||||||
|
document.body.appendChild(input);
|
||||||
|
|
||||||
|
let eventCount = 0;
|
||||||
|
let lastEvent = null;
|
||||||
|
|
||||||
|
input.addEventListener('selectionchange', (e) => {
|
||||||
|
eventCount++;
|
||||||
|
lastEvent = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
testing.expectEqual(0, eventCount);
|
||||||
|
|
||||||
|
input.setSelectionRange(0, 5);
|
||||||
|
input.select();
|
||||||
|
input.selectionStart = 3;
|
||||||
|
input.selectionEnd = 8;
|
||||||
|
|
||||||
|
let bubbledToBody = false;
|
||||||
|
document.body.addEventListener('selectionchange', () => {
|
||||||
|
bubbledToBody = true;
|
||||||
|
});
|
||||||
|
input.setSelectionRange(1, 4);
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(5, eventCount);
|
||||||
|
testing.expectEqual('selectionchange', lastEvent.type);
|
||||||
|
testing.expectEqual(input, lastEvent.target);
|
||||||
|
testing.expectEqual(true, lastEvent.bubbles);
|
||||||
|
testing.expectEqual(false, lastEvent.cancelable);
|
||||||
|
testing.expectEqual(true, bubbledToBody);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- <script id="defaultChecked">
|
<!-- <script id="defaultChecked">
|
||||||
testing.expectEqual(true, $('#check1').defaultChecked)
|
testing.expectEqual(true, $('#check1').defaultChecked)
|
||||||
testing.expectEqual(false, $('#check2').defaultChecked)
|
testing.expectEqual(false, $('#check2').defaultChecked)
|
||||||
|
|||||||
@@ -229,3 +229,41 @@
|
|||||||
testing.expectEqual('some content', clone.value)
|
testing.expectEqual('some content', clone.value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id="selectionchange_event">
|
||||||
|
{
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = 'Hello World';
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
|
||||||
|
let eventCount = 0;
|
||||||
|
let lastEvent = null;
|
||||||
|
|
||||||
|
textarea.addEventListener('selectionchange', (e) => {
|
||||||
|
eventCount++;
|
||||||
|
lastEvent = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
testing.expectEqual(0, eventCount);
|
||||||
|
|
||||||
|
textarea.setSelectionRange(0, 5);
|
||||||
|
textarea.select();
|
||||||
|
textarea.selectionStart = 3;
|
||||||
|
textarea.selectionEnd = 8;
|
||||||
|
|
||||||
|
let bubbledToBody = false;
|
||||||
|
document.body.addEventListener('selectionchange', () => {
|
||||||
|
bubbledToBody = true;
|
||||||
|
});
|
||||||
|
textarea.setSelectionRange(1, 4);
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(5, eventCount);
|
||||||
|
testing.expectEqual('selectionchange', lastEvent.type);
|
||||||
|
testing.expectEqual(textarea, lastEvent.target);
|
||||||
|
testing.expectEqual(true, lastEvent.bubbles);
|
||||||
|
testing.expectEqual(false, lastEvent.cancelable);
|
||||||
|
testing.expectEqual(true, bubbledToBody);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -541,3 +541,55 @@
|
|||||||
testing.expectEqual(3, sel.focusOffset);
|
testing.expectEqual(3, sel.focusOffset);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script id=selectionChangeEvent>
|
||||||
|
{
|
||||||
|
const sel = window.getSelection();
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
let eventCount = 0;
|
||||||
|
let lastEvent = null;
|
||||||
|
|
||||||
|
document.addEventListener('selectionchange', (e) => {
|
||||||
|
eventCount++;
|
||||||
|
lastEvent = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
const p1 = document.getElementById("p1");
|
||||||
|
const textNode = p1.firstChild;
|
||||||
|
const nested = document.getElementById("nested");
|
||||||
|
|
||||||
|
sel.collapse(textNode, 5);
|
||||||
|
sel.extend(textNode, 10);
|
||||||
|
sel.collapseToStart();
|
||||||
|
sel.collapseToEnd();
|
||||||
|
|
||||||
|
sel.removeAllRanges();
|
||||||
|
const range = document.createRange();
|
||||||
|
range.setStart(textNode, 4);
|
||||||
|
range.setEnd(textNode, 15);
|
||||||
|
sel.addRange(range);
|
||||||
|
|
||||||
|
sel.removeRange(range);
|
||||||
|
|
||||||
|
const newRange = document.createRange();
|
||||||
|
newRange.selectNodeContents(p1);
|
||||||
|
sel.addRange(newRange);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
|
||||||
|
sel.selectAllChildren(nested);
|
||||||
|
sel.setBaseAndExtent(textNode, 4, textNode, 15);
|
||||||
|
|
||||||
|
sel.collapse(textNode, 5);
|
||||||
|
sel.extend(textNode, 10);
|
||||||
|
sel.deleteFromDocument();
|
||||||
|
|
||||||
|
testing.eventually(() => {
|
||||||
|
testing.expectEqual(14, eventCount);
|
||||||
|
testing.expectEqual('selectionchange', lastEvent.type);
|
||||||
|
testing.expectEqual(document, lastEvent.target);
|
||||||
|
testing.expectEqual(false, lastEvent.bubbles);
|
||||||
|
testing.expectEqual(false, lastEvent.cancelable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -56,6 +56,20 @@ _script_created_parser: ?Parser.Streaming = null,
|
|||||||
_adopted_style_sheets: ?js.Object.Global = null,
|
_adopted_style_sheets: ?js.Object.Global = null,
|
||||||
_selection: Selection = .init,
|
_selection: Selection = .init,
|
||||||
|
|
||||||
|
_on_selectionchange: ?js.Function.Global = null,
|
||||||
|
|
||||||
|
pub fn getOnSelectionChange(self: *Document) ?js.Function.Global {
|
||||||
|
return self._on_selectionchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSelectionChange(self: *Document, listener: ?js.Function) !void {
|
||||||
|
if (listener) |listen| {
|
||||||
|
self._on_selectionchange = try listen.persistWithThis(self);
|
||||||
|
} else {
|
||||||
|
self._on_selectionchange = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const Type = union(enum) {
|
pub const Type = union(enum) {
|
||||||
generic,
|
generic,
|
||||||
html: *HTMLDocument,
|
html: *HTMLDocument,
|
||||||
@@ -930,6 +944,7 @@ pub const JsApi = struct {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const onselectionchange = bridge.accessor(Document.getOnSelectionChange, Document.setOnSelectionChange, .{});
|
||||||
pub const URL = bridge.accessor(Document.getURL, null, .{});
|
pub const URL = bridge.accessor(Document.getURL, null, .{});
|
||||||
pub const documentURI = bridge.accessor(Document.getURL, null, .{});
|
pub const documentURI = bridge.accessor(Document.getURL, null, .{});
|
||||||
pub const documentElement = bridge.accessor(Document.getDocumentElement, null, .{});
|
pub const documentElement = bridge.accessor(Document.getDocumentElement, null, .{});
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ const Page = @import("../Page.zig");
|
|||||||
const Range = @import("Range.zig");
|
const Range = @import("Range.zig");
|
||||||
const AbstractRange = @import("AbstractRange.zig");
|
const AbstractRange = @import("AbstractRange.zig");
|
||||||
const Node = @import("Node.zig");
|
const Node = @import("Node.zig");
|
||||||
|
const Event = @import("Event.zig");
|
||||||
|
const Document = @import("Document.zig");
|
||||||
|
|
||||||
/// https://w3c.github.io/selection-api/
|
/// https://w3c.github.io/selection-api/
|
||||||
const Selection = @This();
|
const Selection = @This();
|
||||||
@@ -35,6 +37,12 @@ _direction: SelectionDirection = .none,
|
|||||||
|
|
||||||
pub const init: Selection = .{};
|
pub const init: Selection = .{};
|
||||||
|
|
||||||
|
fn dispatchSelectionChangeEvent(page: *Page) !void {
|
||||||
|
const event = try Event.init("selectionchange", .{}, page);
|
||||||
|
defer if (!event._v8_handoff) event.deinit(false);
|
||||||
|
try page._event_manager.dispatch(page.document.asEventTarget(), event);
|
||||||
|
}
|
||||||
|
|
||||||
fn isInTree(self: *const Selection) bool {
|
fn isInTree(self: *const Selection) bool {
|
||||||
if (self._range == null) return false;
|
if (self._range == null) return false;
|
||||||
const anchor_node = self.getAnchorNode() orelse return false;
|
const anchor_node = self.getAnchorNode() orelse return false;
|
||||||
@@ -120,20 +128,23 @@ pub fn addRange(self: *Selection, range: *Range, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self._range = range;
|
self._range = range;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeRange(self: *Selection, range: *Range) !void {
|
pub fn removeRange(self: *Selection, range: *Range, page: *Page) !void {
|
||||||
if (self._range == range) {
|
if (self._range == range) {
|
||||||
self._range = null;
|
self._range = null;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
return error.NotFound;
|
return error.NotFound;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn removeAllRanges(self: *Selection) void {
|
pub fn removeAllRanges(self: *Selection, page: *Page) !void {
|
||||||
self._range = null;
|
self._range = null;
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collapseToEnd(self: *Selection, page: *Page) !void {
|
pub fn collapseToEnd(self: *Selection, page: *Page) !void {
|
||||||
@@ -149,6 +160,7 @@ pub fn collapseToEnd(self: *Selection, page: *Page) !void {
|
|||||||
|
|
||||||
self._range = new_range;
|
self._range = new_range;
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collapseToStart(self: *Selection, page: *Page) !void {
|
pub fn collapseToStart(self: *Selection, page: *Page) !void {
|
||||||
@@ -164,6 +176,7 @@ pub fn collapseToStart(self: *Selection, page: *Page) !void {
|
|||||||
|
|
||||||
self._range = new_range;
|
self._range = new_range;
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn containsNode(self: *const Selection, node: *Node, partial: bool) !bool {
|
pub fn containsNode(self: *const Selection, node: *Node, partial: bool) !bool {
|
||||||
@@ -194,8 +207,8 @@ pub fn containsNode(self: *const Selection, node: *Node, partial: bool) !bool {
|
|||||||
|
|
||||||
pub fn deleteFromDocument(self: *Selection, page: *Page) !void {
|
pub fn deleteFromDocument(self: *Selection, page: *Page) !void {
|
||||||
const range = self._range orelse return;
|
const range = self._range orelse return;
|
||||||
|
|
||||||
try range.deleteContents(page);
|
try range.deleteContents(page);
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extend(self: *Selection, node: *Node, _offset: ?u32, page: *Page) !void {
|
pub fn extend(self: *Selection, node: *Node, _offset: ?u32, page: *Page) !void {
|
||||||
@@ -244,6 +257,7 @@ pub fn extend(self: *Selection, node: *Node, _offset: ?u32, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self._range = new_range;
|
self._range = new_range;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getRangeAt(self: *Selection, index: u32) !*Range {
|
pub fn getRangeAt(self: *Selection, index: u32) !*Range {
|
||||||
@@ -328,6 +342,7 @@ pub fn selectAllChildren(self: *Selection, parent: *Node, page: *Page) !void {
|
|||||||
|
|
||||||
self._range = range;
|
self._range = range;
|
||||||
self._direction = .forward;
|
self._direction = .forward;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setBaseAndExtent(
|
pub fn setBaseAndExtent(
|
||||||
@@ -374,11 +389,12 @@ pub fn setBaseAndExtent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
self._range = range;
|
self._range = range;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn collapse(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !void {
|
pub fn collapse(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !void {
|
||||||
const node = _node orelse {
|
const node = _node orelse {
|
||||||
self.removeAllRanges();
|
try self.removeAllRanges(page);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -400,6 +416,7 @@ pub fn collapse(self: *Selection, _node: ?*Node, _offset: ?u32, page: *Page) !vo
|
|||||||
|
|
||||||
self._range = range;
|
self._range = range;
|
||||||
self._direction = .none;
|
self._direction = .none;
|
||||||
|
try dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toString(self: *const Selection, page: *Page) ![]const u8 {
|
pub fn toString(self: *const Selection, page: *Page) ![]const u8 {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const Element = @import("../../Element.zig");
|
|||||||
const HtmlElement = @import("../Html.zig");
|
const HtmlElement = @import("../Html.zig");
|
||||||
const Form = @import("Form.zig");
|
const Form = @import("Form.zig");
|
||||||
const Selection = @import("../../Selection.zig");
|
const Selection = @import("../../Selection.zig");
|
||||||
|
const Event = @import("../../Event.zig");
|
||||||
|
|
||||||
const Input = @This();
|
const Input = @This();
|
||||||
|
|
||||||
@@ -83,6 +84,26 @@ _selection_start: u32 = 0,
|
|||||||
_selection_end: u32 = 0,
|
_selection_end: u32 = 0,
|
||||||
_selection_direction: Selection.SelectionDirection = .none,
|
_selection_direction: Selection.SelectionDirection = .none,
|
||||||
|
|
||||||
|
_on_selectionchange: ?js.Function.Global = null,
|
||||||
|
|
||||||
|
pub fn getOnSelectionChange(self: *Input) ?js.Function.Global {
|
||||||
|
return self._on_selectionchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSelectionChange(self: *Input, listener: ?js.Function) !void {
|
||||||
|
if (listener) |listen| {
|
||||||
|
self._on_selectionchange = try listen.persistWithThis(self);
|
||||||
|
} else {
|
||||||
|
self._on_selectionchange = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatchSelectionChangeEvent(self: *Input, page: *Page) !void {
|
||||||
|
const event = try Event.init("selectionchange", .{ .bubbles = true }, page);
|
||||||
|
defer if (!event._v8_handoff) event.deinit(false);
|
||||||
|
try page._event_manager.dispatch(self.asElement().asEventTarget(), event);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn asElement(self: *Input) *Element {
|
pub fn asElement(self: *Input) *Element {
|
||||||
return self._proto._proto;
|
return self._proto._proto;
|
||||||
}
|
}
|
||||||
@@ -266,9 +287,9 @@ pub fn setRequired(self: *Input, required: bool, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select(self: *Input) !void {
|
pub fn select(self: *Input, page: *Page) !void {
|
||||||
const len = if (self._value) |v| @as(u32, @intCast(v.len)) else 0;
|
const len = if (self._value) |v| @as(u32, @intCast(v.len)) else 0;
|
||||||
try self.setSelectionRange(0, len, null);
|
try self.setSelectionRange(0, len, null, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selectionAvailable(self: *const Input) bool {
|
fn selectionAvailable(self: *const Input) bool {
|
||||||
@@ -300,6 +321,7 @@ pub fn innerInsert(self: *Input, str: []const u8, page: *Page) !void {
|
|||||||
self._selection_start = @intCast(new_value.len);
|
self._selection_start = @intCast(new_value.len);
|
||||||
self._selection_end = @intCast(new_value.len);
|
self._selection_end = @intCast(new_value.len);
|
||||||
self._selection_direction = .none;
|
self._selection_direction = .none;
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
},
|
},
|
||||||
.partial => |range| {
|
.partial => |range| {
|
||||||
// if the input is partially selected, replace the selected content.
|
// if the input is partially selected, replace the selected content.
|
||||||
@@ -318,6 +340,7 @@ pub fn innerInsert(self: *Input, str: []const u8, page: *Page) !void {
|
|||||||
self._selection_start = @intCast(new_pos);
|
self._selection_start = @intCast(new_pos);
|
||||||
self._selection_end = @intCast(new_pos);
|
self._selection_end = @intCast(new_pos);
|
||||||
self._selection_direction = .none;
|
self._selection_direction = .none;
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
},
|
},
|
||||||
.none => {
|
.none => {
|
||||||
// if the input is not selected, just insert at cursor.
|
// if the input is not selected, just insert at cursor.
|
||||||
@@ -337,9 +360,10 @@ pub fn getSelectionStart(self: *const Input) !?u32 {
|
|||||||
return self._selection_start;
|
return self._selection_start;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSelectionStart(self: *Input, value: u32) !void {
|
pub fn setSelectionStart(self: *Input, value: u32, page: *Page) !void {
|
||||||
if (!self.selectionAvailable()) return error.InvalidStateError;
|
if (!self.selectionAvailable()) return error.InvalidStateError;
|
||||||
self._selection_start = value;
|
self._selection_start = value;
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSelectionEnd(self: *const Input) !?u32 {
|
pub fn getSelectionEnd(self: *const Input) !?u32 {
|
||||||
@@ -347,12 +371,19 @@ pub fn getSelectionEnd(self: *const Input) !?u32 {
|
|||||||
return self._selection_end;
|
return self._selection_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSelectionEnd(self: *Input, value: u32) !void {
|
pub fn setSelectionEnd(self: *Input, value: u32, page: *Page) !void {
|
||||||
if (!self.selectionAvailable()) return error.InvalidStateError;
|
if (!self.selectionAvailable()) return error.InvalidStateError;
|
||||||
self._selection_end = value;
|
self._selection_end = value;
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSelectionRange(self: *Input, selection_start: u32, selection_end: u32, selection_dir: ?[]const u8) !void {
|
pub fn setSelectionRange(
|
||||||
|
self: *Input,
|
||||||
|
selection_start: u32,
|
||||||
|
selection_end: u32,
|
||||||
|
selection_dir: ?[]const u8,
|
||||||
|
page: *Page,
|
||||||
|
) !void {
|
||||||
if (!self.selectionAvailable()) return error.InvalidStateError;
|
if (!self.selectionAvailable()) return error.InvalidStateError;
|
||||||
|
|
||||||
const direction = blk: {
|
const direction = blk: {
|
||||||
@@ -380,6 +411,8 @@ pub fn setSelectionRange(self: *Input, selection_start: u32, selection_end: u32,
|
|||||||
self._selection_direction = direction;
|
self._selection_direction = direction;
|
||||||
self._selection_start = start;
|
self._selection_start = start;
|
||||||
self._selection_end = end;
|
self._selection_end = end;
|
||||||
|
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getForm(self: *Input, page: *Page) ?*Form {
|
pub fn getForm(self: *Input, page: *Page) ?*Form {
|
||||||
@@ -505,6 +538,7 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const onselectionchange = bridge.accessor(Input.getOnSelectionChange, Input.setOnSelectionChange, .{});
|
||||||
pub const @"type" = bridge.accessor(Input.getType, Input.setType, .{});
|
pub const @"type" = bridge.accessor(Input.getType, Input.setType, .{});
|
||||||
pub const value = bridge.accessor(Input.getValue, Input.setValue, .{ .dom_exception = true });
|
pub const value = bridge.accessor(Input.getValue, Input.setValue, .{ .dom_exception = true });
|
||||||
pub const defaultValue = bridge.accessor(Input.getDefaultValue, Input.setDefaultValue, .{});
|
pub const defaultValue = bridge.accessor(Input.getDefaultValue, Input.setDefaultValue, .{});
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const Element = @import("../../Element.zig");
|
|||||||
const HtmlElement = @import("../Html.zig");
|
const HtmlElement = @import("../Html.zig");
|
||||||
const Form = @import("Form.zig");
|
const Form = @import("Form.zig");
|
||||||
const Selection = @import("../../Selection.zig");
|
const Selection = @import("../../Selection.zig");
|
||||||
|
const Event = @import("../../Event.zig");
|
||||||
|
|
||||||
const TextArea = @This();
|
const TextArea = @This();
|
||||||
|
|
||||||
@@ -35,6 +36,26 @@ _selection_start: u32 = 0,
|
|||||||
_selection_end: u32 = 0,
|
_selection_end: u32 = 0,
|
||||||
_selection_direction: Selection.SelectionDirection = .none,
|
_selection_direction: Selection.SelectionDirection = .none,
|
||||||
|
|
||||||
|
_on_selectionchange: ?js.Function.Global = null,
|
||||||
|
|
||||||
|
pub fn getOnSelectionChange(self: *TextArea) ?js.Function.Global {
|
||||||
|
return self._on_selectionchange;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setOnSelectionChange(self: *TextArea, listener: ?js.Function) !void {
|
||||||
|
if (listener) |listen| {
|
||||||
|
self._on_selectionchange = try listen.persistWithThis(self);
|
||||||
|
} else {
|
||||||
|
self._on_selectionchange = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatchSelectionChangeEvent(self: *TextArea, page: *Page) !void {
|
||||||
|
const event = try Event.init("selectionchange", .{ .bubbles = true }, page);
|
||||||
|
defer if (!event._v8_handoff) event.deinit(false);
|
||||||
|
try page._event_manager.dispatch(self.asElement().asEventTarget(), event);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn asElement(self: *TextArea) *Element {
|
pub fn asElement(self: *TextArea) *Element {
|
||||||
return self._proto._proto;
|
return self._proto._proto;
|
||||||
}
|
}
|
||||||
@@ -115,9 +136,9 @@ pub fn setRequired(self: *TextArea, required: bool, page: *Page) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select(self: *TextArea) !void {
|
pub fn select(self: *TextArea, page: *Page) !void {
|
||||||
const len = if (self._value) |v| @as(u32, @intCast(v.len)) else 0;
|
const len = if (self._value) |v| @as(u32, @intCast(v.len)) else 0;
|
||||||
try self.setSelectionRange(0, len, null);
|
try self.setSelectionRange(0, len, null, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const HowSelected = union(enum) { partial: struct { u32, u32 }, full, none };
|
const HowSelected = union(enum) { partial: struct { u32, u32 }, full, none };
|
||||||
@@ -141,6 +162,7 @@ pub fn innerInsert(self: *TextArea, str: []const u8, page: *Page) !void {
|
|||||||
self._selection_start = @intCast(new_value.len);
|
self._selection_start = @intCast(new_value.len);
|
||||||
self._selection_end = @intCast(new_value.len);
|
self._selection_end = @intCast(new_value.len);
|
||||||
self._selection_direction = .none;
|
self._selection_direction = .none;
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
},
|
},
|
||||||
.partial => |range| {
|
.partial => |range| {
|
||||||
// if the text area is partially selected, replace the selected content.
|
// if the text area is partially selected, replace the selected content.
|
||||||
@@ -159,6 +181,7 @@ pub fn innerInsert(self: *TextArea, str: []const u8, page: *Page) !void {
|
|||||||
self._selection_start = @intCast(new_pos);
|
self._selection_start = @intCast(new_pos);
|
||||||
self._selection_end = @intCast(new_pos);
|
self._selection_end = @intCast(new_pos);
|
||||||
self._selection_direction = .none;
|
self._selection_direction = .none;
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
},
|
},
|
||||||
.none => {
|
.none => {
|
||||||
// if the text area is not selected, just insert at cursor.
|
// if the text area is not selected, just insert at cursor.
|
||||||
@@ -177,19 +200,27 @@ pub fn getSelectionStart(self: *const TextArea) u32 {
|
|||||||
return self._selection_start;
|
return self._selection_start;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSelectionStart(self: *TextArea, value: u32) void {
|
pub fn setSelectionStart(self: *TextArea, value: u32, page: *Page) !void {
|
||||||
self._selection_start = value;
|
self._selection_start = value;
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSelectionEnd(self: *const TextArea) u32 {
|
pub fn getSelectionEnd(self: *const TextArea) u32 {
|
||||||
return self._selection_end;
|
return self._selection_end;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSelectionEnd(self: *TextArea, value: u32) void {
|
pub fn setSelectionEnd(self: *TextArea, value: u32, page: *Page) !void {
|
||||||
self._selection_end = value;
|
self._selection_end = value;
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setSelectionRange(self: *TextArea, selection_start: u32, selection_end: u32, selection_dir: ?[]const u8) !void {
|
pub fn setSelectionRange(
|
||||||
|
self: *TextArea,
|
||||||
|
selection_start: u32,
|
||||||
|
selection_end: u32,
|
||||||
|
selection_dir: ?[]const u8,
|
||||||
|
page: *Page,
|
||||||
|
) !void {
|
||||||
const direction = blk: {
|
const direction = blk: {
|
||||||
if (selection_dir) |sd| {
|
if (selection_dir) |sd| {
|
||||||
break :blk std.meta.stringToEnum(Selection.SelectionDirection, sd) orelse .none;
|
break :blk std.meta.stringToEnum(Selection.SelectionDirection, sd) orelse .none;
|
||||||
@@ -215,6 +246,8 @@ pub fn setSelectionRange(self: *TextArea, selection_start: u32, selection_end: u
|
|||||||
self._selection_direction = direction;
|
self._selection_direction = direction;
|
||||||
self._selection_start = start;
|
self._selection_start = start;
|
||||||
self._selection_end = end;
|
self._selection_end = end;
|
||||||
|
|
||||||
|
try self.dispatchSelectionChangeEvent(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getForm(self: *TextArea, page: *Page) ?*Form {
|
pub fn getForm(self: *TextArea, page: *Page) ?*Form {
|
||||||
@@ -250,12 +283,19 @@ pub const JsApi = struct {
|
|||||||
pub var class_id: bridge.ClassId = undefined;
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const onselectionchange = bridge.accessor(TextArea.getOnSelectionChange, TextArea.setOnSelectionChange, .{});
|
||||||
pub const value = bridge.accessor(TextArea.getValue, TextArea.setValue, .{});
|
pub const value = bridge.accessor(TextArea.getValue, TextArea.setValue, .{});
|
||||||
pub const defaultValue = bridge.accessor(TextArea.getDefaultValue, TextArea.setDefaultValue, .{});
|
pub const defaultValue = bridge.accessor(TextArea.getDefaultValue, TextArea.setDefaultValue, .{});
|
||||||
pub const disabled = bridge.accessor(TextArea.getDisabled, TextArea.setDisabled, .{});
|
pub const disabled = bridge.accessor(TextArea.getDisabled, TextArea.setDisabled, .{});
|
||||||
pub const name = bridge.accessor(TextArea.getName, TextArea.setName, .{});
|
pub const name = bridge.accessor(TextArea.getName, TextArea.setName, .{});
|
||||||
pub const required = bridge.accessor(TextArea.getRequired, TextArea.setRequired, .{});
|
pub const required = bridge.accessor(TextArea.getRequired, TextArea.setRequired, .{});
|
||||||
pub const form = bridge.accessor(TextArea.getForm, null, .{});
|
pub const form = bridge.accessor(TextArea.getForm, null, .{});
|
||||||
|
pub const select = bridge.function(TextArea.select, .{});
|
||||||
|
|
||||||
|
pub const selectionStart = bridge.accessor(TextArea.getSelectionStart, TextArea.setSelectionStart, .{});
|
||||||
|
pub const selectionEnd = bridge.accessor(TextArea.getSelectionEnd, TextArea.setSelectionEnd, .{});
|
||||||
|
pub const selectionDirection = bridge.accessor(TextArea.getSelectionDirection, null, .{});
|
||||||
|
pub const setSelectionRange = bridge.function(TextArea.setSelectionRange, .{ .dom_exception = true });
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Build = struct {
|
pub const Build = struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user