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.
- package/README.md +1 -1
- package/SKIPPED.md +9 -8
- package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
- package/dist/controls/AnimatedFramesRenderer.js +16 -1
- package/dist/controls/DrawnGame.d.ts +4 -1
- package/dist/controls/DrawnGame.js +7 -3
- package/dist/controls/GridStructure.d.ts +13 -1
- package/dist/controls/GridStructure.js +38 -2
- package/dist/controls/SkiaBackdrop.d.ts +2 -0
- package/dist/controls/SkiaBackdrop.js +2 -0
- package/dist/controls/SkiaButton.d.ts +2 -0
- package/dist/controls/SkiaButton.js +2 -0
- package/dist/controls/SkiaCarousel.d.ts +3 -1
- package/dist/controls/SkiaCarousel.js +5 -2
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.d.ts +5 -1
- package/dist/controls/SkiaEditor.js +29 -18
- package/dist/controls/SkiaGif.d.ts +3 -0
- package/dist/controls/SkiaGif.js +11 -1
- package/dist/controls/SkiaImage.js +15 -10
- package/dist/controls/SkiaLabel.d.ts +54 -3
- package/dist/controls/SkiaLabel.js +235 -52
- package/dist/controls/SkiaLayout.d.ts +20 -0
- package/dist/controls/SkiaLayout.js +78 -6
- package/dist/controls/SkiaLottie.js +3 -1
- package/dist/controls/SkiaProgress.d.ts +2 -1
- package/dist/controls/SkiaProgress.js +6 -3
- package/dist/controls/SkiaScroll.d.ts +16 -0
- package/dist/controls/SkiaScroll.js +85 -8
- package/dist/controls/SkiaShape.d.ts +13 -2
- package/dist/controls/SkiaShape.js +50 -10
- package/dist/controls/SkiaSlider.d.ts +7 -1
- package/dist/controls/SkiaSlider.js +12 -4
- package/dist/controls/SkiaSprite.d.ts +1 -0
- package/dist/controls/SkiaSprite.js +4 -0
- package/dist/controls/SkiaToggle.d.ts +2 -0
- package/dist/controls/SkiaToggle.js +3 -1
- package/dist/controls/SnappingLayout.d.ts +7 -0
- package/dist/controls/SnappingLayout.js +11 -0
- package/dist/core/Accessibility.d.ts +21 -0
- package/dist/core/Accessibility.js +66 -9
- package/dist/core/Animators.d.ts +28 -1
- package/dist/core/Animators.js +76 -8
- package/dist/core/Canvas.d.ts +26 -4
- package/dist/core/Canvas.js +136 -23
- package/dist/core/Easing.d.ts +12 -1
- package/dist/core/Easing.js +25 -1
- package/dist/core/Gestures.d.ts +24 -1
- package/dist/core/Gestures.js +75 -2
- package/dist/core/Hover.d.ts +47 -0
- package/dist/core/Hover.js +134 -0
- package/dist/core/KeyboardManager.d.ts +13 -0
- package/dist/core/KeyboardManager.js +18 -0
- package/dist/core/ScrollAnimators.js +9 -0
- package/dist/core/SkiaControl.d.ts +97 -1
- package/dist/core/SkiaControl.js +271 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/SkiaShell.js +33 -14
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +30 -3
- package/dist/react/reconciler.js +29 -10
- package/package.json +18 -1
package/dist/react/index.js
CHANGED
|
@@ -126,6 +126,13 @@ const NAVIGATION_KEYS = new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDow
|
|
|
126
126
|
* canvas. Keyboard and screen-reader input arrive as DOM events: Enter / Space activate (a Tapped), the navigation keys
|
|
127
127
|
* go to the focused control and then to its arrow-key group; a group is one Tab stop (roving tabindex).
|
|
128
128
|
*/
|
|
129
|
+
/** aria-valuenow / min / max / valuetext of a range control (drawnui-cross 6c); a slider reads as horizontal. */
|
|
130
|
+
function valueAttributes(n) {
|
|
131
|
+
const v = n.Value;
|
|
132
|
+
if (!v)
|
|
133
|
+
return undefined;
|
|
134
|
+
return { "aria-valuenow": v.Now, "aria-valuemin": v.Min, "aria-valuemax": v.Max, "aria-valuetext": v.Text || undefined, "aria-orientation": n.Role === "slider" ? "horizontal" : undefined };
|
|
135
|
+
}
|
|
129
136
|
function AccessibilityOverlay({ view }) {
|
|
130
137
|
const [nodes, setNodes] = useState(() => view.AccessibilityManager.Snapshot);
|
|
131
138
|
// keyboard focus moved by the manager (arrow keys in a group): focus that node's element once it is rendered
|
|
@@ -147,6 +154,23 @@ function AccessibilityOverlay({ view }) {
|
|
|
147
154
|
setFocusRequest(undefined);
|
|
148
155
|
elementOf(focusRequest.id)?.focus({ preventScroll: true });
|
|
149
156
|
}, [focusRequest]);
|
|
157
|
+
// the focused node went away (its page or popup closed): the focus goes to the first Tab stop on screen in reading
|
|
158
|
+
// order instead of falling back to the page (drawnui-cross 6c refocus); a focus the user moved away stays away
|
|
159
|
+
const focusedId = useRef(0);
|
|
160
|
+
useLayoutEffect(() => {
|
|
161
|
+
const id = focusedId.current;
|
|
162
|
+
if (!id || nodes.some((n) => n.Id === id))
|
|
163
|
+
return;
|
|
164
|
+
focusedId.current = 0;
|
|
165
|
+
const active = document.activeElement;
|
|
166
|
+
if (active && active !== document.body)
|
|
167
|
+
return;
|
|
168
|
+
const box = overlayRef.current?.getBoundingClientRect();
|
|
169
|
+
const first = nodes.find((n) => n.CanInteract && view.AccessibilityManager.IsTabStop(n.Source)
|
|
170
|
+
&& (!box || (n.Rect.Bottom > 0 && n.Rect.Top < box.height && n.Rect.Right > 0 && n.Rect.Left < box.width)));
|
|
171
|
+
if (first)
|
|
172
|
+
elementOf(first.Id)?.focus({ preventScroll: true });
|
|
173
|
+
}, [nodes]);
|
|
150
174
|
// Tab / Shift+Tab in a text field leave it for the next node, also after a click into it: DOM focus goes to the
|
|
151
175
|
// field's own element (without taking the caret again) and the browser's Tab moves on from there
|
|
152
176
|
const tabbingOut = useRef(0);
|
|
@@ -198,7 +222,7 @@ function AccessibilityOverlay({ view }) {
|
|
|
198
222
|
return (_jsxs("div", { className: "drawnui-a11y-overlay", onScroll: pin, ref: overlayRef, children: [_jsx("style", { children: A11Y_CSS }), nodes.map((n) => {
|
|
199
223
|
const pos = { left: n.Rect.Left, top: n.Rect.Top, width: n.Rect.Width, height: n.Rect.Height };
|
|
200
224
|
const activate = () => SkiaAccessibilityManager.Activate(n.Source);
|
|
201
|
-
return n.CanInteract ? (_jsx("div", { "data-a11y-id": n.Id, role: n.Role, "aria-label": n.Label, title: n.Hint, "aria-pressed": n.Role === "button" ? n.IsPressed : undefined, "aria-checked": n.Role === "switch" || n.Role === "checkbox" || n.Role === "radio" ? n.IsPressed : undefined, "aria-live": n.Live, tabIndex: view.AccessibilityManager.IsTabStop(n.Source) ? 0 : -1, className: "drawnui-a11y-node", style: { ...pos, fontSize: 0, userSelect: "none" }, onClick: activate, onKeyDown: (e) => {
|
|
225
|
+
return n.CanInteract ? (_jsx("div", { "data-a11y-id": n.Id, role: n.Role, "aria-label": n.Label, title: n.Hint, ...valueAttributes(n), "aria-pressed": n.Role === "button" ? n.IsPressed : undefined, "aria-checked": n.Role === "switch" || n.Role === "checkbox" || n.Role === "radio" ? n.IsPressed : undefined, "aria-live": n.Live, tabIndex: view.AccessibilityManager.IsTabStop(n.Source) ? 0 : -1, className: "drawnui-a11y-node", style: { ...pos, fontSize: 0, userSelect: "none" }, onClick: activate, onKeyDown: (e) => {
|
|
202
226
|
if (e.key === "Enter" || e.key === " ") {
|
|
203
227
|
e.preventDefault();
|
|
204
228
|
activate();
|
|
@@ -206,6 +230,7 @@ function AccessibilityOverlay({ view }) {
|
|
|
206
230
|
else if (NAVIGATION_KEYS.has(e.key) && SkiaAccessibilityManager.Key(n.Source, e.key))
|
|
207
231
|
e.preventDefault();
|
|
208
232
|
}, onFocus: (e) => {
|
|
233
|
+
focusedId.current = n.Id;
|
|
209
234
|
pin({ currentTarget: e.currentTarget.parentElement });
|
|
210
235
|
SkiaScrollCtrl.EnsureVisible(n.Source); // inside every enclosing scroll
|
|
211
236
|
if (tabbingOut.current === n.Id)
|
|
@@ -215,15 +240,17 @@ function AccessibilityOverlay({ view }) {
|
|
|
215
240
|
n.Source.NotifyAccessibilityFocused(true);
|
|
216
241
|
setFocusTick((t) => t + 1);
|
|
217
242
|
}, onBlur: (e) => {
|
|
243
|
+
if (e.currentTarget.isConnected && focusedId.current === n.Id)
|
|
244
|
+
focusedId.current = 0; // moved away on purpose
|
|
218
245
|
if (TextInputProxy.IsProxyTarget(e.relatedTarget))
|
|
219
246
|
return; // a text field moved DOM focus to its own input
|
|
220
247
|
n.Source.OnAccessibilityFocused(false);
|
|
221
248
|
n.Source.NotifyAccessibilityFocused(false);
|
|
222
249
|
}, children: n.Label }, n.Id)) : n.TextLines ? (
|
|
223
250
|
// AccessibilityTextSelectable: one positioned span per drawn line, in the drawn font, so the browser selects and copies it like HTML
|
|
224
|
-
_jsx("div", { role: n.Role, title: n.Hint, "aria-live": n.Live, className: "drawnui-a11y-node drawnui-a11y-text", style: pos, children: n.TextLines.map((l, i) => (_jsxs("span", { "data-w": l.Width, style: { left: l.Left, top: l.Top, height: l.Height, fontFamily: l.FontFamily, fontWeight: l.FontWeight, fontSize: l.FontSize, lineHeight: `${l.Height}px` }, children: [l.Text, i < n.TextLines.length - 1 ? "\n" : ""] }, i))) }, n.Id)) : (
|
|
251
|
+
_jsx("div", { role: n.Role, title: n.Hint, "aria-disabled": n.Disabled || undefined, "aria-live": n.Live, className: "drawnui-a11y-node drawnui-a11y-text", style: pos, children: n.TextLines.map((l, i) => (_jsxs("span", { "data-w": l.Width, style: { left: l.Left, top: l.Top, height: l.Height, fontFamily: l.FontFamily, fontWeight: l.FontWeight, fontSize: l.FontSize, lineHeight: `${l.Height}px` }, children: [l.Text, i < n.TextLines.length - 1 ? "\n" : ""] }, i))) }, n.Id)) : (
|
|
225
252
|
// static text is exposed as real (transparent) text content; aria-label only for roles that need a name
|
|
226
|
-
_jsx("div", { role: n.Role, "aria-label": n.Role === "text" ? undefined : n.Label, title: n.Hint, "aria-live": n.Live, className: "drawnui-a11y-node", style: pos, children: n.Label }, n.Id));
|
|
253
|
+
_jsx("div", { role: n.Role, "aria-label": n.Role === "text" ? undefined : n.Label, title: n.Hint, ...valueAttributes(n), "aria-disabled": n.Disabled || undefined, "aria-live": n.Live, className: "drawnui-a11y-node", style: pos, children: n.Label }, n.Id));
|
|
227
254
|
})] }));
|
|
228
255
|
}
|
|
229
256
|
// One import for apps: React tags + every engine type (Colors, Thickness, Super, gestures, animators...).
|
package/dist/react/reconciler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createContext } from "react";
|
|
2
2
|
import Reconciler from "react-reconciler";
|
|
3
3
|
import { ConcurrentRoot, DefaultEventPriority } from "react-reconciler/constants";
|
|
4
|
+
import { SkiaControl } from "../core/SkiaControl";
|
|
4
5
|
import { SkiaLabel } from "../controls/SkiaLabel";
|
|
5
6
|
import { TextSpan } from "../controls/TextSpan";
|
|
6
7
|
import { SkiaRichLabel } from "../controls/SkiaRichLabel";
|
|
@@ -38,12 +39,14 @@ export const Registry = {
|
|
|
38
39
|
const SKIP = new Set(["children", "key", "ref"]);
|
|
39
40
|
/** Props that change how the subtree is composited, not what it contains: repaint, keep caches (DrawnUi RedrawCanvas). */
|
|
40
41
|
const REPAINT_ONLY = new Set(["TranslationX", "TranslationY", "Rotation", "ScaleX", "ScaleY", "Scale", "SkewX", "SkewY", "AnchorX", "AnchorY", "Opacity"]);
|
|
42
|
+
/** Props that change only how a control looks, never its size (C# NeedDraw): an ImageComposite above redraws only its area. */
|
|
43
|
+
const isDrawOnly = (k) => k.endsWith("Color") || k === "FillGradient" || k === "StrokeGradient";
|
|
41
44
|
/**
|
|
42
45
|
* Assigns changed props straight onto the control (same names as the C# properties).
|
|
43
46
|
* Handler props (functions) are swapped without invalidating: inline arrows change identity every render.
|
|
44
47
|
*/
|
|
45
48
|
function applyProps(inst, prev, next) {
|
|
46
|
-
let changed = false, repaint = false;
|
|
49
|
+
let changed = false, repaint = false, layout = false;
|
|
47
50
|
for (const k in next) {
|
|
48
51
|
if (SKIP.has(k) || (prev && prev[k] === next[k]))
|
|
49
52
|
continue;
|
|
@@ -52,23 +55,36 @@ function applyProps(inst, prev, next) {
|
|
|
52
55
|
continue;
|
|
53
56
|
if (REPAINT_ONLY.has(k))
|
|
54
57
|
repaint = true;
|
|
55
|
-
else
|
|
58
|
+
else {
|
|
56
59
|
changed = true;
|
|
60
|
+
if (!isDrawOnly(k))
|
|
61
|
+
layout = true;
|
|
62
|
+
}
|
|
57
63
|
}
|
|
58
64
|
if (prev)
|
|
59
65
|
for (const k in prev)
|
|
60
66
|
if (!SKIP.has(k) && !(k in next)) {
|
|
61
67
|
inst[k] = new inst.constructor()[k];
|
|
62
|
-
changed = true;
|
|
68
|
+
changed = layout = true;
|
|
63
69
|
}
|
|
64
|
-
if (changed && prev)
|
|
65
|
-
inst
|
|
70
|
+
if (changed && prev) {
|
|
71
|
+
if (layout || !(inst instanceof SkiaControl))
|
|
72
|
+
inst.Update();
|
|
73
|
+
else
|
|
74
|
+
inst.UpdateDraw();
|
|
75
|
+
}
|
|
66
76
|
else if (repaint && prev)
|
|
67
77
|
inst.RepaintComposition?.();
|
|
68
78
|
}
|
|
69
79
|
let currentUpdatePriority = DefaultEventPriority;
|
|
70
80
|
const noop = () => { };
|
|
71
81
|
const hostContext = {};
|
|
82
|
+
function appendChild(parent, child) {
|
|
83
|
+
if (parent instanceof SkiaLayout)
|
|
84
|
+
return parent.InsertChild(child);
|
|
85
|
+
child.IsChildrenItem = true;
|
|
86
|
+
parent.AddSubView(child);
|
|
87
|
+
}
|
|
72
88
|
// Record<string, unknown> absorbs members that react-reconciler 0.33 reads but @types 0.32 does not declare yet.
|
|
73
89
|
const hostConfig = {
|
|
74
90
|
supportsMutation: true,
|
|
@@ -94,12 +110,15 @@ const hostConfig = {
|
|
|
94
110
|
createTextInstance(text) {
|
|
95
111
|
throw new Error(`DrawnUi: raw text "${text}" is not allowed, use <SkiaLabel Text="..." />`);
|
|
96
112
|
},
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
// JSX children are the app's children (C# IsChildrenItem): a layout keeps them in JSX order inside the slots they
|
|
114
|
+
// occupy, so its own parts keep their place; an existing child passed again is a move
|
|
115
|
+
appendInitialChild: (parent, child) => appendChild(parent, child),
|
|
116
|
+
appendChild: (parent, child) => appendChild(parent, child),
|
|
99
117
|
insertBefore: (parent, child, before) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
if (parent instanceof SkiaLayout)
|
|
119
|
+
return parent.InsertChild(child, before);
|
|
120
|
+
child.IsChildrenItem = true;
|
|
121
|
+
parent.InsertSubView(parent instanceof SkiaLabel ? parent.Spans.indexOf(before) : 0, child);
|
|
103
122
|
},
|
|
104
123
|
removeChild: (parent, child) => parent.RemoveSubView(child),
|
|
105
124
|
appendChildToContainer: (canvas, child) => { canvas.Content = child; },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drawnui-react",
|
|
3
|
-
"version": "0.1.0-preview.
|
|
3
|
+
"version": "0.1.0-preview.17",
|
|
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)",
|
|
@@ -56,6 +56,23 @@
|
|
|
56
56
|
"check:recycling": "node dev/check.mjs dev/check-recycling.ts",
|
|
57
57
|
"check:keyboard": "node dev/check.mjs dev/check-keyboard.ts",
|
|
58
58
|
"check:wheel": "node dev/check.mjs dev/check-wheel.ts",
|
|
59
|
+
"check:children": "node dev/check.mjs dev/check-children.tsx",
|
|
60
|
+
"check:composite": "node dev/check.mjs dev/check-composite.tsx",
|
|
61
|
+
"check:hover": "node dev/check.mjs dev/check-hover.ts",
|
|
62
|
+
"check:grid-label": "node dev/check.mjs dev/check-grid-label.ts",
|
|
63
|
+
"check:snap": "node dev/check.mjs dev/check-snap.ts",
|
|
64
|
+
"check:game": "node dev/check.mjs dev/check-game.ts",
|
|
65
|
+
"check:animation": "node dev/check.mjs dev/check-animation.ts",
|
|
66
|
+
"check:shape": "node dev/check.mjs dev/check-shape.ts",
|
|
67
|
+
"check:label": "node dev/check.mjs dev/check-label.ts",
|
|
68
|
+
"check:image": "node dev/check.mjs dev/check-image.ts",
|
|
69
|
+
"check:longpress": "node dev/check.mjs dev/check-longpress.ts",
|
|
70
|
+
"check:hidden": "node dev/check.mjs dev/check-hidden.ts",
|
|
71
|
+
"check:shell": "node dev/check.mjs dev/check-shell.tsx",
|
|
72
|
+
"check:wheel-routing": "node dev/check.mjs dev/check-wheel-routing.ts",
|
|
73
|
+
"check:velocity": "node dev/check.mjs dev/check-velocity.ts",
|
|
74
|
+
"check:scroll-index": "node dev/check.mjs dev/check-scroll-index.ts",
|
|
75
|
+
"check:culling": "node dev/check.mjs dev/check-culling.ts",
|
|
59
76
|
"site:extras": "node dev/site-extras.mjs dist/demo",
|
|
60
77
|
"build:lib": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
61
78
|
"prepublishOnly": "npm run build:lib",
|