Merge pull request #1900 from lightpanda-io/input-event
Some checks failed
zig-test / zig fmt (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled

distapch InputEvent on input/TextArea changes
This commit is contained in:
Karl Seguin
2026-03-19 06:39:44 +08:00
committed by GitHub
7 changed files with 146 additions and 3 deletions

View File

@@ -848,6 +848,7 @@ pub const JsApis = flattenTypes(&.{
@import("../webapi/event/FocusEvent.zig"), @import("../webapi/event/FocusEvent.zig"),
@import("../webapi/event/WheelEvent.zig"), @import("../webapi/event/WheelEvent.zig"),
@import("../webapi/event/TextEvent.zig"), @import("../webapi/event/TextEvent.zig"),
@import("../webapi/event/InputEvent.zig"),
@import("../webapi/event/PromiseRejectionEvent.zig"), @import("../webapi/event/PromiseRejectionEvent.zig"),
@import("../webapi/MessageChannel.zig"), @import("../webapi/MessageChannel.zig"),
@import("../webapi/MessagePort.zig"), @import("../webapi/MessagePort.zig"),

View File

@@ -365,6 +365,11 @@ pub fn createEvent(_: *const Document, event_type: []const u8, page: *Page) !*@i
return (try KeyboardEvent.init("", null, page)).asEvent(); return (try KeyboardEvent.init("", null, page)).asEvent();
} }
if (std.mem.eql(u8, normalized, "inputevent")) {
const InputEvent = @import("event/InputEvent.zig");
return (try InputEvent.init("", null, page)).asEvent();
}
if (std.mem.eql(u8, normalized, "mouseevent") or std.mem.eql(u8, normalized, "mouseevents")) { if (std.mem.eql(u8, normalized, "mouseevent") or std.mem.eql(u8, normalized, "mouseevents")) {
const MouseEvent = @import("event/MouseEvent.zig"); const MouseEvent = @import("event/MouseEvent.zig");
return (try MouseEvent.init("", null, page)).asEvent(); return (try MouseEvent.init("", null, page)).asEvent();

View File

@@ -28,6 +28,7 @@ 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 Event = @import("../../Event.zig");
const InputEvent = @import("../../event/InputEvent.zig");
const Input = @This(); const Input = @This();
@@ -103,6 +104,11 @@ fn dispatchSelectionChangeEvent(self: *Input, page: *Page) !void {
try page._event_manager.dispatch(self.asElement().asEventTarget(), event); try page._event_manager.dispatch(self.asElement().asEventTarget(), event);
} }
fn dispatchInputEvent(self: *Input, data: ?[]const u8, input_type: []const u8, page: *Page) !void {
const event = try InputEvent.initTrusted(comptime .wrap("input"), .{ .data = data, .inputType = input_type }, page);
try page._event_manager.dispatch(self.asElement().asEventTarget(), event.asEvent());
}
pub fn asElement(self: *Input) *Element { pub fn asElement(self: *Input) *Element {
return self._proto._proto; return self._proto._proto;
} }
@@ -425,6 +431,7 @@ pub fn innerInsert(self: *Input, str: []const u8, page: *Page) !void {
try self.setValue(new_value, page); try self.setValue(new_value, page);
}, },
} }
try self.dispatchInputEvent(str, "insertText", page);
} }
pub fn getSelectionDirection(self: *const Input) []const u8 { pub fn getSelectionDirection(self: *const Input) []const u8 {

View File

@@ -26,6 +26,7 @@ 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 Event = @import("../../Event.zig");
const InputEvent = @import("../../event/InputEvent.zig");
const TextArea = @This(); const TextArea = @This();
@@ -55,6 +56,11 @@ fn dispatchSelectionChangeEvent(self: *TextArea, page: *Page) !void {
try page._event_manager.dispatch(self.asElement().asEventTarget(), event); try page._event_manager.dispatch(self.asElement().asEventTarget(), event);
} }
fn dispatchInputEvent(self: *TextArea, data: ?[]const u8, input_type: []const u8, page: *Page) !void {
const event = try InputEvent.initTrusted(comptime .wrap("input"), .{ .data = data, .inputType = input_type }, page);
try page._event_manager.dispatch(self.asElement().asEventTarget(), event.asEvent());
}
pub fn asElement(self: *TextArea) *Element { pub fn asElement(self: *TextArea) *Element {
return self._proto._proto; return self._proto._proto;
} }
@@ -189,6 +195,7 @@ pub fn innerInsert(self: *TextArea, str: []const u8, page: *Page) !void {
try self.setValue(new_value, page); try self.setValue(new_value, page);
}, },
} }
try self.dispatchInputEvent(str, "insertText", page);
} }
pub fn getSelectionDirection(self: *const TextArea) []const u8 { pub fn getSelectionDirection(self: *const TextArea) []const u8 {

View File

@@ -0,0 +1,121 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// 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 String = @import("../../../string.zig").String;
const Page = @import("../../Page.zig");
const Session = @import("../../Session.zig");
const js = @import("../../js/js.zig");
const Event = @import("../Event.zig");
const UIEvent = @import("UIEvent.zig");
const Allocator = std.mem.Allocator;
const InputEvent = @This();
_proto: *UIEvent,
_data: ?[]const u8,
// TODO: add dataTransfer
_input_type: []const u8,
_is_composing: bool,
pub const InputEventOptions = struct {
data: ?[]const u8 = null,
inputType: ?[]const u8 = null,
isComposing: bool = false,
};
const Options = Event.inheritOptions(
InputEvent,
InputEventOptions,
);
pub fn initTrusted(typ: String, _opts: ?Options, page: *Page) !*InputEvent {
const arena = try page.getArena(.{ .debug = "InputEvent.trusted" });
errdefer page.releaseArena(arena);
return initWithTrusted(arena, typ, _opts, true, page);
}
pub fn init(typ: []const u8, _opts: ?Options, page: *Page) !*InputEvent {
const arena = try page.getArena(.{ .debug = "InputEvent" });
errdefer page.releaseArena(arena);
const type_string = try String.init(arena, typ, .{});
return initWithTrusted(arena, type_string, _opts, false, page);
}
fn initWithTrusted(arena: Allocator, typ: String, _opts: ?Options, trusted: bool, page: *Page) !*InputEvent {
const opts = _opts orelse Options{};
const event = try page._factory.uiEvent(
arena,
typ,
InputEvent{
._proto = undefined,
._data = if (opts.data) |d| try arena.dupe(u8, d) else null,
._input_type = if (opts.inputType) |it| try arena.dupe(u8, it) else "",
._is_composing = opts.isComposing,
},
);
Event.populatePrototypes(event, opts, trusted);
// https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event
const rootevt = event._proto._proto;
rootevt._bubbles = true;
rootevt._cancelable = false;
rootevt._composed = true;
return event;
}
pub fn deinit(self: *InputEvent, shutdown: bool, session: *Session) void {
self._proto.deinit(shutdown, session);
}
pub fn asEvent(self: *InputEvent) *Event {
return self._proto.asEvent();
}
pub fn getData(self: *const InputEvent) ?[]const u8 {
return self._data;
}
pub fn getInputType(self: *const InputEvent) []const u8 {
return self._input_type;
}
pub fn getIsComposing(self: *const InputEvent) bool {
return self._is_composing;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(InputEvent);
pub const Meta = struct {
pub const name = "InputEvent";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
pub const weak = true;
pub const finalizer = bridge.finalizer(InputEvent.deinit);
};
pub const constructor = bridge.constructor(InputEvent.init, .{});
pub const data = bridge.accessor(InputEvent.getData, null, .{});
pub const inputType = bridge.accessor(InputEvent.getInputType, null, .{});
pub const isComposing = bridge.accessor(InputEvent.getIsComposing, null, .{});
};

View File

@@ -37,6 +37,7 @@ pub const Type = union(enum) {
keyboard_event: *@import("KeyboardEvent.zig"), keyboard_event: *@import("KeyboardEvent.zig"),
focus_event: *@import("FocusEvent.zig"), focus_event: *@import("FocusEvent.zig"),
text_event: *@import("TextEvent.zig"), text_event: *@import("TextEvent.zig"),
input_event: *@import("InputEvent.zig"),
}; };
pub const UIEventOptions = struct { pub const UIEventOptions = struct {
@@ -88,6 +89,7 @@ pub fn is(self: *UIEvent, comptime T: type) ?*T {
.keyboard_event => |e| return if (T == @import("KeyboardEvent.zig")) e else null, .keyboard_event => |e| return if (T == @import("KeyboardEvent.zig")) e else null,
.focus_event => |e| return if (T == @import("FocusEvent.zig")) e else null, .focus_event => |e| return if (T == @import("FocusEvent.zig")) e else null,
.text_event => |e| return if (T == @import("TextEvent.zig")) e else null, .text_event => |e| return if (T == @import("TextEvent.zig")) e else null,
.input_event => |e| return if (T == @import("InputEvent.zig")) e else null,
} }
return null; return null;
} }