drawnui-react 0.1.0-preview.7 → 0.1.0-preview.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKIPPED.md CHANGED
@@ -51,6 +51,7 @@ DrawnUi name and semantics; nothing here is a redesign, only an omission.
51
51
  | Mouse cursor over interactive controls (Blazor `Canvas.razor` `.xaml-a11y-interactive { cursor: pointer }`) | ported (differently) | The overlay here is `pointer-events: none`, so `Canvas.UpdateCursor` hit-tests mouse moves with no active pointer against the accessibility snapshot rects (`CanInteract` = has `Tapped`, buttons, links) and asks each hit control `WantsPointerCursor(x, y)` (default `AccessibilityCanInteract`; `SkiaLabel` also answers for its tappable `TextSpan`s), setting the canvas element's `cursor` only when the answer changes. Touch pointers do nothing. Engine hover (`IsHovered` / `OnHover`, `TouchActionResult.Pointer`) stays skipped, see Gestures. |
52
52
  | `ContextMenu` (right click / long press / Menu key, React extension) | ported (web-only) | `SkiaControl.ContextMenu` / `Canvas.ContextMenu` handlers with `ContextMenuEventArgs` (`Location` points, `Pixels`, `Local` px in the control, `Source`, `Native`); the DOM `contextmenu` event is routed like a tap (`ProcessContextMenu`: visible non-transparent children top-most first through `TransformPointToLocalSpace`, then the control, then the canvas). `true` = handled, browser menu suppressed; nothing handled = browser menu. Not in DrawnUi.Net. |
53
53
  | Keyboard, focus (`FocusedChild`) | skipped | |
