drawnui-react 0.1.0-preview.15 → 0.1.0-preview.17

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.
Files changed (64) hide show
  1. package/README.md +1 -1
  2. package/SKIPPED.md +9 -8
  3. package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
  4. package/dist/controls/AnimatedFramesRenderer.js +16 -1
  5. package/dist/controls/DrawnGame.d.ts +4 -1
  6. package/dist/controls/DrawnGame.js +7 -3
  7. package/dist/controls/GridStructure.d.ts +13 -1
  8. package/dist/controls/GridStructure.js +38 -2
  9. package/dist/controls/SkiaBackdrop.d.ts +2 -0
  10. package/dist/controls/SkiaBackdrop.js +2 -0
  11. package/dist/controls/SkiaButton.d.ts +2 -0
  12. package/dist/controls/SkiaButton.js +2 -0
  13. package/dist/controls/SkiaCarousel.d.ts +3 -1
  14. package/dist/controls/SkiaCarousel.js +5 -2
  15. package/dist/controls/SkiaDrawer.d.ts +2 -0
  16. package/dist/controls/SkiaDrawer.js +2 -0
  17. package/dist/controls/SkiaEditor.d.ts +5 -1
  18. package/dist/controls/SkiaEditor.js +29 -18
  19. package/dist/controls/SkiaGif.d.ts +3 -0
  20. package/dist/controls/SkiaGif.js +11 -1
  21. package/dist/controls/SkiaImage.js +15 -10
  22. package/dist/controls/SkiaLabel.d.ts +54 -3
  23. package/dist/controls/SkiaLabel.js +235 -52
  24. package/dist/controls/SkiaLayout.d.ts +20 -0
  25. package/dist/controls/SkiaLayout.js +78 -6
  26. package/dist/controls/SkiaLottie.js +3 -1
  27. package/dist/controls/SkiaProgress.d.ts +2 -1
  28. package/dist/controls/SkiaProgress.js +6 -3
  29. package/dist/controls/SkiaScroll.d.ts +16 -0
  30. package/dist/controls/SkiaScroll.js +85 -8
  31. package/dist/controls/SkiaShape.d.ts +13 -2
  32. package/dist/controls/SkiaShape.js +50 -10
  33. package/dist/controls/SkiaSlider.d.ts +7 -1
  34. package/dist/controls/SkiaSlider.js +12 -4
  35. package/dist/controls/SkiaSprite.d.ts +1 -0
  36. package/dist/controls/SkiaSprite.js +4 -0
  37. package/dist/controls/SkiaToggle.d.ts +2 -0
  38. package/dist/controls/SkiaToggle.js +3 -1
  39. package/dist/controls/SnappingLayout.d.ts +7 -0
  40. package/dist/controls/SnappingLayout.js +11 -0
  41. package/dist/core/Accessibility.d.ts +21 -0
  42. package/dist/core/Accessibility.js +66 -9
  43. package/dist/core/Animators.d.ts +28 -1
  44. package/dist/core/Animators.js +76 -8
  45. package/dist/core/Canvas.d.ts +26 -4
  46. package/dist/core/Canvas.js +136 -23
  47. package/dist/core/Easing.d.ts +12 -1
  48. package/dist/core/Easing.js +25 -1
  49. package/dist/core/Gestures.d.ts +24 -1
  50. package/dist/core/Gestures.js +75 -2
  51. package/dist/core/Hover.d.ts +47 -0
  52. package/dist/core/Hover.js +134 -0
  53. package/dist/core/KeyboardManager.d.ts +13 -0
  54. package/dist/core/KeyboardManager.js +18 -0
  55. package/dist/core/ScrollAnimators.js +9 -0
  56. package/dist/core/SkiaControl.d.ts +97 -1
  57. package/dist/core/SkiaControl.js +271 -38
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.js +1 -0
  60. package/dist/react/SkiaShell.js +33 -14
  61. package/dist/react/index.d.ts +1 -1
  62. package/dist/react/index.js +30 -3
  63. package/dist/react/reconciler.js +29 -10
  64. package/package.json +18 -1
