@delacour/react-native-ui 0.4.1 → 0.5.0-alpha.20261021201504
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/package.json +5 -4
- package/src/components/chart/chart-tooltip-x.tsx +1 -1
- package/src/components/sketch/AGENTS.md +6 -2
- package/src/components/sketch/sketch-canvas.tsx +2 -1
- package/src/components/sketch/sketch.paths.test.ts +1 -1
- package/src/components/sketch/sketch.skia.ts +2 -2
- package/src/lib/skia-native-id.test.ts +29 -0
- package/src/lib/skia-native-id.ts +42 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@delacour/react-native-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-alpha.20261021201504",
|
|
4
4
|
"description": "React Native UI components — Uniwind, Reanimated, Gesture Handler",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -162,6 +162,7 @@
|
|
|
162
162
|
"./lib/scroll-edges": "./src/lib/scroll-edges.ts",
|
|
163
163
|
"./lib/scroll-maths": "./src/lib/scroll-maths.ts",
|
|
164
164
|
"./lib/scroll-sections": "./src/lib/scroll-sections.ts",
|
|
165
|
+
"./lib/skia-native-id": "./src/lib/skia-native-id.ts",
|
|
165
166
|
"./lib/slot": "./src/lib/slot.tsx",
|
|
166
167
|
"./lib/tv": "./src/lib/tv.ts",
|
|
167
168
|
"./lib/wrap-text-children": "./src/lib/wrap-text-children.ts",
|
|
@@ -183,9 +184,9 @@
|
|
|
183
184
|
"@maplibre/maplibre-react-native": "^11.5.0",
|
|
184
185
|
"@central-icons-react-native/round-outlined-radius-1-stroke-1.5": "^1.1",
|
|
185
186
|
"@delacour/react-native-bottom-sheet": ">=0.2.0",
|
|
186
|
-
"@delacour/react-native-charts": "
|
|
187
|
+
"@delacour/react-native-charts": "0.2.0-alpha.20261021201504",
|
|
187
188
|
"@legendapp/list": ">=3.3",
|
|
188
|
-
"
|
|
189
|
+
"react-native-skia": ">=3",
|
|
189
190
|
"expo-linear-gradient": ">=15",
|
|
190
191
|
"expo-router": ">=57",
|
|
191
192
|
"react": ">=19",
|
|
@@ -218,7 +219,7 @@
|
|
|
218
219
|
"@legendapp/list": {
|
|
219
220
|
"optional": true
|
|
220
221
|
},
|
|
221
|
-
"
|
|
222
|
+
"react-native-skia": {
|
|
222
223
|
"optional": true
|
|
223
224
|
},
|
|
224
225
|
"expo-router": {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ChartBounds, ChartOrientation, ChartScrubState, ChartStep } from "@delacour/react-native-charts";
|
|
2
2
|
import { ChartCursorLine, useChartContext as useEngineChart } from "@delacour/react-native-charts";
|
|
3
|
-
import { Rect } from "@shopify/react-native-skia";
|
|
4
3
|
import type { ReactElement } from "react";
|
|
5
4
|
import { useDerivedValue } from "react-native-reanimated";
|
|
5
|
+
import { Rect } from "react-native-skia";
|
|
6
6
|
import { useChart } from "./chart.context";
|
|
7
7
|
|
|
8
8
|
export type ChartTooltipXProps = {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A freehand drawing surface with its tools around it: a pen, eight shapes, an
|
|
4
4
|
eraser, colour swatches, a stroke width and undo — delivered as SVG or PNG.
|
|
5
|
-
Drawn with Skia (
|
|
5
|
+
Drawn with Skia (`react-native-skia`, an optional peer), driven by one
|
|
6
6
|
Gesture Handler pan on the UI thread.
|
|
7
7
|
|
|
8
8
|
`import { Sketch } from "@delacour/react-native-ui/sketch";`
|
|
@@ -136,7 +136,11 @@ DisplayNames: `DelacourUI.Sketch`, `.Sketch.Canvas`, `.Toolbar`, `.Tool`,
|
|
|
136
136
|
- **`toSVG()` is vector and `toPNG()` is raster; both hold the marks and the
|
|
137
137
|
background only.** Neither includes what `Sketch.Canvas` children paint under
|
|
138
138
|
the marks.
|
|
139
|
-
-
|
|
139
|
+
- **The canvas imports `lib/skia-native-id` for its effect.** After a JS reload Skia reused the
|
|
140
|
+
old runtime's canvas ids, which its native registry still held as torn down, so a sketch came up
|
|
141
|
+
blank until something re-rendered it. The import moves this runtime's ids to a fresh range
|
|
142
|
+
before the canvas asks for one.
|
|
143
|
+
- **`toPNG()` renders off screen**, on a CPU surface (`Skia.Surface.MakeRaster`) sized
|
|
140
144
|
to the canvas times `scale` (default the screen's pixel ratio), by the same
|
|
141
145
|
`drawSketchItems` the picture records — not a snapshot of the visible canvas,
|
|
142
146
|
which would need the view mounted and would capture nothing of the background
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Canvas, Group, Path, Picture, Skia } from "@shopify/react-native-skia";
|
|
2
1
|
import { type ReactElement, type ReactNode, useCallback, useEffect, useMemo } from "react";
|
|
3
2
|
import { type AccessibilityActionEvent, type LayoutChangeEvent, StyleSheet, View, type ViewProps } from "react-native";
|
|
4
3
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
5
4
|
import { useDerivedValue, useSharedValue } from "react-native-reanimated";
|
|
5
|
+
import { Canvas, Group, Path, Picture, Skia } from "react-native-skia";
|
|
6
6
|
import { scheduleOnRN, scheduleOnUI } from "react-native-worklets";
|
|
7
|
+
import "../../lib/skia-native-id";
|
|
7
8
|
import { useSketchPart } from "./sketch.context";
|
|
8
9
|
import {
|
|
9
10
|
appendSketchSegment,
|
|
@@ -352,7 +352,7 @@ describe("the drawing surface is Skia", () => {
|
|
|
352
352
|
test("the canvas and the renderer import Skia and not react-native-svg", () => {
|
|
353
353
|
for (const file of ["sketch-canvas.tsx", "sketch.skia.ts"]) {
|
|
354
354
|
const source = readFileSync(join(import.meta.dirname, file), "utf-8");
|
|
355
|
-
expect(source).toContain('from "
|
|
355
|
+
expect(source).toContain('from "react-native-skia"');
|
|
356
356
|
expect(source).not.toContain("react-native-svg");
|
|
357
357
|
}
|
|
358
358
|
});
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type SkPicture,
|
|
8
8
|
StrokeCap,
|
|
9
9
|
StrokeJoin,
|
|
10
|
-
} from "
|
|
10
|
+
} from "react-native-skia";
|
|
11
11
|
import type { SketchItem } from "./sketch.paths";
|
|
12
12
|
|
|
13
13
|
/** What `drawSketchItems` paints into: the canvas size in points. */
|
|
@@ -77,7 +77,7 @@ export function renderSketchPng(items: readonly SketchItem[], target: SketchPngT
|
|
|
77
77
|
const pixelHeight = Math.round(target.height * target.scale);
|
|
78
78
|
if (pixelWidth <= 0 || pixelHeight <= 0) return "";
|
|
79
79
|
|
|
80
|
-
const surface = Skia.Surface.
|
|
80
|
+
const surface = Skia.Surface.MakeRaster(pixelWidth, pixelHeight);
|
|
81
81
|
if (surface === null) return "";
|
|
82
82
|
const canvas = surface.getCanvas();
|
|
83
83
|
canvas.scale(target.scale, target.scale);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { resolveSkiaNativeId, SKIA_NATIVE_ID_FLOOR } from "./skia-native-id";
|
|
3
|
+
|
|
4
|
+
describe("resolveSkiaNativeId", () => {
|
|
5
|
+
test("moves a fresh runtime's counter off Skia's starting id", () => {
|
|
6
|
+
expect(resolveSkiaNativeId(1000, 0)).toBe(SKIA_NATIVE_ID_FLOOR);
|
|
7
|
+
expect(resolveSkiaNativeId(1000, 0.5)).toBeGreaterThan(SKIA_NATIVE_ID_FLOOR);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("keeps the ids a runtime already handed out behind the new counter", () => {
|
|
11
|
+
expect(resolveSkiaNativeId(1003, 0)).toBe(SKIA_NATIVE_ID_FLOOR + 3);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("leaves a counter that is already seeded alone, so two copies seed once", () => {
|
|
15
|
+
const seeded = resolveSkiaNativeId(1000, 0.25);
|
|
16
|
+
expect(resolveSkiaNativeId(seeded, 0.75)).toBe(seeded);
|
|
17
|
+
expect(resolveSkiaNativeId(seeded + 12, 0.75)).toBe(seeded + 12);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("spreads runtimes far enough apart that one's ids never reach the next's", () => {
|
|
21
|
+
const a = resolveSkiaNativeId(1000, 0.1);
|
|
22
|
+
const b = resolveSkiaNativeId(1000, 0.1 + 1 / 100_000);
|
|
23
|
+
expect(b - a).toBeGreaterThanOrEqual(10_000);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("stays inside a signed 32-bit int, which Skia's native side casts ids to", () => {
|
|
27
|
+
expect(resolveSkiaNativeId(1000, 0.999_999_999)).toBeLessThan(2 ** 31 - 1_000_000);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { SkiaViewNativeId } from "react-native-skia/src/views/SkiaViewNativeId";
|
|
2
|
+
|
|
3
|
+
/** Skia's counter starts at 1000; anything at or above this has been seeded. */
|
|
4
|
+
export const SKIA_NATIVE_ID_FLOOR = 10_000;
|
|
5
|
+
|
|
6
|
+
/** Ids a runtime may hand out before it reaches the next runtime's range. */
|
|
7
|
+
const SKIA_NATIVE_ID_SPAN = 10_000;
|
|
8
|
+
|
|
9
|
+
/** How many ranges a runtime can land in — 1e9 at the top, inside a signed 32-bit int. */
|
|
10
|
+
const SKIA_NATIVE_ID_SLOTS = 100_000;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Where a JS runtime's Skia canvas ids should start, given the counter as it stands and a number
|
|
14
|
+
* in `[0, 1)`. A counter already at or above the floor is returned as it is, so every copy of this
|
|
15
|
+
* module in an app can run it and only the first moves anything.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveSkiaNativeId(current: number, random: number): number {
|
|
18
|
+
if (current >= SKIA_NATIVE_ID_FLOOR) return current;
|
|
19
|
+
const slot = Math.floor(random * SKIA_NATIVE_ID_SLOTS);
|
|
20
|
+
return SKIA_NATIVE_ID_FLOOR + slot * SKIA_NATIVE_ID_SPAN + (current - 1000);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Starts this runtime's canvas ids somewhere the last runtime's never were.
|
|
25
|
+
*
|
|
26
|
+
* **A canvas mounted after a JS reload drew nothing until it re-rendered.** Skia numbers canvases
|
|
27
|
+
* from a module-level counter that restarts at 1000 in every runtime, while its native
|
|
28
|
+
* `ViewRegistry` is a process-wide singleton that outlives the reload. The old runtime's views are
|
|
29
|
+
* torn down after the new one has started, and each leaves a tombstone for its id; the new
|
|
30
|
+
* runtime's first canvases reuse those ids, their pictures are handed to a throwaway entry, and the
|
|
31
|
+
* view registers to an empty one. Nothing draws until the next React commit sends the picture again.
|
|
32
|
+
* A dev-menu reload hits it, and so does `Updates.reloadAsync` after an OTA update.
|
|
33
|
+
* Reported upstream as wcandillon/react-native-skia#4312; drop this once a release fixes it.
|
|
34
|
+
*
|
|
35
|
+
* Evaluated once per runtime, on import, before any canvas has asked for an id. `SkiaViewNativeId`
|
|
36
|
+
* is not exported from the package root, so this reaches it by path — the same file Metro resolves
|
|
37
|
+
* `Canvas` from through the package's `react-native` field.
|
|
38
|
+
*
|
|
39
|
+
* `@delacour/react-native-charts` keeps an identical copy in `src/skia/native-id.ts` for its own
|
|
40
|
+
* canvases, since `Chart` draws through it; change both together.
|
|
41
|
+
*/
|
|
42
|
+
SkiaViewNativeId.current = resolveSkiaNativeId(SkiaViewNativeId.current, Math.random());
|