drawnui-react 0.1.0-preview.1

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/PARITY.md +287 -0
  3. package/README.md +92 -0
  4. package/SKIPPED.md +107 -0
  5. package/dist/controls/AnimatedFramesRenderer.d.ts +58 -0
  6. package/dist/controls/AnimatedFramesRenderer.js +133 -0
  7. package/dist/controls/GridStructure.d.ts +79 -0
  8. package/dist/controls/GridStructure.js +403 -0
  9. package/dist/controls/RefreshIndicator.d.ts +35 -0
  10. package/dist/controls/RefreshIndicator.js +82 -0
  11. package/dist/controls/SkiaBackdrop.d.ts +25 -0
  12. package/dist/controls/SkiaBackdrop.js +80 -0
  13. package/dist/controls/SkiaButton.d.ts +58 -0
  14. package/dist/controls/SkiaButton.js +148 -0
  15. package/dist/controls/SkiaCarousel.d.ts +133 -0
  16. package/dist/controls/SkiaCarousel.js +603 -0
  17. package/dist/controls/SkiaCheckbox.d.ts +21 -0
  18. package/dist/controls/SkiaCheckbox.js +118 -0
  19. package/dist/controls/SkiaDecoratedGrid.d.ts +15 -0
  20. package/dist/controls/SkiaDecoratedGrid.js +46 -0
  21. package/dist/controls/SkiaDrawer.d.ts +46 -0
  22. package/dist/controls/SkiaDrawer.js +212 -0
  23. package/dist/controls/SkiaDynamicDrawnCell.d.ts +11 -0
  24. package/dist/controls/SkiaDynamicDrawnCell.js +17 -0
  25. package/dist/controls/SkiaEditor.d.ts +163 -0
  26. package/dist/controls/SkiaEditor.js +803 -0
  27. package/dist/controls/SkiaGif.d.ts +54 -0
  28. package/dist/controls/SkiaGif.js +206 -0
  29. package/dist/controls/SkiaHotspot.d.ts +13 -0
  30. package/dist/controls/SkiaHotspot.js +36 -0
  31. package/dist/controls/SkiaImage.d.ts +75 -0
  32. package/dist/controls/SkiaImage.js +222 -0
  33. package/dist/controls/SkiaImageTiles.d.ts +27 -0
  34. package/dist/controls/SkiaImageTiles.js +76 -0
  35. package/dist/controls/SkiaLabel.d.ts +160 -0
  36. package/dist/controls/SkiaLabel.js +580 -0
  37. package/dist/controls/SkiaLayout.d.ts +163 -0
  38. package/dist/controls/SkiaLayout.js +723 -0
  39. package/dist/controls/SkiaLottie.d.ts +75 -0
  40. package/dist/controls/SkiaLottie.js +290 -0
  41. package/dist/controls/SkiaProgress.d.ts +40 -0
  42. package/dist/controls/SkiaProgress.js +80 -0
  43. package/dist/controls/SkiaRadioButton.d.ts +32 -0
  44. package/dist/controls/SkiaRadioButton.js +157 -0
  45. package/dist/controls/SkiaRichLabel.d.ts +77 -0
  46. package/dist/controls/SkiaRichLabel.js +274 -0
  47. package/dist/controls/SkiaScroll.d.ts +231 -0
  48. package/dist/controls/SkiaScroll.js +958 -0
  49. package/dist/controls/SkiaScrollBar.d.ts +56 -0
  50. package/dist/controls/SkiaScrollBar.js +158 -0
  51. package/dist/controls/SkiaShaderCarousel.d.ts +67 -0
  52. package/dist/controls/SkiaShaderCarousel.js +246 -0
  53. package/dist/controls/SkiaShape.d.ts +63 -0
  54. package/dist/controls/SkiaShape.js +317 -0
  55. package/dist/controls/SkiaSlider.d.ts +74 -0
  56. package/dist/controls/SkiaSlider.js +243 -0
  57. package/dist/controls/SkiaSprite.d.ts +99 -0
  58. package/dist/controls/SkiaSprite.js +324 -0
  59. package/dist/controls/SkiaSpriteSet.d.ts +27 -0
  60. package/dist/controls/SkiaSpriteSet.js +75 -0
  61. package/dist/controls/SkiaSvg.d.ts +43 -0
  62. package/dist/controls/SkiaSvg.js +181 -0
  63. package/dist/controls/SkiaSwitch.d.ts +26 -0
  64. package/dist/controls/SkiaSwitch.js +147 -0
  65. package/dist/controls/SkiaToggle.d.ts +50 -0
  66. package/dist/controls/SkiaToggle.js +71 -0
  67. package/dist/controls/SnappingLayout.d.ts +78 -0
  68. package/dist/controls/SnappingLayout.js +217 -0
  69. package/dist/controls/TextSpan.d.ts +70 -0
  70. package/dist/controls/TextSpan.js +76 -0
  71. package/dist/core/Accessibility.d.ts +104 -0
  72. package/dist/core/Accessibility.js +150 -0
  73. package/dist/core/Animators.d.ts +82 -0
  74. package/dist/core/Animators.js +247 -0
  75. package/dist/core/Canvas.d.ts +89 -0
  76. package/dist/core/Canvas.js +328 -0
  77. package/dist/core/ControlStyle.d.ts +6 -0
  78. package/dist/core/ControlStyle.js +11 -0
  79. package/dist/core/Easing.d.ts +13 -0
  80. package/dist/core/Easing.js +15 -0
  81. package/dist/core/Gestures.d.ts +74 -0
  82. package/dist/core/Gestures.js +104 -0
  83. package/dist/core/ImageEffects.d.ts +29 -0
  84. package/dist/core/ImageEffects.js +55 -0
  85. package/dist/core/KeyboardManager.d.ts +25 -0
  86. package/dist/core/KeyboardManager.js +54 -0
  87. package/dist/core/ScrollAnimators.d.ts +90 -0
  88. package/dist/core/ScrollAnimators.js +244 -0
  89. package/dist/core/SkiaControl.d.ts +401 -0
  90. package/dist/core/SkiaControl.js +1239 -0
  91. package/dist/core/SkiaEffect.d.ts +216 -0
  92. package/dist/core/SkiaEffect.js +571 -0
  93. package/dist/core/SkiaImageManager.d.ts +43 -0
  94. package/dist/core/SkiaImageManager.js +122 -0
  95. package/dist/core/Super.d.ts +61 -0
  96. package/dist/core/Super.js +178 -0
  97. package/dist/core/TextInputProxy.d.ts +31 -0
  98. package/dist/core/TextInputProxy.js +144 -0
  99. package/dist/core/Types.d.ts +177 -0
  100. package/dist/core/Types.js +122 -0
  101. package/dist/core/ViewsAdapter.d.ts +41 -0
  102. package/dist/core/ViewsAdapter.js +113 -0
  103. package/dist/index.d.ts +48 -0
  104. package/dist/index.js +48 -0
  105. package/dist/react/SkiaShell.d.ts +144 -0
  106. package/dist/react/SkiaShell.js +490 -0
  107. package/dist/react/index.d.ts +110 -0
  108. package/dist/react/index.js +158 -0
  109. package/dist/react/reconciler.d.ts +14 -0
  110. package/dist/react/reconciler.js +160 -0
  111. package/package.json +60 -0