@@ -0,0 +1,134 @@
1
+ import { SKPoint } from "./Gestures";
2
+ /**
3
+ * DrawnUI hover (drawnui-cross 6m, C# DrawnView ReportHover / CommitHover): every control under the mouse that takes
4
+ * hover (`ReceivesHover`) is hovered, a card and the button inside it alike, as CSS :hover. Decided from the hit path
5
+ * of one pointer position, by the routing rules of ProcessGestures (visible, not InputTransparent, inside, children
6
+ * not locked, nothing below a control that blocks gestures below).
7
+ * While content moves under the pointer (a scroll glide, fling or pan, a carousel or drawer transition) hover is not
8
+ * tracked and what was hovered stays; one check at the last pointer position follows when it stops, and after
9
+ * controls are added or removed, a cell is rebound, a hovered control is hidden or a scroll jumps. Leaving the canvas
10
+ * clears hover at once. Touch never hovers. No allocation on frames where nothing changes.
11
+ */
12
+ export class HoverManager {
13
+ /** The hovered controls, root first. */
14
+ Hovered = [];
15
+ next = [];
16
+ /** The controls under the pointer at the last hit test, root first, and the pointer in each one's own space. */
17
+ Path = [];
18
+ PathX = [];
19
+ PathY = [];
20
+ movers = [];
21
+ hasPointer = false;
22
+ x = 0;
23
+ y = 0;
24
+ paused = false;
25
+ check = false;
26
+ /**
27
+ * The mouse (or a pen in range) moved to (x, y), canvas pixels, nothing pressed. The hit path is taken also while
28
+ * hover waits (the cursor follows it), hover changes only when nothing moves.
29
+ */
30
+ Move(root, x, y) {
31
+ this.hasPointer = true;
32
+ this.x = x;
33
+ this.y = y;
34
+ this.HitTest(root);
35
+ if (this.paused)
36
+ return; // content moves under the pointer: what was hovered stays, one check when it stops
37
+ this.check = false;
38
+ this.Apply(true);
39
+ }
40
+ /** Fills Path with the controls under the pointer by the routing rules of ProcessGestures. */
41
+ HitTest(root) {
42
+ const path = this.Path;
43
+ path.length = 0;
44
+ this.PathX.length = 0;
45
+ this.PathY.length = 0;
46
+ if (root && root.IsVisible && !root.InputTransparent) {
47
+ const point = root.TransformPointToLocalSpace(new SKPoint(this.x, this.y));
48
+ if (root.HitIsInside(point.X, point.Y))
49
+ root.CollectHovered(point, path, this.PathX, this.PathY);
50
+ }
51
+ }
52
+ /** The pointer left the canvas: nothing is hovered, also while content moves. */
53
+ Leave() {
54
+ this.hasPointer = false;
55
+ this.check = false;
56
+ this.Path.length = 0;
57
+ this.PathX.length = 0;
58
+ this.PathY.length = 0;
59
+ this.Apply(false);
60
+ }
61
+ /** The controls under a still pointer may have changed: checked once at the end of the frame. */
62
+ RequestCheck() { if (this.hasPointer)
63
+ this.check = true; }
64
+ /** A control that moves its content (MovesContent) while drawn: hover waits while any of them moves. */
65
+ RegisterMover(control) { if (this.movers.indexOf(control) < 0)
66
+ this.movers.push(control); }
67
+ /** End of a frame: pause while content moves, else run a pending check (at most one a frame, none while pressed). */
68
+ AfterFrame(root, pressed) {
69
+ let moving = false;
70
+ for (let i = this.movers.length - 1; i >= 0; i--) {
71
+ const m = this.movers[i];
72
+ if (m.IsDisposed || !m.Superview) {
73
+ this.movers.splice(i, 1);
74
+ continue;
75
+ }
76
+ if (!moving && m.IsVisible && m.MovesContent())
77
+ moving = true;
78
+ }
79
+ if (moving) {
80
+ this.paused = true;
81
+ return;
82
+ }
83
+ if (this.paused) {
84
+ this.paused = false;
85
+ this.check = true;
86
+ }
87
+ // a hovered control (or an ancestor) hidden or removed under a still pointer stops being hovered
88
+ for (let i = 0; i < this.Hovered.length && !this.check; i++)
89
+ if (!HoverManager.Shown(this.Hovered[i]))
90
+ this.check = true;
91
+ if (!this.check)
92
+ return;
93
+ this.check = false;
94
+ if (!this.hasPointer || pressed)
95
+ return;
96
+ this.HitTest(root);
97
+ this.Apply(true);
98
+ }
99
+ static Shown(control) {
100
+ if (control.IsDisposed || !control.Superview)
101
+ return false;
102
+ for (let c = control; c; c = c.Parent)
103
+ if (!c.IsVisible)
104
+ return false;
105
+ return true;
106
+ }
107
+ /**
108
+ * The hovered set becomes the controls of the hit path that take hover (`fromPath`), or none: the ones that left get
109
+ * HoverChanged(false), then the new ones (true).
110
+ */
111
+ Apply(fromPath) {
112
+ const now = this.Hovered, next = this.next;
113
+ next.length = 0;
114
+ if (fromPath)
115
+ for (const c of this.Path)
116
+ if (c.ReceivesHover)
117
+ next.push(c);
118
+ if (now.length === next.length) {
119
+ let same = true;
120
+ for (let i = 0; i < now.length && same; i++)
121
+ same = now[i] === next[i];
122
+ if (same)
123
+ return;
124
+ }
125
+ const was = now.splice(0);
126
+ now.push(...next);
127
+ for (const c of was)
128
+ if (now.indexOf(c) < 0)
129
+ c.ApplyHover(false);
130
+ for (const c of now)
131
+ if (was.indexOf(c) < 0)
132
+ c.ApplyHover(true);
133
+ }
134
+ }
@@ -20,6 +20,19 @@ export declare class KeyboardManager {
20
20
  static AttachToKeyboard(): void;
21
21
  static Subscribe(down: (key: InputKey, e: KeyboardEvent) => void, char: (ch: string, e: KeyboardEvent) => void, up?: (key: InputKey, e: KeyboardEvent) => void): void;
22
22
  static Unsubscribe(down: (key: InputKey, e: KeyboardEvent) => void, char: (ch: string, e: KeyboardEvent) => void, up?: (key: InputKey, e: KeyboardEvent) => void): void;
23
+ /**
24
+ * Hooks observe keys, they never take them from the element that owns them (C# 845b26e9, GitHub #231). True when a
25
+ * focused DOM element is the key's target: a page input, textarea, select, button, link or contenteditable, an
26
+ * accessibility overlay node, the drawn editor's hidden textarea. False when the key goes to the page itself (body,
27
+ * html) or the canvas. A handler that acts on keys (edits, moves, calls preventDefault) skips owned keys, except
28
+ * those of its own element.
29
+ */
30
+ static IsOwnedByElement(e: KeyboardEvent): boolean;
31
+ /**
32
+ * The key belongs to the input method (an IME composition, a soft keyboard): isComposing, keyCode 229, key "Process"
33
+ * or "Unidentified". Its result arrives as input events (TextInputProxy), never act on the key itself.
34
+ */
35
+ static IsInputMethodKey(e: KeyboardEvent): boolean;
23
36
  /** JS blurExternalTextInput: a page text input outside the canvas would keep receiving the keys. */
24
37
  static BlurExternalTextInput(): void;
25
38
  }
