@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delacour/react-native-ui",
3
- "version": "0.4.1",
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": ">=0.1.0",
187
+ "@delacour/react-native-charts": "0.2.0-alpha.20261021201504",
187
188
  "@legendapp/list": ">=3.3",
188
- "@shopify/react-native-skia": ">=2",
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
- "@shopify/react-native-skia": {
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 (`@shopify/react-native-skia`, an optional peer), driven by one
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
- - **`toPNG()` renders off screen**, on a CPU surface (`Skia.Surface.Make`) sized
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 "@shopify/react-native-skia"');
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 "@shopify/react-native-skia";
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.Make(pixelWidth, pixelHeight);
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());