54
+ | Canvas draw events | ported | `WillFirstTimeDraw` (once, right before the first frame drawn on a ready surface, with that frame's `{ Canvas, Surface }`), `WasDrawn` (after every frame, no context, like the C# `null`), `WasRendered` (set after the first frame completes, so the first `WasDrawn` still sees `false`, C# order), `CanRender` (surface ready). JSX: `<Canvas WillFirstTimeDraw WasDrawn>`; the engine draws frame 1 in its constructor, so the component hands both callbacks to the constructor's `init`. Not ported: `WillDraw`. |
54
55
  | Rendering stats | partial | `Canvas.FPS` (frames drawn over the last second) and `Canvas.FrameTime` (ms of the last measure + arrange + render, GPU flush excluded) are computed every frame and reachable from React through the `<Canvas>` ref. Not ported: the `SkiaLabelFps` control, `Super.EnableRenderingStats`. |
55
56
  | Insets / safe areas | ported | `Super.Insets` measures `env(safe-area-inset-*)` off a probe element (once, and again on resize / orientation), `Super.OnInsetsChanged` notifies; `SkiaShell` pads its nav and tab bars with them. Not ported: the C# per-page `IgnoreSafeArea` / MAUI inset plumbing. |
56
57
 
@@ -1,5 +1,5 @@
1
1
  import type { Surface } from "canvaskit-wasm";
2
- import type { SkiaControl } from "./SkiaControl";
2
+ import type { DrawingContext, SkiaControl } from "./SkiaControl";
3
3
  import type { AnimatorBase } from "./Animators";
4
4
  import { SkiaAccessibilityManager } from "./Accessibility";
5
5
  import { type Color, type RenderingModeType } from "./Types";
@@ -23,6 +23,18 @@ export declare class Canvas {
23
23
  /** Frames per second over the last second of drawn frames. */
24
24
  FPS: number;
25
25
  private frameTimes;
26
+ /** DrawnView.WasRendered: a frame has been drawn on a ready surface (set after the first one completes). */
27
+ WasRendered: boolean;
28
+ /** DrawnView.CanRender: the surface (and its GPU context when accelerated) is ready to draw. */
29
+ get CanRender(): boolean;
30
+ /**
31
+ * DrawnView.WillFirstTimeDraw: raised once, right before the first frame drawn on a ready surface, with that
32
+ * frame's drawing context. The engine draws its first frame while it is being constructed (so the page never shows
33
+ * an unpainted canvas): pass it through the constructor's `init` to receive that frame.
34
+ */
35
+ WillFirstTimeDraw?: (sender: Canvas, context: DrawingContext["Context"]) => void;
36
+ /** DrawnView.WasDrawn: raised after every frame has been drawn (the C# event passes no context). */
37
+ WasDrawn?: (sender: Canvas) => void;
26
38
  /** Registry + rate-limited snapshot of accessible controls, rendered by the DOM overlay (DrawnUi AccessibilityManager). */
27
39
  readonly AccessibilityManager: SkiaAccessibilityManager;
28
40
  private content?;
@@ -35,7 +47,8 @@ export declare class Canvas {
35
47
  private frameId;
36
48
  private disposed;
37
49
  private readonly observer;
38
- constructor(Element: HTMLCanvasElement);
50
+ /** `init` runs before the first frame is drawn: the place to attach `WillFirstTimeDraw` / `WasDrawn` for that frame. */
51
+ constructor(Element: HTMLCanvasElement, init?: (canvas: Canvas) => void);
39
52
  /** Request a redraw (full measure + arrange + render) on the next animation frame. */
40
53
  Update(): void;
41
54
  /** Deletes the current surface; a pending frame on it is cancelled first (drawing on a deleted surface faults). */
@@ -21,6 +21,18 @@ export class Canvas {
21
21
  /** Frames per second over the last second of drawn frames. */
22
22
  FPS = 0;
23
23
  frameTimes = [];
24
+ /** DrawnView.WasRendered: a frame has been drawn on a ready surface (set after the first one completes). */
25
+ WasRendered = false;
26
+ /** DrawnView.CanRender: the surface (and its GPU context when accelerated) is ready to draw. */
27
+ get CanRender() { return !!this.surface && !this.disposed; }
28
+ /**
29
+ * DrawnView.WillFirstTimeDraw: raised once, right before the first frame drawn on a ready surface, with that
30
+ * frame's drawing context. The engine draws its first frame while it is being constructed (so the page never shows
31
+ * an unpainted canvas): pass it through the constructor's `init` to receive that frame.
32
+ */
33
+ WillFirstTimeDraw;
34
+ /** DrawnView.WasDrawn: raised after every frame has been drawn (the C# event passes no context). */
35
+ WasDrawn;
24
36
  /** Registry + rate-limited snapshot of accessible controls, rendered by the DOM overlay (DrawnUi AccessibilityManager). */
25
37
  AccessibilityManager = new SkiaAccessibilityManager();
26
38
  content;
@@ -42,10 +54,12 @@ export class Canvas {
42
54
  frameId = 0;
43
55
  disposed = false;
44
56
  observer;
45
- constructor(Element) {
57
+ /** `init` runs before the first frame is drawn: the place to attach `WillFirstTimeDraw` / `WasDrawn` for that frame. */
58
+ constructor(Element, init) {
46
59
  this.Element = Element;
47
60
  if (!Super.CK)
48
61
  throw new Error("DrawnUi: call Super.UseDrawnUi()...BuildAsync() before creating a Canvas");
62
+ init?.(this);
49
63
  this.observer = new ResizeObserver(() => this.OnResized());
50
64
  this.observer.observe(Element);
51
65
  this.OnResized();
@@ -122,6 +136,9 @@ export class Canvas {
122
136
  const started = performance.now();
123
137
  this.ProcessPendingGestures();
124
138
  const executed = this.ExecuteAnimators(Math.round(started * 1_000_000));
139
+ const canRender = this.CanRender;
140
+ if (canRender && !this.WasRendered)
141
+ this.WillFirstTimeDraw?.(this, { Canvas: canvas, Surface: this.surface });
125
142
  canvas.clear(Super.ParseColor(this.BackgroundColor));
126
143
  const root = this.content;
127
144
  if (root) {
@@ -141,6 +158,10 @@ export class Canvas {
141
158
  this.FPS = this.frameTimes.length;
142
159
  if (executed > 0)
143
160
  this.Update(); // animators running: keep frames coming
161
+ // C# order: WasDrawn at the end of the frame, then the frame counts as rendered
162
+ this.WasDrawn?.(this);
163
+ if (!this.WasRendered && canRender)
164
+ this.WasRendered = true;
144
165
  }
145
166
  // ---- deferred disposal (DrawnUi DisposeObject: never delete Skia objects mid-frame) ----
146
167
  disposeQueue = [];
@@ -395,6 +416,10 @@ export class Canvas {
395
416
  y = true;
396
417
  if (!x && !clips(hs.overflowX) && !clips(bs.overflowX) && root.scrollWidth > root.clientWidth + 1)
397
418
  x = true;
419
+ // a canvas filling a framed document (a widget in an iframe): the page that scrolls is the parent, which a
420
+ // cross-origin frame cannot inspect, so assume it scrolls vertically; horizontal pans and taps stay on the canvas
421
+ if (!y && window.self !== window.top)
422
+ y = true;
398
423
  return x && y ? "pan-x pan-y" : y ? "pan-y" : x ? "pan-x" : "none";
399
424
  }
400
425
  /**
@@ -113,12 +113,16 @@ export interface CanvasProps {
113
113
  ref?: Ref<CanvasView>;
114
114
  /** Context-menu request (right click / long press / Menu key) no control handled; return true to suppress the browser menu. */
115
115
  ContextMenu?: CanvasView["ContextMenu"];
116
+ /** DrawnView.WillFirstTimeDraw: once, right before the first frame is drawn, with its drawing context. */
117
+ WillFirstTimeDraw?: CanvasView["WillFirstTimeDraw"];
118
+ /** DrawnView.WasDrawn: after every drawn frame. */
119
+ WasDrawn?: CanvasView["WasDrawn"];
116
120
  }
117
121
  /**
118
122
  * Mirrors DrawnUi Canvas: the bridge between the DOM (react-dom) and the drawn tree (DrawnUi reconciler).
119
123
  * Requires Super.UseDrawnUi()...BuildAsync() to have completed.
120
124
  */
121
- export declare function Canvas({ BackgroundColor, RenderingMode, Gestures, children, style, className, ref: viewRef, ContextMenu }: CanvasProps): import("react").JSX.Element;
125
+ export declare function Canvas({ BackgroundColor, RenderingMode, Gestures, children, style, className, ref: viewRef, ContextMenu, WillFirstTimeDraw, WasDrawn }: CanvasProps): import("react").JSX.Element;
122
126
  export type { SkiaControl };
123
127
  export * from "../index";
124
128
  export * from "./SkiaShell";
@@ -66,15 +66,19 @@ function removeStaticContent() {
66
66
  * Mirrors DrawnUi Canvas: the bridge between the DOM (react-dom) and the drawn tree (DrawnUi reconciler).
67
67
  * Requires Super.UseDrawnUi()...BuildAsync() to have completed.
68
68
  */
69
- export function Canvas({ BackgroundColor, RenderingMode, Gestures, children, style, className, ref: viewRef, ContextMenu }) {
69
+ export function Canvas({ BackgroundColor, RenderingMode, Gestures, children, style, className, ref: viewRef, ContextMenu, WillFirstTimeDraw, WasDrawn }) {
70
70
  const ref = useRef(null);
71
71
  const view = useRef(null);
72
72
  const root = useRef(null);
73
73
  const [engine, setEngine] = useState(null);
74
74
  useLayoutEffect(() => {
75
- const v = new CanvasView(ref.current);
76
- if (RenderingMode)
77
- v.RenderingMode = RenderingMode;
75
+ // the engine draws frame 1 inside its constructor: the draw events and the rendering mode must be on it before that
76
+ const v = new CanvasView(ref.current, (c) => {
77
+ if (RenderingMode)
78
+ c.RenderingMode = RenderingMode;
79
+ c.WillFirstTimeDraw = WillFirstTimeDraw;
80
+ c.WasDrawn = WasDrawn;
81
+ });
78
82
  view.current = v;
79
83
  root.current = createDrawnRoot(v);
80
84
  setEngine(v);
@@ -93,6 +97,8 @@ export function Canvas({ BackgroundColor, RenderingMode, Gestures, children, sty
93
97
  }
94
98
  v.Gestures = Gestures ?? "Disabled";
95
99
  v.ContextMenu = ContextMenu;
100
+ v.WillFirstTimeDraw = WillFirstTimeDraw;
101
+ v.WasDrawn = WasDrawn;
96
102
  root.current.render(children);
97
103
  });
98
104
  // The drawn surface is aria-hidden; the overlay is the accessibility tree (DrawnUi.Blazor pattern).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drawnui-react",
3
- "version": "0.1.0-preview.7",
3
+ "version": "0.1.0-preview.8",
4
4
  "description": "DrawnUi for React: the DrawnUi (.NET) drawn-UI engine in TypeScript on CanvasKit (Skia WASM). Same controls, same PascalCase API as DrawnUi.Net.",
5
5
  "license": "MIT",
6
6
  "author": "Nick Kovalsky (AppoMobi)",