@@ -45,6 +45,24 @@ export class KeyboardManager {
45
45
  if (up)
46
46
  KeyboardManager.keyUp.delete(up);
47
47
  }
48
+ /**
49
+ * Hooks observe keys, they never take them from the element that owns them (C# 845b26e9, GitHub #231). True when a
50
+ * focused DOM element is the key's target: a page input, textarea, select, button, link or contenteditable, an
51
+ * accessibility overlay node, the drawn editor's hidden textarea. False when the key goes to the page itself (body,
52
+ * html) or the canvas. A handler that acts on keys (edits, moves, calls preventDefault) skips owned keys, except
53
+ * those of its own element.
54
+ */
55
+ static IsOwnedByElement(e) {
56
+ const tag = e.target?.tagName;
57
+ return !!tag && tag !== "BODY" && tag !== "HTML" && tag !== "CANVAS";
58
+ }
59
+ /**
60
+ * The key belongs to the input method (an IME composition, a soft keyboard): isComposing, keyCode 229, key "Process"
61
+ * or "Unidentified". Its result arrives as input events (TextInputProxy), never act on the key itself.
62
+ */
63
+ static IsInputMethodKey(e) {
64
+ return e.isComposing || e.keyCode === 229 || e.key === "Process" || e.key === "Unidentified";
65
+ }
48
66
  /** JS blurExternalTextInput: a page text input outside the canvas would keep receiving the keys. */