@@ -0,0 +1,110 @@
1
+ import { type CSSProperties, type FC, type ReactNode, type Ref } from "react";
2
+ import { Canvas as CanvasView } from "../core/Canvas";
3
+ import type { SkiaControl } from "../core/SkiaControl";
4
+ import type { SkiaLabel as SkiaLabelCtrl } from "../controls/SkiaLabel";
5
+ import type { TextSpan as TextSpanCtrl } from "../controls/TextSpan";
6
+ import type { SkiaRichLabel as SkiaRichLabelCtrl } from "../controls/SkiaRichLabel";
7
+ import type { SkiaSwitch as SkiaSwitchCtrl } from "../controls/SkiaSwitch";
8
+ import type { SkiaCheckbox as SkiaCheckboxCtrl } from "../controls/SkiaCheckbox";
9
+ import type { SkiaRadioButton as SkiaRadioButtonCtrl } from "../controls/SkiaRadioButton";
10
+ import type { SkiaProgress as SkiaProgressCtrl } from "../controls/SkiaProgress";
11
+ import type { SkiaSlider as SkiaSliderCtrl } from "../controls/SkiaSlider";
12
+ import type { SkiaCarousel as SkiaCarouselCtrl } from "../controls/SkiaCarousel";
13
+ import type { SkiaShaderCarousel as SkiaShaderCarouselCtrl } from "../controls/SkiaShaderCarousel";
14
+ import type { SkiaDrawer as SkiaDrawerCtrl } from "../controls/SkiaDrawer";
15
+ import type { SkiaLayout as SkiaLayoutCtrl } from "../controls/SkiaLayout";
16
+ import type { SkiaHotspot as SkiaHotspotCtrl } from "../controls/SkiaHotspot";
17
+ import type { SkiaButton as SkiaButtonCtrl } from "../controls/SkiaButton";
18
+ import type { SkiaImage as SkiaImageCtrl } from "../controls/SkiaImage";
19
+ import type { SkiaImageTiles as SkiaImageTilesCtrl } from "../controls/SkiaImageTiles";
20
+ import type { SkiaDecoratedGrid as SkiaDecoratedGridCtrl } from "../controls/SkiaDecoratedGrid";
21
+ import type { SkiaScrollBar as SkiaScrollBarCtrl } from "../controls/SkiaScrollBar";
22
+ import type { RefreshIndicator as RefreshIndicatorCtrl } from "../controls/RefreshIndicator";
23
+ import type { SkiaSvg as SkiaSvgCtrl } from "../controls/SkiaSvg";
24
+ import type { SkiaBackdrop as SkiaBackdropCtrl } from "../controls/SkiaBackdrop";
25
+ import type { SkiaEditor as SkiaEditorCtrl } from "../controls/SkiaEditor";
26
+ import type { SkiaSprite as SkiaSpriteCtrl } from "../controls/SkiaSprite";
27
+ import type { SkiaSpriteSet as SkiaSpriteSetCtrl } from "../controls/SkiaSpriteSet";
28
+ import type { SkiaLottie as SkiaLottieCtrl } from "../controls/SkiaLottie";
29
+ import type { SkiaGif as SkiaGifCtrl } from "../controls/SkiaGif";
30
+ import { SkiaScroll as SkiaScrollCtrl } from "../controls/SkiaScroll";
31
+ import type { SkiaShape as SkiaShapeCtrl } from "../controls/SkiaShape";
32
+ import type { Color, RenderingModeType } from "../core/Types";
33
+ import type { GesturesMode } from "../core/Gestures";
34
+ /** Public settable properties of a control become its JSX props, same PascalCase names as C#. */
35
+ type PropsOf<T> = Partial<{
36
+ [K in keyof T as T[K] extends Function ? never : K extends "Children" | "Views" | "Parent" | "Spans" | "GridStructure" | "AccessibilityId" | "IsAccessibilityElement" | "HasTransform" | "RenderTransformMatrix" | "RenderObjectPrevious" | "LastMeasuredIndex" | "ItemsInsertedAtStart" | "UsingControlStyle" | "Track" | "Thumb" | "FrameOn" | "FrameOff" | "ViewCheckOn" | "ViewOn" | "ViewText" | "Ratio" | "StartThumbX" | "EndThumbX" | "SnapPoints" | "CurrentPosition" | "CurrentSnap" | "ContentOffsetBounds" | "InTransition" | "CanAnimate" | "MaxIndex" | "ChildrenTotal" | "IsAtStart" | "IsAtEnd" | "ScrollProgress" | "ScrollAmount" | "TransitionProgress" | "LastIndex" | "ChildrenCount" | "Horizontal" | "Animator" | "Animation" | "IsPlaying" | "PlayWhenAvailable" | "TotalFrames" | "HasEffects" | "IsDisposed" | "Label" | "IsMultiline" | "HasSelection" | "LayoutVersion" | "LinesCount" | "MeasuredLineHeight" | "Focused" | "SpriteSheet" | "FrameWidth" | "FrameHeight" | "DurationMs" | "FrameDurationMs" | "CurrentSprite" | "Rects" | "HasTapHandler" | "HasDecorations" | "LinesCount" | "Superview" | "DrawingRect" | "MeasuredSize" | "RenderingScale" | "NeedMeasure" | "_superview" | "HitBoxAuto" | "TotalDown" | "TotalTapped" | "TouchDown" | "PostAnimators" | "LoadedSource" | "IsLoading" | "DisplayRect" | "AspectScale" | "Content" | "ContentSize" | "ContentOffsetBounds" | "OverscrollDistance" | "OverScrolled" | "IsUserPanning" | "IsUserFocused" | "IsScrolling" | "IsTemplated" | "FirstVisibleIndex" | "LastVisibleIndex" | "DebugString" | "ChildrenFactory" | "ContextIndex" | "RenderObject" | "UsingCacheType" | "TransitionEffect" | "TransitionFromIndex" | "TransitionToIndex" | "EffectPostRenderers" | "CachedImage" | "ImageBitmap" | "ShouldSubmitOnEnter" | "Header" | "Footer" | "RefreshIndicator" | "ScrollBar" | "ScrollBarHorizontal" | "IsRunning" | "VisibleRatio" | "CurrentIndex" | "LastCompositeRecord" | "DirtyChildrenInternal" | "IsRenderingWithComposition" ? never : K]: T[K];
37
+ }>;
38
+ /** `ref` receives the engine control instance (react-reconciler getPublicInstance). */
39
+ type LeafProps<T> = PropsOf<T> & {
40
+ ref?: Ref<T>;
41
+ };
42
+ type LayoutProps<T> = PropsOf<T> & {
43
+ children?: ReactNode;
44
+ ref?: Ref<T>;
45
+ };
46
+ /** Typed JSX tags resolved by the reconciler Registry. */
47
+ export declare const SkiaLayout: FC<LayoutProps<SkiaLayoutCtrl>>;
48
+ export declare const SkiaStack: FC<LayoutProps<SkiaLayoutCtrl>>;
49
+ export declare const SkiaRow: FC<LayoutProps<SkiaLayoutCtrl>>;
50
+ export declare const SkiaLayer: FC<LayoutProps<SkiaLayoutCtrl>>;
51
+ export declare const SkiaWrap: FC<LayoutProps<SkiaLayoutCtrl>>;
52
+ export declare const SkiaGrid: FC<LayoutProps<SkiaLayoutCtrl>>;
53
+ /** Grid drawing gradient separator lines in its spacing (C# SkiaDecoratedGrid). */
54
+ export declare const SkiaDecoratedGrid: FC<LayoutProps<SkiaDecoratedGridCtrl>>;
55
+ export declare const SkiaLabel: FC<LayoutProps<SkiaLabelCtrl>>;
56
+ /** Markdown label (C# SkiaRichLabel): Text is markdown, rendered as spans; LinkTapped for [text](url). */
57
+ export declare const SkiaRichLabel: FC<LeafProps<SkiaRichLabelCtrl>>;
58
+ /** Child of <SkiaLabel>: a styled fragment (C# TextSpan). */
59
+ export declare const TextSpan: FC<LeafProps<TextSpanCtrl>>;
60
+ export declare const SkiaHotspot: FC<LeafProps<SkiaHotspotCtrl>>;
61
+ export declare const SkiaButton: FC<LeafProps<SkiaButtonCtrl>>;
62
+ export declare const SkiaImage: FC<LeafProps<SkiaImageCtrl>>;
63
+ /** Source repeated as TileWidth x TileHeight tiles over the box (C# SkiaImageTiles). */
64
+ export declare const SkiaImageTiles: FC<LeafProps<SkiaImageTilesCtrl>>;
65
+ export declare const SkiaSvg: FC<LeafProps<SkiaSvgCtrl>>;
66
+ /** Blurs / tints what is painted beneath it (C# SkiaBackdrop); children draw first. */
67
+ export declare const SkiaBackdrop: FC<LayoutProps<SkiaBackdropCtrl>>;
68
+ /** Drawn text input (C# SkiaEditor): Text, PlaceholderText, MaxLines, IsPassword, ControlStyle, TextChanged / TextSubmitted. */
69
+ export declare const SkiaEditor: FC<LeafProps<SkiaEditorCtrl>>;
70
+ /** Spritesheet player (C# SkiaSprite): Source, Columns, Rows, FramesPerSecond, FrameSequence. */
71
+ export declare const SkiaSprite: FC<LeafProps<SkiaSpriteCtrl>>;
72
+ /** Stateful sprite switcher (C# SkiaSpriteSet): Define() per State through a ref. */
73
+ export declare const SkiaSpriteSet: FC<LayoutProps<SkiaSpriteSetCtrl>>;
74
+ export declare const SkiaLottie: FC<LeafProps<SkiaLottieCtrl>>;
75
+ export declare const SkiaGif: FC<LeafProps<SkiaGifCtrl>>;
76
+ export declare const SkiaScroll: FC<LayoutProps<SkiaScrollCtrl>>;
77
+ /** Default scroll bar overlay (C# SkiaScrollBar); as a SkiaScroll child with Tag="ScrollBar" / "ScrollBarHorizontal". */
78
+ export declare const SkiaScrollBar: FC<LayoutProps<SkiaScrollBarCtrl>>;
79
+ /** Pull-to-refresh view base (C# RefreshIndicator); as a SkiaScroll child with Tag="RefreshIndicator", put the visuals inside. */
80
+ export declare const RefreshIndicator: FC<LayoutProps<RefreshIndicatorCtrl>>;
81
+ export declare const SkiaShape: FC<LayoutProps<SkiaShapeCtrl>>;
82
+ export declare const SkiaFrame: FC<LayoutProps<SkiaShapeCtrl>>;
83
+ export declare const SkiaSwitch: FC<LeafProps<SkiaSwitchCtrl>>;
84
+ export declare const SkiaCheckbox: FC<LeafProps<SkiaCheckboxCtrl>>;
85
+ export declare const SkiaRadioButton: FC<LeafProps<SkiaRadioButtonCtrl>>;
86
+ export declare const SkiaProgress: FC<LeafProps<SkiaProgressCtrl>>;
87
+ export declare const SkiaSlider: FC<LeafProps<SkiaSliderCtrl>>;
88
+ export declare const SkiaCarousel: FC<LayoutProps<SkiaCarouselCtrl>>;
89
+ /** Carousel whose slide changes are rendered by an SkSL transition (C# SkiaShaderCarousel); slides need UseCache="Image". */
90
+ export declare const SkiaShaderCarousel: FC<LayoutProps<SkiaShaderCarouselCtrl>>;
91
+ export declare const SkiaDrawer: FC<LayoutProps<SkiaDrawerCtrl>>;
92
+ export interface CanvasProps {
93
+ BackgroundColor?: Color;
94
+ RenderingMode?: RenderingModeType;
95
+ /** Disabled (default) / Enabled / Lock, like DrawnUi Canvas.Gestures. */
96
+ Gestures?: GesturesMode;
97
+ children?: ReactNode;
98
+ style?: CSSProperties;
99
+ className?: string;
100
+ /** Receives the engine Canvas (FPS, FrameTime, RenderingScale...). */
101
+ ref?: Ref<CanvasView>;
102
+ }
103
+ /**
104
+ * Mirrors DrawnUi Canvas: the bridge between the DOM (react-dom) and the drawn tree (DrawnUi reconciler).
105
+ * Requires Super.UseDrawnUi()...BuildAsync() to have completed.
106
+ */
107
+ export declare function Canvas({ BackgroundColor, RenderingMode, Gestures, children, style, className, ref: viewRef }: CanvasProps): import("react").JSX.Element;
108
+ export type { SkiaControl };
109
+ export * from "../index";
110
+ export * from "./SkiaShell";
@@ -0,0 +1,158 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
3
+ import { Canvas as CanvasView } from "../core/Canvas";
4
+ import { SkiaScroll as SkiaScrollCtrl } from "../controls/SkiaScroll";
5
+ import { createDrawnRoot } from "./reconciler";
6
+ /** Typed JSX tags resolved by the reconciler Registry. */
7
+ export const SkiaLayout = "SkiaLayout";
8
+ export const SkiaStack = "SkiaStack";
9
+ export const SkiaRow = "SkiaRow";
10
+ export const SkiaLayer = "SkiaLayer";
11
+ export const SkiaWrap = "SkiaWrap";
12
+ export const SkiaGrid = "SkiaGrid";
13
+ /** Grid drawing gradient separator lines in its spacing (C# SkiaDecoratedGrid). */
14
+ export const SkiaDecoratedGrid = "SkiaDecoratedGrid";
15
+ export const SkiaLabel = "SkiaLabel";
16
+ /** Markdown label (C# SkiaRichLabel): Text is markdown, rendered as spans; LinkTapped for [text](url). */
17
+ export const SkiaRichLabel = "SkiaRichLabel";
18
+ /** Child of <SkiaLabel>: a styled fragment (C# TextSpan). */
19
+ export const TextSpan = "TextSpan";
20
+ export const SkiaHotspot = "SkiaHotspot";
21
+ export const SkiaButton = "SkiaButton";
22
+ export const SkiaImage = "SkiaImage";
23
+ /** Source repeated as TileWidth x TileHeight tiles over the box (C# SkiaImageTiles). */
24
+ export const SkiaImageTiles = "SkiaImageTiles";
25
+ export const SkiaSvg = "SkiaSvg";
26
+ /** Blurs / tints what is painted beneath it (C# SkiaBackdrop); children draw first. */
27
+ export const SkiaBackdrop = "SkiaBackdrop";
28
+ /** Drawn text input (C# SkiaEditor): Text, PlaceholderText, MaxLines, IsPassword, ControlStyle, TextChanged / TextSubmitted. */
29
+ export const SkiaEditor = "SkiaEditor";
30
+ /** Spritesheet player (C# SkiaSprite): Source, Columns, Rows, FramesPerSecond, FrameSequence. */
31
+ export const SkiaSprite = "SkiaSprite";
32
+ /** Stateful sprite switcher (C# SkiaSpriteSet): Define() per State through a ref. */
33
+ export const SkiaSpriteSet = "SkiaSpriteSet";
34
+ export const SkiaLottie = "SkiaLottie";
35
+ export const SkiaGif = "SkiaGif";
36
+ export const SkiaScroll = "SkiaScroll";
37
+ /** Default scroll bar overlay (C# SkiaScrollBar); as a SkiaScroll child with Tag="ScrollBar" / "ScrollBarHorizontal". */
38
+ export const SkiaScrollBar = "SkiaScrollBar";
39
+ /** Pull-to-refresh view base (C# RefreshIndicator); as a SkiaScroll child with Tag="RefreshIndicator", put the visuals inside. */
40
+ export const RefreshIndicator = "RefreshIndicator";
41
+ export const SkiaShape = "SkiaShape";
42
+ export const SkiaFrame = "SkiaFrame";
43
+ export const SkiaSwitch = "SkiaSwitch";
44
+ export const SkiaCheckbox = "SkiaCheckbox";
45
+ export const SkiaRadioButton = "SkiaRadioButton";
46
+ export const SkiaProgress = "SkiaProgress";
47
+ export const SkiaSlider = "SkiaSlider";
48
+ export const SkiaCarousel = "SkiaCarousel";
49
+ /** Carousel whose slide changes are rendered by an SkSL transition (C# SkiaShaderCarousel); slides need UseCache="Image". */
50
+ export const SkiaShaderCarousel = "SkiaShaderCarousel";
51
+ export const SkiaDrawer = "SkiaDrawer";
52
+ /**
53
+ * Mirrors DrawnUi Canvas: the bridge between the DOM (react-dom) and the drawn tree (DrawnUi reconciler).
54
+ * Requires Super.UseDrawnUi()...BuildAsync() to have completed.
55
+ */
56
+ export function Canvas({ BackgroundColor, RenderingMode, Gestures, children, style, className, ref: viewRef }) {
57
+ const ref = useRef(null);
58
+ const view = useRef(null);
59
+ const root = useRef(null);
60
+ const [engine, setEngine] = useState(null);
61
+ useLayoutEffect(() => {
62
+ const v = new CanvasView(ref.current);
63
+ if (RenderingMode)
64
+ v.RenderingMode = RenderingMode;
65
+ view.current = v;
66
+ root.current = createDrawnRoot(v);
67
+ setEngine(v);
68
+ return () => { root.current?.unmount(); v.Dispose(); view.current = null; root.current = null; setEngine(null); };
69
+ // RenderingMode is read once at surface creation, like DrawnUi.
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ }, []);
72
+ // After the effect above, so the handle resolves to the created view.
73
+ useImperativeHandle(viewRef, () => view.current, []);
74
+ useLayoutEffect(() => {
75
+ const v = view.current;
76
+ if (BackgroundColor !== undefined && v.BackgroundColor !== BackgroundColor) {
77
+ v.BackgroundColor = BackgroundColor;
78
+ v.Update();
79
+ }
80
+ v.Gestures = Gestures ?? "Disabled";
81
+ root.current.render(children);
82
+ });
83
+ // The drawn surface is aria-hidden; the overlay is the accessibility tree (DrawnUi.Blazor pattern).
84
+ return (_jsxs("div", { className: className, style: { position: "relative", ...style }, children: [_jsx("canvas", { ref: ref, "aria-hidden": true, style: { display: "block", width: "100%", height: "100%" } }), engine && _jsx(AccessibilityOverlay, { view: engine })] }));
85
+ }
86
+ const A11Y_CSS = `
87
+ .drawnui-a11y-overlay{position:absolute;inset:0;overflow:hidden;pointer-events:none;user-select:none;-webkit-user-select:none}
88
+ .drawnui-a11y-node{position:absolute;margin:0;padding:0;border:0;background:transparent;color:transparent;overflow:hidden;white-space:nowrap;pointer-events:none;font:inherit;user-select:none;-webkit-user-select:none}
89
+ .drawnui-a11y-node::selection,.drawnui-a11y-node *::selection{background:transparent;color:transparent}
90
+ .drawnui-a11y-node:focus{outline:none}
91
+ .drawnui-a11y-node:focus-visible{outline:3px solid rgba(13,110,253,.85);outline-offset:1px;border-radius:3px}
92
+ .drawnui-a11y-text{overflow:visible}
93
+ .drawnui-a11y-text>span{position:absolute;white-space:pre;color:transparent;user-select:text;-webkit-user-select:text;pointer-events:auto;cursor:text;line-height:1;font-kerning:none;font-feature-settings:"kern" 0,"liga" 0,"calt" 0;text-rendering:geometricPrecision}
94
+ .drawnui-a11y-text>span::selection{background:rgba(110,168,254,.55);color:transparent}
95
+ `;
96
+ /**
97
+ * Invisible ARIA elements mirroring the accessible drawn controls, positioned over the canvas.
98
+ * Improvement over DrawnUi.Blazor: `pointer-events:none`, so hover and every pointer gesture still reach the canvas —
99
+ * keyboard (Tab / Enter / Space) and screen-reader activation arrive as DOM events and are routed back as a Tapped.
100
+ */
101
+ function AccessibilityOverlay({ view }) {
102
+ const [nodes, setNodes] = useState(() => view.AccessibilityManager.Snapshot);
103
+ useEffect(() => {
104
+ const mgr = view.AccessibilityManager;
105
+ setNodes(mgr.Snapshot);
106
+ const off = mgr.OnChanged(() => setNodes(mgr.Snapshot));
107
+ const offLive = mgr.OnLiveRegionUpdated(() => { mgr.ForceRebuildOnNextFrame(); view.Update(); });
108
+ return () => { off(); offLive(); };
109
+ }, [view]);
110
+ // selectable text spans take pointer events: the wheel over them is re-dispatched to the canvas so drawn scrolls keep working
111
+ const overlayRef = useRef(null);
112
+ const hasNodes = nodes.length > 0;
113
+ useEffect(() => {
114
+ const el = overlayRef.current;
115
+ if (!el)
116
+ return;
117
+ const forward = (e) => { e.preventDefault(); view.Element.dispatchEvent(new WheelEvent("wheel", { deltaX: e.deltaX, deltaY: e.deltaY, deltaMode: e.deltaMode, clientX: e.clientX, clientY: e.clientY, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, bubbles: false, cancelable: true })); };
118
+ el.addEventListener("wheel", forward, { passive: false });
119
+ return () => el.removeEventListener("wheel", forward);
120
+ }, [view, hasNodes]);
121
+ // the browser lays the invisible lines out with its own shaping; stretch each line with letter-spacing to the drawn width
122
+ // so the selection highlight covers the drawn glyphs end to end
123
+ useLayoutEffect(() => {
124
+ const el = overlayRef.current;
125
+ if (!el)
126
+ return;
127
+ for (const span of el.querySelectorAll(".drawnui-a11y-text>span")) {
128
+ const want = parseFloat(span.dataset.w ?? "0"), n = (span.textContent ?? "").replace(/\n$/, "").length - 1;
129
+ span.style.letterSpacing = "0px";
130
+ if (want <= 0 || n <= 0)
131
+ continue;
132
+ const have = span.getBoundingClientRect().width;
133
+ if (Math.abs(have - want) > 0.5)
134
+ span.style.letterSpacing = `${(want - have) / n}px`;
135
+ }
136
+ }, [nodes]);
137
+ if (!hasNodes)
138
+ return null;
139
+ // the browser scrolls an overflow:hidden container to reveal a focused child; the overlay must stay pinned to the canvas
140
+ const pin = (e) => { e.currentTarget.scrollTop = 0; e.currentTarget.scrollLeft = 0; };
141
+ return (_jsxs("div", { className: "drawnui-a11y-overlay", onScroll: pin, ref: overlayRef, children: [_jsx("style", { children: A11Y_CSS }), nodes.map((n) => {
142
+ const pos = { left: n.Rect.Left, top: n.Rect.Top, width: n.Rect.Width, height: n.Rect.Height };
143
+ const activate = () => n.Source.OnAccessibilityActivated();
144
+ return n.CanInteract ? (_jsx("div", { 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: 0, className: "drawnui-a11y-node", style: { ...pos, fontSize: 0, userSelect: "none" }, onClick: activate, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") {
145
+ e.preventDefault();
146
+ activate();
147
+ } }, onFocus: (e) => { pin({ currentTarget: e.currentTarget.parentElement }); SkiaScrollCtrl.EnsureVisible(n.Source); n.Source.OnAccessibilityFocused(true); n.Source.NotifyAccessibilityFocused(true); }, onBlur: () => { n.Source.OnAccessibilityFocused(false); n.Source.NotifyAccessibilityFocused(false); }, children: n.Label }, n.Id)) : n.TextLines ? (
148
+ // AccessibilityTextSelectable: one positioned span per drawn line, in the drawn font, so the browser selects and copies it like HTML
149
+ _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)) : (
150
+ // static text is exposed as real (transparent) text content; aria-label only for roles that need a name
151
+ _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));
152
+ })] }));
153
+ }
154
+ // One import for apps: React tags + every engine type (Colors, Thickness, Super, gestures, animators...).
155
+ // The engine `Canvas` class is shadowed by the React <Canvas> above; reach it via "drawnui-react/core".
156
+ export * from "../index";
157
+ // React-level SkiaShell (routes, back nav bar, useShell())
158
+ export * from "./SkiaShell";
@@ -0,0 +1,14 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Canvas } from "../core/Canvas";
3
+ import { SkiaControl } from "../core/SkiaControl";
4
+ import { TextSpan } from "../controls/TextSpan";
5
+ /** Anything React can mount: controls, plus TextSpan (a SkiaLabel child that is not a control, as in C#). */
6
+ type HostInstance = SkiaControl | TextSpan;
7
+ /** JSX tag name -> engine class. Add a control here to expose it to React. */
8
+ export declare const Registry: Record<string, new () => HostInstance>;
9
+ /** Creates a React root whose container is an engine Canvas. */
10
+ export declare function createDrawnRoot(canvas: Canvas): {
11
+ render(children: ReactNode): void;
12
+ unmount(): void;
13
+ };
14
+ export {};
@@ -0,0 +1,160 @@
1
+ import { createContext } from "react";
2
+ import Reconciler from "react-reconciler";
3
+ import { ConcurrentRoot, DefaultEventPriority } from "react-reconciler/constants";
4
+ import { SkiaLabel } from "../controls/SkiaLabel";
5
+ import { TextSpan } from "../controls/TextSpan";
6
+ import { SkiaRichLabel } from "../controls/SkiaRichLabel";
7
+ import { SkiaSwitch } from "../controls/SkiaSwitch";
8
+ import { SkiaCheckbox } from "../controls/SkiaCheckbox";
9
+ import { SkiaRadioButton } from "../controls/SkiaRadioButton";
10
+ import { SkiaProgress } from "../controls/SkiaProgress";
11
+ import { SkiaSlider } from "../controls/SkiaSlider";
12
+ import { SkiaCarousel } from "../controls/SkiaCarousel";
13
+ import { SkiaShaderCarousel } from "../controls/SkiaShaderCarousel";
14
+ import { SkiaDrawer } from "../controls/SkiaDrawer";
15
+ import { SkiaGrid, SkiaLayer, SkiaLayout, SkiaRow, SkiaStack, SkiaWrap } from "../controls/SkiaLayout";
16
+ import { SkiaHotspot } from "../controls/SkiaHotspot";
17
+ import { SkiaButton } from "../controls/SkiaButton";
18
+ import { SkiaImage } from "../controls/SkiaImage";
19
+ import { SkiaImageTiles } from "../controls/SkiaImageTiles";
20
+ import { SkiaDecoratedGrid } from "../controls/SkiaDecoratedGrid";
21
+ import { SkiaScrollBar } from "../controls/SkiaScrollBar";
22
+ import { RefreshIndicator } from "../controls/RefreshIndicator";
23
+ import { SkiaSvg } from "../controls/SkiaSvg";
24
+ import { SkiaBackdrop } from "../controls/SkiaBackdrop";
25
+ import { SkiaEditor } from "../controls/SkiaEditor";
26
+ import { SkiaSprite } from "../controls/SkiaSprite";
27
+ import { SkiaSpriteSet } from "../controls/SkiaSpriteSet";
28
+ import { SkiaLottie } from "../controls/SkiaLottie";
29
+ import { SkiaGif } from "../controls/SkiaGif";
30
+ import { SkiaScroll } from "../controls/SkiaScroll";
31
+ import { SkiaFrame, SkiaShape } from "../controls/SkiaShape";
32
+ /** JSX tag name -> engine class. Add a control here to expose it to React. */
33
+ export const Registry = {
34
+ SkiaLayout, SkiaStack, SkiaRow, SkiaLayer, SkiaWrap, SkiaGrid, SkiaDecoratedGrid, SkiaBackdrop, SkiaEditor, SkiaSprite, SkiaSpriteSet, SkiaLabel, SkiaRichLabel, TextSpan, SkiaHotspot, SkiaButton, SkiaImage, SkiaImageTiles, SkiaSvg, SkiaLottie, SkiaGif, SkiaScroll, SkiaScrollBar, RefreshIndicator, SkiaShape, SkiaFrame,
35
+ SkiaSwitch, SkiaCheckbox, SkiaRadioButton, SkiaProgress, SkiaSlider, SkiaCarousel, SkiaShaderCarousel, SkiaDrawer,
36
+ };
37
+ const SKIP = new Set(["children", "key", "ref"]);
38
+ /** Props that change how the subtree is composited, not what it contains: repaint, keep caches (DrawnUi RedrawCanvas). */
39
+ const REPAINT_ONLY = new Set(["TranslationX", "TranslationY", "Rotation", "ScaleX", "ScaleY", "Scale", "SkewX", "SkewY", "AnchorX", "AnchorY", "Opacity"]);
40
+ /**
41
+ * Assigns changed props straight onto the control (same names as the C# properties).
42
+ * Handler props (functions) are swapped without invalidating: inline arrows change identity every render.
43
+ */
44
+ function applyProps(inst, prev, next) {
45
+ let changed = false, repaint = false;
46
+ for (const k in next) {
47
+ if (SKIP.has(k) || (prev && prev[k] === next[k]))
48
+ continue;
49
+ inst[k] = next[k];
50
+ if (typeof next[k] === "function")
51
+ continue;
52
+ if (REPAINT_ONLY.has(k))
53
+ repaint = true;
54
+ else
55
+ changed = true;
56
+ }
57
+ if (prev)
58
+ for (const k in prev)
59
+ if (!SKIP.has(k) && !(k in next)) {
60
+ inst[k] = new inst.constructor()[k];
61
+ changed = true;
62
+ }
63
+ if (changed && prev)
64
+ inst.Update();
65
+ else if (repaint && prev)
66
+ inst.RepaintComposition?.();
67
+ }
68
+ let currentUpdatePriority = DefaultEventPriority;
69
+ const noop = () => { };
70
+ const hostContext = {};
71
+ // Record<string, unknown> absorbs members that react-reconciler 0.33 reads but @types 0.32 does not declare yet.
72
+ const hostConfig = {
73
+ supportsMutation: true,
74
+ supportsPersistence: false,
75
+ supportsHydration: false,
76
+ isPrimaryRenderer: false,
77
+ warnsIfNotActing: false,
78
+ noTimeout: -1,
79
+ scheduleTimeout: setTimeout,
80
+ cancelTimeout: clearTimeout,
81
+ supportsMicrotasks: true,
82
+ scheduleMicrotask: queueMicrotask,
83
+ createInstance(type, props) {
84
+ const ctor = Registry[type];
85
+ if (!ctor)
86
+ throw new Error(`DrawnUi: unknown control <${type}>`);
87
+ const inst = new ctor();
88
+ applyProps(inst, null, props);
89
+ return inst;
90
+ },
91
+ createTextInstance(text) {
92
+ throw new Error(`DrawnUi: raw text "${text}" is not allowed, use <SkiaLabel Text="..." />`);
93
+ },
94
+ appendInitialChild: (parent, child) => parent.AddSubView(child),
95
+ appendChild: (parent, child) => parent.AddSubView(child),
96
+ insertBefore: (parent, child, before) => {
97
+ const index = parent instanceof SkiaLayout ? parent.Views.indexOf(before)
98
+ : parent instanceof SkiaLabel ? parent.Spans.indexOf(before) : 0;
99
+ parent.InsertSubView(index, child);
100
+ },
101
+ removeChild: (parent, child) => parent.RemoveSubView(child),
102
+ appendChildToContainer: (canvas, child) => { canvas.Content = child; },
103
+ insertInContainerBefore: (canvas, child) => { canvas.Content = child; },
104
+ removeChildFromContainer: (canvas, child) => { if (canvas.Content === child)
105
+ canvas.Content = undefined; },
106
+ clearContainer: (canvas) => { canvas.Content = undefined; },
107
+ commitUpdate: (inst, _type, prev, next) => applyProps(inst, prev, next),
108
+ commitTextUpdate: noop,
109
+ commitMount: noop,
110
+ finalizeInitialChildren: () => false,
111
+ shouldSetTextContent: () => false,
112
+ resetTextContent: noop,
113
+ hideInstance: (inst) => { inst.IsVisible = false; inst.Update(); },
114
+ unhideInstance: (inst) => { inst.IsVisible = true; inst.Update(); },
115
+ hideTextInstance: noop,
116
+ unhideTextInstance: noop,
117
+ getRootHostContext: () => hostContext,
118
+ getChildHostContext: (ctx) => ctx,
119
+ getPublicInstance: (inst) => inst,
120
+ prepareForCommit: () => null,
121
+ resetAfterCommit: noop,
122
+ preparePortalMount: noop,
123
+ getInstanceFromNode: () => null,
124
+ beforeActiveInstanceBlur: noop,
125
+ afterActiveInstanceBlur: noop,
126
+ prepareScopeUpdate: noop,
127
+ getInstanceFromScope: () => null,
128
+ // React deleted the element: free the engine resources (C# Dispose); children are disposed by their parent first
129
+ detachDeletedInstance: (inst) => { inst.Dispose?.(); },
130
+ setCurrentUpdatePriority: (p) => { currentUpdatePriority = p; },
131
+ getCurrentUpdatePriority: () => currentUpdatePriority,
132
+ resolveUpdatePriority: () => currentUpdatePriority || DefaultEventPriority,
133
+ shouldAttemptEagerTransition: () => false,
134
+ trackSchedulerEvent: noop,
135
+ resolveEventType: () => null,
136
+ resolveEventTimeStamp: () => -1.1,
137
+ requestPostPaintCallback: noop,
138
+ maySuspendCommit: () => false,
139
+ maySuspendCommitOnUpdate: () => false,
140
+ maySuspendCommitInSyncRender: () => false,
141
+ preloadInstance: () => true,
142
+ startSuspendingCommit: noop,
143
+ suspendInstance: noop,
144
+ suspendOnActiveViewTransition: noop,
145
+ waitForCommitToBeReady: () => null,
146
+ getSuspendedCommitReason: () => null,
147
+ resetFormInstance: noop,
148
+ NotPendingTransition: null,
149
+ HostTransitionContext: createContext(null),
150
+ };
151
+ const reconciler = Reconciler(hostConfig);
152
+ const onError = (e) => console.error("DrawnUi.React:", e);
153
+ /** Creates a React root whose container is an engine Canvas. */
154
+ export function createDrawnRoot(canvas) {
155
+ const container = reconciler.createContainer(canvas, ConcurrentRoot, null, false, null, "", onError, onError, onError, noop, null);
156
+ return {
157
+ render(children) { reconciler.updateContainer(children, container, null, null); },
158
+ unmount() { reconciler.updateContainer(null, container, null, null); },
159
+ };
160
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "drawnui-react",
3
+ "version": "0.1.0-preview.1",
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
+ "license": "MIT",
6
+ "author": "Nick Kovalsky (AppoMobi)",
7
+ "homepage": "https://helloreact.drawnui.net",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/DrawnUi/DrawnUi.React.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/DrawnUi/DrawnUi.React/issues"
14
+ },
15
+ "keywords": ["drawnui", "skia", "canvaskit", "react", "canvas", "ui", "drawn-ui", "react-reconciler"],
16
+ "type": "module",
17
+ "main": "./dist/react/index.js",
18
+ "module": "./dist/react/index.js",
19
+ "types": "./dist/react/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/react/index.d.ts",
23
+ "import": "./dist/react/index.js"
24
+ },
25
+ "./core": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ },
29
+ "./package.json": "./package.json"
30
+ },
31
+ "files": ["dist", "README.md", "LICENSE", "SKIPPED.md", "PARITY.md"],
32
+ "sideEffects": false,
33
+ "scripts": {
34
+ "dev": "vite samples/demo",
35
+ "typecheck": "tsc --noEmit",
36
+ "build": "tsc --noEmit && node dev/build-samples.mjs",
37
+ "build:lib": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
38
+ "prepublishOnly": "npm run build:lib",
39
+ "preview": "vite preview samples/demo"
40
+ },
41
+ "peerDependencies": {
42
+ "react": "^18.2.0 || ^19.0.0",
43
+ "react-dom": "^18.2.0 || ^19.0.0"
44
+ },
45
+ "dependencies": {
46
+ "canvaskit-wasm": "^0.42.0",
47
+ "react-reconciler": "^0.33.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^24.0.0",
51
+ "@types/react": "^19.2.0",
52
+ "@types/react-dom": "^19.2.0",
53
+ "@types/react-reconciler": "^0.32.0",
54
+ "@vitejs/plugin-react": "^5.0.0",
55
+ "react": "^19.2.0",
56
+ "react-dom": "^19.2.0",
57
+ "typescript": "^5.9.0",
58
+ "vite": "^7.0.0"
59
+ }
60
+ }