diff --git a/.gitignore b/.gitignore
index 84faae1806..61a3b53de1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -215,3 +215,4 @@ src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js
node_modules
src/Web/Avalonia.Web.Blazor/webapp/package-lock.json
src/Web/Avalonia.Web.Blazor/wwwroot
+src/Web/Avalonia.Web/wwwroot
diff --git a/src/Web/Avalonia.Web.Sample/index.html b/src/Web/Avalonia.Web.Sample/index.html
index 25b92798ad..518abd4a15 100644
--- a/src/Web/Avalonia.Web.Sample/index.html
+++ b/src/Web/Avalonia.Web.Sample/index.html
@@ -13,7 +13,7 @@
-
+
diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs
index f7af35d2b0..54652bc7e2 100644
--- a/src/Web/Avalonia.Web/AvaloniaView.cs
+++ b/src/Web/Avalonia.Web/AvaloniaView.cs
@@ -13,6 +13,8 @@ using Avalonia.Input.TextInput;
using Avalonia.Platform.Storage;
using Avalonia.Rendering.Composition;
using Avalonia.Web.Blazor;
+using Avalonia.Web.Interop;
+
using SkiaSharp;
using static Avalonia.Web.AvaloniaRuntime;
@@ -72,8 +74,10 @@ namespace Avalonia.Web
_topLevel.Renderer.Start();
-
-
+ InputHelper.SubscribeKeyboardEvents(
+ div,
+ (code, key, modifier) => _topLevelImpl.RawKeyboardEvent(Input.Raw.RawKeyEventType.KeyDown, code, key, (Input.RawInputModifiers)modifier),
+ (code, key, modifier) => _topLevelImpl.RawKeyboardEvent(Input.Raw.RawKeyEventType.KeyUp, code, key, (Input.RawInputModifiers)modifier));
//_interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame);
diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs
new file mode 100644
index 0000000000..6856a021f2
--- /dev/null
+++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Runtime.InteropServices.JavaScript;
+
+namespace Avalonia.Web.Interop;
+
+internal static partial class InputHelper
+{
+ //[JSImport("InputHelper.subscribePointerEvents", "avalonia.ts")]
+ //public static partial void SubscribePointerEvents(
+ // JSObject htmlElement,
+ // [JSMarshalAs>]
+ // Func wheel);
+
+ [JSImport("InputHelper.subscribeKeyboardEvents", "avalonia.ts")]
+ public static partial void SubscribeKeyboardEvents(
+ JSObject htmlElement,
+ [JSMarshalAs>]
+ Func keyDown,
+ [JSMarshalAs>]
+ Func keyUp);
+
+ [JSImport("InputHelper.subscribeInputEvents", "avalonia.ts")]
+ public static partial void SubscribeInputEvents(
+ JSObject htmlElement,
+ [JSMarshalAs>]
+ Func input);
+
+
+ [JSImport("InputHelper.clearInput", "avalonia.ts")]
+ public static partial void ClearInputElement(JSObject htmlElement);
+
+ [JSImport("InputHelper.isInputElement", "avalonia.ts")]
+ public static partial void IsInputElement(JSObject htmlElement);
+
+ [JSImport("InputHelper.focusElement", "avalonia.ts")]
+ public static partial void FocusElement(JSObject htmlElement);
+
+ [JSImport("InputHelper.setCursor", "avalonia.ts")]
+ public static partial void SetCursor(JSObject htmlElement, string kind);
+
+ [JSImport("InputHelper.hide", "avalonia.ts")]
+ public static partial void HideElement(JSObject htmlElement);
+
+ [JSImport("InputHelper.show", "avalonia.ts")]
+ public static partial void ShowElement(JSObject htmlElement);
+}
diff --git a/src/Web/Avalonia.Web/Interop/StreamHelper.cs b/src/Web/Avalonia.Web/Interop/StreamHelper.cs
new file mode 100644
index 0000000000..c77d87fbe4
--- /dev/null
+++ b/src/Web/Avalonia.Web/Interop/StreamHelper.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading.Tasks;
+
+namespace Avalonia.Web.Storage;
+
+///
+/// Set of FileSystemWritableFileStream and Blob methods.
+///
+internal static partial class StreamHelper
+{
+ [JSImport("StreamHelper.seek", "avalonia.ts")]
+ public static partial void Seek(JSObject stream, [JSMarshalAs] long position);
+
+ [JSImport("StreamHelper.truncate", "avalonia.ts")]
+ public static partial void Truncate(JSObject stream, [JSMarshalAs] long position);
+
+ [JSImport("StreamHelper.write", "avalonia.ts")]
+ public static partial void Write(JSObject stream, [JSMarshalAs] Span data);
+
+ [JSImport("StreamHelper.write", "avalonia.ts")]
+ public static partial Task WriteAsync(JSObject stream, [JSMarshalAs] ArraySegment data);
+
+ [JSImport("StreamHelper.close", "avalonia.ts")]
+ public static partial void Close(JSObject stream);
+
+ [JSImport("StreamHelper.close", "avalonia.ts")]
+ public static partial Task CloseAsync(JSObject stream);
+
+ [JSImport("StreamHelper.size", "avalonia.ts")]
+ [return: JSMarshalAs]
+ public static partial long Size(JSObject stream);
+
+ [JSImport("StreamHelper.byteLength", "avalonia.ts")]
+ [return: JSMarshalAs]
+ public static partial long ByteLength(JSObject stream);
+
+ [JSImport("StreamHelper.sliceToArray", "avalonia.ts")]
+ [return: JSMarshalAs]
+ public static partial Span Slice(JSObject stream, [JSMarshalAs] long offset, int count);
+
+ public static async Task> SliceAsync(JSObject stream, long offset, int count)
+ {
+ using var buffer = await SliceToBufferAsync(stream, offset, count);
+ return BufferToArray(buffer);
+ }
+
+ [JSImport("StreamHelper.slice", "avalonia.ts")]
+ [return: JSMarshalAs>]
+ private static partial Task SliceToBufferAsync(JSObject stream, [JSMarshalAs] long offset, int count);
+
+ [JSImport("StreamHelper.toArray", "avalonia.ts")]
+ [return: JSMarshalAs]
+ private static partial ArraySegment BufferToArray(JSObject stream);
+}
diff --git a/src/Web/Avalonia.Web/Keycodes.cs b/src/Web/Avalonia.Web/Keycodes.cs
new file mode 100644
index 0000000000..d1185f6e45
--- /dev/null
+++ b/src/Web/Avalonia.Web/Keycodes.cs
@@ -0,0 +1,129 @@
+using System.Collections.Generic;
+
+using Avalonia.Input;
+
+namespace Avalonia.Web
+{
+ internal static class Keycodes
+ {
+ public static Dictionary KeyCodes = new()
+ {
+ { "Escape", Key.Escape },
+ { "Digit1", Key.D1 },
+ { "Digit2", Key.D2 },
+ { "Digit3", Key.D3 },
+ { "Digit4", Key.D4 },
+ { "Digit5", Key.D5 },
+ { "Digit6", Key.D6 },
+ { "Digit7", Key.D7 },
+ { "Digit8", Key.D8 },
+ { "Digit9", Key.D9 },
+ { "Digit0", Key.D0 },
+ { "Minus", Key.OemMinus },
+ //{ "Equal" , Key. },
+ { "Backspace", Key.Back },
+ { "Tab", Key.Tab },
+ { "KeyQ", Key.Q },
+ { "KeyW", Key.W },
+ { "KeyE", Key.E },
+ { "KeyR", Key.R },
+ { "KeyT", Key.T },
+ { "KeyY", Key.Y },
+ { "KeyU", Key.U },
+ { "KeyI", Key.I },
+ { "KeyO", Key.O },
+ { "KeyP", Key.P },
+ { "BracketLeft", Key.OemOpenBrackets },
+ { "BracketRight", Key.OemCloseBrackets },
+ { "Enter", Key.Enter },
+ { "ControlLeft", Key.LeftCtrl },
+ { "KeyA", Key.A },
+ { "KeyS", Key.S },
+ { "KeyD", Key.D },
+ { "KeyF", Key.F },
+ { "KeyG", Key.G },
+ { "KeyH", Key.H },
+ { "KeyJ", Key.J },
+ { "KeyK", Key.K },
+ { "KeyL", Key.L },
+ { "Semicolon", Key.OemSemicolon },
+ { "Quote", Key.OemQuotes },
+ //{ "Backquote" , Key. },
+ { "ShiftLeft", Key.LeftShift },
+ { "Backslash", Key.OemBackslash },
+ { "KeyZ", Key.Z },
+ { "KeyX", Key.X },
+ { "KeyC", Key.C },
+ { "KeyV", Key.V },
+ { "KeyB", Key.B },
+ { "KeyN", Key.N },
+ { "KeyM", Key.M },
+ { "Comma", Key.OemComma },
+ { "Period", Key.OemPeriod },
+ //{ "Slash" , Key. },
+ { "ShiftRight", Key.RightShift },
+ { "NumpadMultiply", Key.Multiply },
+ { "AltLeft", Key.LeftAlt },
+ { "Space", Key.Space },
+ { "CapsLock", Key.CapsLock },
+ { "F1", Key.F1 },
+ { "F2", Key.F2 },
+ { "F3", Key.F3 },
+ { "F4", Key.F4 },
+ { "F5", Key.F5 },
+ { "F6", Key.F6 },
+ { "F7", Key.F7 },
+ { "F8", Key.F8 },
+ { "F9", Key.F9 },
+ { "F10", Key.F10 },
+ { "NumLock", Key.NumLock },
+ { "ScrollLock", Key.Scroll },
+ { "Numpad7", Key.NumPad7 },
+ { "Numpad8", Key.NumPad8 },
+ { "Numpad9", Key.NumPad9 },
+ { "NumpadSubtract", Key.Subtract },
+ { "Numpad4", Key.NumPad4 },
+ { "Numpad5", Key.NumPad5 },
+ { "Numpad6", Key.NumPad6 },
+ { "NumpadAdd", Key.Add },
+ { "Numpad1", Key.NumPad1 },
+ { "Numpad2", Key.NumPad2 },
+ { "Numpad3", Key.NumPad3 },
+ { "Numpad0", Key.NumPad0 },
+ { "NumpadDecimal", Key.Decimal },
+ { "Unidentified", Key.NoName },
+ //{ "IntlBackslash" , Key.bac },
+ { "F11", Key.F11 },
+ { "F12", Key.F12 },
+ //{ "IntlRo" , Key.Ro },
+ //{ "Unidentified" , Key. },
+ { "Convert", Key.ImeConvert },
+ { "KanaMode", Key.KanaMode },
+ { "NonConvert", Key.ImeNonConvert },
+ //{ "Unidentified" , Key. },
+ { "NumpadEnter", Key.Enter },
+ { "ControlRight", Key.RightCtrl },
+ { "NumpadDivide", Key.Divide },
+ { "PrintScreen", Key.PrintScreen },
+ { "AltRight", Key.RightAlt },
+ //{ "Unidentified" , Key. },
+ { "Home", Key.Home },
+ { "ArrowUp", Key.Up },
+ { "PageUp", Key.PageUp },
+ { "ArrowLeft", Key.Left },
+ { "ArrowRight", Key.Right },
+ { "End", Key.End },
+ { "ArrowDown", Key.Down },
+ { "PageDown", Key.PageDown },
+ { "Insert", Key.Insert },
+ { "Delete", Key.Delete },
+ //{ "Unidentified" , Key. },
+ { "AudioVolumeMute", Key.VolumeMute },
+ { "AudioVolumeDown", Key.VolumeDown },
+ { "AudioVolumeUp", Key.VolumeUp },
+ //{ "NumpadEqual" , Key. },
+ { "Pause", Key.Pause },
+ { "NumpadComma", Key.OemComma }
+ };
+ }
+}
diff --git a/src/Web/Avalonia.Web/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web/RazorViewTopLevelImpl.cs
index 82f4064e1a..2bcc36e687 100644
--- a/src/Web/Avalonia.Web/RazorViewTopLevelImpl.cs
+++ b/src/Web/Avalonia.Web/RazorViewTopLevelImpl.cs
@@ -110,14 +110,15 @@ namespace Avalonia.Web.Blazor
public bool RawKeyboardEvent(RawKeyEventType type, string code, string key, RawInputModifiers modifiers)
{
- /*if (Keycodes.KeyCodes.TryGetValue(code, out var avkey))
+ Console.WriteLine($"{type} {code} {key} {modifiers}");
+ if (Keycodes.KeyCodes.TryGetValue(code, out var avkey))
{
if (_inputRoot is { })
{
var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers);
-
+
Input?.Invoke(args);
-
+
return args.Handled;
}
}
@@ -126,12 +127,12 @@ namespace Avalonia.Web.Blazor
if (_inputRoot is { })
{
var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers);
-
+
Input?.Invoke(args);
return args.Handled;
}
- }*/
+ }
return false;
}
diff --git a/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs
new file mode 100644
index 0000000000..6afc5e3054
--- /dev/null
+++ b/src/Web/Avalonia.Web/Storage/BlobReadableStream.cs
@@ -0,0 +1,100 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Avalonia.Web.Storage;
+
+internal class BlobReadableStream : Stream
+{
+ private JSObject? _jSReference;
+ private long _position;
+ private readonly long _length;
+
+ public BlobReadableStream(JSObject jsStreamReference)
+ {
+ _jSReference = jsStreamReference;
+ _position = 0;
+ _length = StreamHelper.ByteLength(JSReference);
+ }
+
+ private JSObject JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(JSWriteableStream));
+
+ public override bool CanRead => true;
+
+ public override bool CanSeek => false;
+
+ public override bool CanWrite => false;
+
+ public override long Length => _length;
+
+ public override long Position
+ {
+ get => _position;
+ set => throw new NotSupportedException();
+ }
+
+ public override void Flush() { }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ return _position = origin switch
+ {
+ SeekOrigin.Current => _position + offset,
+ SeekOrigin.End => _length + offset,
+ _ => offset
+ };
+ }
+
+ public override void SetLength(long value)
+ => throw new NotSupportedException();
+
+ public override void Write(byte[] buffer, int offset, int count)
+ => throw new NotSupportedException();
+
+ public override int Read(byte[] buffer, int offset, int count)
+ => Read(buffer.AsSpan(offset, count));
+
+ public override int Read(Span buffer)
+ {
+ var numBytesToRead = (int)Math.Min(buffer.Length, Length - _position);
+ var bytesRead = StreamHelper.Slice(JSReference, _position, numBytesToRead);
+ if (bytesRead.Length != numBytesToRead)
+ {
+ throw new EndOfStreamException("Failed to read the requested number of bytes from the stream.");
+ }
+
+ _position += bytesRead.Length;
+ bytesRead.CopyTo(buffer);
+
+ return bytesRead.Length;
+ }
+
+ public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ => await ReadAsync(buffer.AsMemory(offset, count), cancellationToken);
+
+ public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default)
+ {
+ var numBytesToRead = (int)Math.Min(buffer.Length, Length - _position);
+ var bytesRead = await StreamHelper.SliceAsync(JSReference, _position, numBytesToRead);
+ if (bytesRead.Count != numBytesToRead)
+ {
+ throw new EndOfStreamException("Failed to read the requested number of bytes from the stream.");
+ }
+
+ _position += bytesRead.Count;
+ bytesRead.AsMemory().CopyTo(buffer);
+
+ return bytesRead.Count;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_jSReference is { } jsReference)
+ {
+ _jSReference = null;
+ jsReference.Dispose();
+ }
+ }
+}
diff --git a/src/Web/Avalonia.Web/Storage/WriteableStream.cs b/src/Web/Avalonia.Web/Storage/WriteableStream.cs
new file mode 100644
index 0000000000..4dce5a4c6c
--- /dev/null
+++ b/src/Web/Avalonia.Web/Storage/WriteableStream.cs
@@ -0,0 +1,136 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Avalonia.Web.Storage;
+
+// Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream
+internal sealed class JSWriteableStream : Stream
+{
+ private JSObject? _jSReference;
+
+ // Unfortunatelly we can't read current length/position, so we need to keep it C#-side only.
+ private long _length, _position;
+
+ internal JSWriteableStream(JSObject jSReference, long initialLength)
+ {
+ _jSReference = jSReference;
+ _length = initialLength;
+ }
+
+ private JSObject JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(JSWriteableStream));
+
+ public override bool CanRead => false;
+
+ public override bool CanSeek => true;
+
+ public override bool CanWrite => true;
+
+ public override long Length => _length;
+
+ public override long Position
+ {
+ get => _position;
+ set => Seek(_position, SeekOrigin.Begin);
+ }
+
+ public override void Flush()
+ {
+ // no-op
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ var position = origin switch
+ {
+ SeekOrigin.Current => _position + offset,
+ SeekOrigin.End => _length + offset,
+ _ => offset
+ };
+ StreamHelper.Seek(JSReference, position);
+ return position;
+ }
+
+ public override void SetLength(long value)
+ {
+ _length = value;
+
+ // See https://docs.w3cub.com/dom/filesystemwritablefilestream/truncate
+ // If the offset is smaller than the size, it remains unchanged. If the offset is larger than size, the offset is set to that size
+ if (_position > _length)
+ {
+ _position = _length;
+ }
+
+ StreamHelper.Truncate(JSReference, value);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ StreamHelper.Write(JSReference, buffer.AsSpan(offset, count));
+ }
+
+ public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ if (offset != 0 || count != buffer.Length)
+ {
+ // TODO, we need to pass prepared buffer to the JS
+ // Can't use ArrayPool as it can return bigger array than requested
+ // Can't use Span/Memory, as it's not supported by JS interop yet.
+ // Alternatively we can pass original buffer and offset+count, so it can be trimmed on the JS side (but is it more efficient tho?)
+ buffer = buffer.AsMemory(offset, count).ToArray();
+ }
+ return WriteAsyncInternal(buffer, cancellationToken);
+ }
+
+ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default)
+ {
+ return new ValueTask(WriteAsyncInternal(buffer.ToArray(), cancellationToken));
+ }
+
+ private Task WriteAsyncInternal(byte[] buffer, CancellationToken _)
+ {
+ _position += buffer.Length;
+
+ return StreamHelper.WriteAsync(JSReference, buffer);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ if (_jSReference is { } jsReference)
+ {
+ _jSReference = null;
+ try
+ {
+ StreamHelper.Close(JSReference);
+ }
+ finally
+ {
+ jsReference.Dispose();
+ }
+ }
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ if (_jSReference is { } jsReference)
+ {
+ _jSReference = null;
+ try
+ {
+ await StreamHelper.CloseAsync(JSReference);
+ }
+ finally
+ {
+ jsReference.Dispose();
+ }
+ }
+ }
+}
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
new file mode 100644
index 0000000000..a6734c8ce0
--- /dev/null
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
@@ -0,0 +1,107 @@
+enum RawInputModifiers
+{
+ None = 0,
+ Alt = 1,
+ Control = 2,
+ Shift = 4,
+ Meta = 8,
+
+ LeftMouseButton = 16,
+ RightMouseButton = 32,
+ MiddleMouseButton = 64,
+ XButton1MouseButton = 128,
+ XButton2MouseButton = 256,
+ KeyboardMask = Alt | Control | Shift | Meta,
+
+ PenInverted = 512,
+ PenEraser = 1024,
+ PenBarrelButton = 2048
+}
+
+export class InputHelper {
+ public static subscribeKeyboardEvents(
+ element: HTMLInputElement,
+ keyDownCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean,
+ keyUpCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean,
+ ) {
+ const keyDownHandler = (args: Event) => {
+ const keyArgs = args;
+ if (keyDownCallback(keyArgs.code, keyArgs.key, this.getModifiers(keyArgs))) {
+ args.preventDefault();
+ }
+ };
+ element.addEventListener("keydown", keyDownHandler);
+
+ const keyUpHandler = (args: Event) => {
+ const keyArgs = args;
+ if (keyUpCallback(keyArgs.code, keyArgs.key, this.getModifiers(keyArgs))) {
+ args.preventDefault();
+ }
+ };
+ element.addEventListener("keyup", keyUpHandler);
+
+ return () => {
+ element.removeEventListener("keydown", keyDownHandler);
+ element.removeEventListener("keyup", keyUpHandler);
+ };
+ }
+
+ public static subscribeInputEvents(
+ element: HTMLInputElement,
+ inputCallback: (value: string) => boolean
+ ) {
+ const inputHandler = (args: Event) => {
+ if (inputCallback((args as any).value)) {
+ args.preventDefault();
+ }
+ };
+ element.addEventListener("input", inputHandler);
+
+ return () => {
+ element.removeEventListener("input", inputHandler);
+ };
+ }
+
+ public static clearInput(inputElement: HTMLInputElement) {
+ inputElement.value = "";
+ }
+
+ public static isInputElement(element: HTMLInputElement | HTMLElement): element is HTMLInputElement {
+ return (element as HTMLInputElement).setSelectionRange !== undefined;
+ }
+
+ public static focusElement(inputElement: HTMLElement) {
+ inputElement.focus();
+
+ if (this.isInputElement(inputElement)) {
+ (inputElement as HTMLInputElement).setSelectionRange(0, 0);
+ }
+ }
+
+ public static setCursor(inputElement: HTMLInputElement, kind: string) {
+ inputElement.style.cursor = kind;
+ }
+
+ public static hide(inputElement: HTMLInputElement) {
+ inputElement.style.display = 'none';
+ }
+
+ public static show(inputElement: HTMLInputElement) {
+ inputElement.style.display = 'block';
+ }
+
+ private static getModifiers(args: KeyboardEvent): RawInputModifiers {
+ var modifiers = RawInputModifiers.None;
+
+ if (args.ctrlKey)
+ modifiers |= RawInputModifiers.Control;
+ if (args.altKey)
+ modifiers |= RawInputModifiers.Alt;
+ if (args.shiftKey)
+ modifiers |= RawInputModifiers.Shift;
+ if (args.metaKey)
+ modifiers |= RawInputModifiers.Meta;
+
+ return modifiers;
+ }
+}
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/runtime.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/runtime.ts
index bb97d0c9b7..2f154aeb3f 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia/runtime.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/runtime.ts
@@ -1,6 +1,7 @@
import { RuntimeAPI } from "../../types/dotnet";
import { Canvas } from "./canvas";
+import { InputHelper } from "./input";
export class AvaloniaRuntime {
constructor(
@@ -8,7 +9,8 @@ export class AvaloniaRuntime {
api: RuntimeAPI
) {
api.setModuleImports("avalonia.ts", {
- Canvas
+ Canvas,
+ InputHelper
});
}
}
diff --git a/src/Web/Avalonia.Web/wwwroot/avalonia.js b/src/Web/Avalonia.Web/wwwroot/avalonia.js
deleted file mode 100644
index 8c2721c600..0000000000
--- a/src/Web/Avalonia.Web/wwwroot/avalonia.js
+++ /dev/null
@@ -1,124 +0,0 @@
-var __async = (__this, __arguments, generator) => {
- return new Promise((resolve, reject) => {
- var fulfilled = (value) => {
- try {
- step(generator.next(value));
- } catch (e) {
- reject(e);
- }
- };
- var rejected = (value) => {
- try {
- step(generator.throw(value));
- } catch (e) {
- reject(e);
- }
- };
- var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
- step((generator = generator.apply(__this, __arguments)).next());
- });
-};
-
-// modules/avalonia/canvas.ts
-var Canvas = class {
- constructor(useGL, element) {
- this.renderLoopEnabled = false;
- this.renderLoopRequest = 0;
- if (useGL) {
- const ctx = Canvas.createWebGLContext(element);
- if (!ctx) {
- console.error(`Failed to create WebGL context: err ${ctx}`);
- return;
- }
- var GL = globalThis.AvaloniaGL;
- GL.makeContextCurrent(ctx);
- var GLctx = GL.currentContext.GLctx;
- const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING);
- this.glInfo = {
- context: ctx,
- fboId: fbo ? fbo.id : 0,
- stencil: GLctx.getParameter(GLctx.STENCIL_BITS),
- sample: 0,
- depth: GLctx.getParameter(GLctx.DEPTH_BITS)
- };
- }
- }
- static createCanvas(element) {
- var canvas = document.createElement("canvas");
- element.appendChild(canvas);
- return canvas;
- }
- static initGL(element, elementId) {
- console.log("inside initGL");
- var view = Canvas.init(true, element, elementId);
- if (!view || !view.glInfo)
- return null;
- return view.glInfo;
- }
- static init(useGL, element, elementId) {
- var htmlCanvas = element;
- if (!htmlCanvas) {
- console.error(`No canvas element was provided.`);
- return null;
- }
- if (!Canvas.elements)
- Canvas.elements = /* @__PURE__ */ new Map();
- Canvas.elements.set(elementId, element);
- const view = new Canvas(useGL, element);
- htmlCanvas.SKHtmlCanvas = view;
- return view;
- }
- static Foo(canvas) {
- const ctx = canvas.getContext("2d");
- ctx.fillStyle = "#FF0000";
- ctx.fillRect(0, 0, 150, 75);
- }
- static createWebGLContext(htmlCanvas) {
- const contextAttributes = {
- alpha: 1,
- depth: 1,
- stencil: 8,
- antialias: 0,
- premultipliedAlpha: 1,
- preserveDrawingBuffer: 0,
- preferLowPowerToHighPerformance: 0,
- failIfMajorPerformanceCaveat: 0,
- majorVersion: 2,
- minorVersion: 0,
- enableExtensionsByDefault: 1,
- explicitSwapControl: 0,
- renderViaOffscreenBackBuffer: 1
- };
- var GL = globalThis.AvaloniaGL;
- let ctx = GL.createContext(htmlCanvas, contextAttributes);
- if (!ctx && contextAttributes.majorVersion > 1) {
- console.warn("Falling back to WebGL 1.0");
- contextAttributes.majorVersion = 1;
- contextAttributes.minorVersion = 0;
- ctx = GL.createContext(htmlCanvas, contextAttributes);
- }
- return ctx;
- }
-};
-
-// modules/avalonia/runtime.ts
-var AvaloniaRuntime = class {
- constructor(dotnetAssembly, api) {
- this.dotnetAssembly = dotnetAssembly;
- api.setModuleImports("avalonia.ts", {
- Canvas
- });
- }
-};
-
-// modules/avalonia.ts
-function createAvaloniaRuntime(api) {
- return __async(this, null, function* () {
- const dotnetAssembly = yield api.getAssemblyExports("Avalonia.Web.dll");
- return new AvaloniaRuntime(dotnetAssembly, api);
- });
-}
-export {
- createAvaloniaRuntime
-};
-//# sourceMappingURL=avalonia.js.map
diff --git a/src/Web/Avalonia.Web/wwwroot/storage.js b/src/Web/Avalonia.Web/wwwroot/storage.js
deleted file mode 100644
index a2042aaa11..0000000000
--- a/src/Web/Avalonia.Web/wwwroot/storage.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// modules/storage.ts
-var StorageProvider = class {
- static isFileApiSupported() {
- return globalThis.showOpenFilePicker !== void 0;
- }
-};
-export {
- StorageProvider
-};
-//# sourceMappingURL=storage.js.map