49
67
  static BlurExternalTextInput() {
50
68
  const a = typeof document !== "undefined" ? document.activeElement : null;
@@ -1,6 +1,7 @@
1
1
  // Mirrors DrawnUi scrolling physics: DecelerationTimingParameters + ScrollFlingAnimator (fling),
2
2
  // Spring + SpringTimingParameters + SpringWithVelocityAnimator (bounce), VelocityAccumulator, RubberBandUtils.
3
3
  import { SkiaValueAnimator } from "./Animators";
4
+ import { VelocityTrail } from "./Gestures";
4
5
  /** UIKit-style exponential deceleration: value(t) = v0 * (rate^(1000 t) - 1) / k, k = 1000 ln(rate). */
5
6
  export class DecelerationTimingParameters {
6
7
  InitialValue;
@@ -201,6 +202,14 @@ export class VelocityAccumulator {
201
202
  Clear() { this.samples.length = 0; }
202
203
  CaptureVelocity(x, y, arrivedTimeNanos = 0) {
203
204
  const time = arrivedTimeNanos > 0 ? arrivedTimeNanos / 1e6 : performance.now();
205
+ // a move in the same burst as the last one replaces it: the first move of a burst carries only part of the burst's
206
+ // distance (DrawnUi.Rust VelocityAccumulator, 6d525f7)
207
+ const last = this.samples[this.samples.length - 1];
208
+ if (last && time - last.time < VelocityTrail.BurstMs) {
209
+ last.X = x;
210
+ last.Y = y;
211
+ return;
212
+ }
204
213
  if (this.samples.length === VelocityAccumulator.MaxSampleSize)
205
214
  this.samples.shift();
206
215
  this.samples.push({ X: x, Y: y, time });
@@ -138,18 +138,43 @@ export declare class SkiaControl {
138
138
  private compositeSurface?;
139
139
  /** Own content or structure changed: the next composite record is a full one. */
140
140
  private compositeFull;
141
+ /** Where the kept composite surface starts relative to DrawingRect (the effects margin it was laid out with). */
142
+ private compositeInsetX;
143
+ private compositeInsetY;
141
144
  /** Canvas-pixel bounds this control covered when its parent last recorded it (composite erase region). */
142
145
  private lastCompositeBounds?;
143
- /** What the last composite record did (diagnostics): "full" or "partial", the number of children re-recorded and which ones. */
146
+ /**
147
+ * What the last composite record did (diagnostics): "full" or "partial", the number of children re-recorded and which
148
+ * ones; Areas: the pixels erased and drawn again for changes deeper than a child (C# Areas), Changed: the controls
149
+ * those areas belong to (C# Changed).
150
+ */
144
151
  LastCompositeRecord: {
145
152
  Mode: "full" | "partial";
146
153
  Children: number;
147
154
  Dirty: readonly SkiaControl[];
155
+ Areas: readonly SKRect[];
156
+ Changed: readonly SkiaControl[];
148
157
  };
158
+ /** C# MaxCompositionAreas: more areas than this in one composite record draw it whole instead. */
159
+ static MaxCompositionAreas: number;
160
+ /** C# MaxCompositionShare: areas covering more than this share of a composite draw it whole instead. */
161
+ static MaxCompositionShare: number;
162
+ /**
163
+ * Changes deeper than a direct child since the last composite record (drawnui-cross 6m): the control, the direct child
164
+ * it is in, where it was drawn before the change (its own drawn bounds, as drawn) and its rect then.
165
+ */
166
+ private compositeDeep?;
167
+ /** Set while an UpdateDraw climbs: the ancestors re-measure but an ImageComposite among them keeps its partial record. */
168
+ private static drawOrigin;
149
169
  /** The children a composite record can re-record individually; layouts return their views. */
150
170
  protected GetCompositeChildren(): readonly SkiaControl[];
151
171
  /** C# TrackChildAsDirty (DirtyChildrenTracker): the child changed without a remeasure of this control. */
152
172
  TrackChildAsDirty(child: SkiaControl): void;
173
+ private static readonly rejectRect;
174
+ /** Its cache must be recorded before it can be drawn (none, dirty, another type, scale, size or GPU context). */
175
+ private CacheStale;
176
+ /** Its rect grown by its effects margin, through the matrix it is drawn with, lies outside the canvas clip. */
177
+ private OutsideClip;
153
178
  /** Drawn bounds in canvas pixels including transforms and effects margins (C# GetTransformedDirtyBounds). */
154
179
  GetTransformedDirtyBounds(): SKRect;
155
180
  /** Current cache, if any. */
@@ -160,6 +185,36 @@ export declare class SkiaControl {
160
185
  /** Consume every gesture that lands on this control so nothing below (z-order) receives it. */
161
186
  BlockGesturesBelow: boolean;
162
187
  LockChildrenGestures: LockTouch;
188
+ private receivesHover?;
189
+ /**
190
+ * Whether this control takes mouse hover: the canvas sets IsHovered while the mouse is over it. Opt-in: false by
191
+ * default, true by default only in the controls that always had hover (SkiaButton, SkiaSlider, the toggles,
192
+ * SkiaRadioButton, SkiaCarousel, SkiaDrawer), never because a control has a tap handler. A HoverChanged handler turns
193
+ * it on (C# fluent OnHovered) unless it is set.
194
+ */
195
+ get ReceivesHover(): boolean;
196
+ set ReceivesHover(value: boolean);
197
+ /** The value of ReceivesHover until it is set. */
198
+ protected ReceivesHoverByDefault(): boolean;
199
+ private isHovered;
200
+ /**
201
+ * True while the mouse is over this control and it takes hover. Every such control under the pointer is hovered at
202
+ * once (a card and the button inside it, as CSS :hover); it stays as it is while content moves under the pointer and
203
+ * is checked again when that stops. Touch never hovers.
204
+ */
205
+ get IsHovered(): boolean;
206
+ /** IsHovered changed: true when the mouse came over this control, false when it left (C# HoverChanged / OnHovered). */
207
+ HoverChanged?: (sender: SkiaControl, hovered: boolean) => void;
208
+ /** Set by the canvas's HoverManager. */
209
+ ApplyHover(on: boolean): void;
210
+ /** A scroll, carousel or drawer moving its content right now: hover waits until it stops (C# IsScrolling / InTransition). */
211
+ MovesContent(): boolean;
212
+ /**
213
+ * Hover hit path (HoverManager): this control is under the point (in its own space), its children under it follow,
214
+ * top-most first, by the routing rules of ProcessGestures. True when this subtree blocks gestures below it, so the
215
+ * controls under it get no hover (a popup over a list).
216
+ */
217
+ CollectHovered(point: SKPoint, into: SkiaControl[], xs?: number[], ys?: number[]): boolean;
163
218
  TouchEffectColor: Color;
164
219
  /** Plays a ripple (from the tap point) when Tapped fires. */
165
220
  AnimationTapped: SkiaTouchAnimation;
@@ -211,6 +266,11 @@ export declare class SkiaControl {
211
266
  * built in code-behind is styled on its first measure and keeps whatever the code already set.
212
267
  */
213
268
  ApplyInitialStyles(atConstruction?: boolean): void;
269
+ /**
270
+ * C# IsChildrenItem: added by the app (JSX children or the `Children` list), not a part the control made itself. A new
271
+ * `Children` list drops only these, so a control's own parts keep their place.
272
+ */
273
+ IsChildrenItem: boolean;
214
274
  AddSubView(_control: SkiaControl): void;
215
275
  InsertSubView(_index: number, control: SkiaControl): void;
216
276
  RemoveSubView(_control: SkiaControl): void;
@@ -272,6 +332,8 @@ export declare class SkiaControl {
272
332
  * host's pointer cursor. Default: the control as a whole (AccessibilityCanInteract); SkiaLabel adds its tappable spans.
273
333
  */
274
334
  WantsPointerCursor(_x: number, _y: number): boolean;
335
+ /** A range control's value for screen readers (slider, progress); undefined for everything else (drawnui-cross 6c). */
336
+ get AccessibilityValue(): import("./Accessibility").AccessibilityValue | undefined;
275
337
  /** aria-pressed for toggles; undefined = not a toggle. */
276
338
  get AccessibilityIsPressed(): boolean | undefined;
277
339
  set AccessibilityIsPressed(v: boolean | undefined);
@@ -411,11 +473,45 @@ export declare class SkiaControl {
411
473
  Update(): void;
412
474
  /** Redraw without remeasure and WITHOUT dropping caches (position/overlay changes). */
413
475
  Repaint(): void;
476
+ /**
477
+ * A parent (not the control itself) is hidden: a change here asks for no frame and the latest state shows when the
478
+ * parent is shown again (C# 329f6c44 HiddenUpdateTests, DrawnUi.Rust hidden_updates). The control's own visibility
479
+ * change still draws: it is the one that hides or shows it.
480
+ */
481
+ UnderHiddenParent(): boolean;
482
+ /** The control or a parent is hidden: its animators pause until it shows again. */
483
+ HiddenInTree(): boolean;
484
+ /** The control an invalidation started at while it climbs (InvalidateMeasure): a hidden parent of it wakes no frame. */
485
+ private static invalidateOrigin;
414
486
  /**
415
487
  * Transform / Opacity changed: this control's own cache is still valid (content unchanged) but every ancestor
416
488
  * cache holds its composited output, so those are staled before redrawing (DrawnUi RedrawCanvas + parent invalidation).
417
489
  */
418
490
  RepaintComposition(): void;
491
+ /**
492
+ * Update() for a change of how this control looks, not of its size (a color set through props, C# NeedDraw): the same
493
+ * re-measure and cache invalidation, but an ImageComposite above redraws only this control's area instead of
494
+ * recording whole (drawnui-cross 6m). The record still goes whole when the control's rect changed after all.
495
+ */
496
+ UpdateDraw(): void;
497
+ /**
498
+ * A change of this control that keeps its layout, to every ImageComposite above: a direct child is drawn again whole
499
+ * (C# TrackChildAsDirty), a deeper control by its area, read now as it was drawn (its last matrix and effects margin).
500
+ */
501
+ private ReportToComposites;
502
+ /** The matrix this control will be drawn with (Render), from its current properties; undefined when none. */
503
+ private CurrentMatrix;
504
+ /** Where this control will be drawn: its rect grown by its effects margin, through its current matrix. */
505
+ private DrawnBoundsNow;
506
+ private static MapRect;
507
+ /** Its output depends on all of its content (a visual effect; a backdrop samples below): a change inside redraws it whole. */
508
+ protected get SamplesContent(): boolean;
509
+ /**
510
+ * Where a deep change shows inside the composite (drawnui-cross 6m rule b): the control as drawn before and as drawn
511
+ * now, then through the transform of every ancestor up to the composite. undefined: draw the child whole (an effect
512
+ * or a backdrop on the way, the control gone from the child); null: the layout changed, record whole.
513
+ */
514
+ private static DeepArea;
419
515
  /**
420
516
  * DrawnUi IsParentIndependent: this control's size never changes the layout around it, so its own invalidation
421
517
  * stops here (ancestors composite again, nothing above re-measures). The parent re-measures it on draw when