@delacour/react-native-charts 0.1.0 → 0.2.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/README.md CHANGED
@@ -17,10 +17,10 @@ Docs: [ui.delacour.co.nz/docs/charts](https://ui.delacour.co.nz/docs/charts).
17
17
 
18
18
  ```bash
19
19
  bun add @delacour/react-native-charts
20
- bunx expo install @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-worklets
20
+ bunx expo install react-native-skia react-native-gesture-handler react-native-reanimated react-native-worklets
21
21
  ```
22
22
 
23
- Peers: `@shopify/react-native-skia`, `react-native-reanimated`,
23
+ Peers: `react-native-skia`, `react-native-reanimated`,
24
24
  `react-native-gesture-handler`, `react-native-worklets`, `react`,
25
25
  `react-native`. The native four go through `expo install`, so the SDK picks
26
26
  versions it can build; `react` and `react-native` are already in any app.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delacour/react-native-charts",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-alpha.20261021201504",
4
4
  "description": "Headless Skia charting primitives for React Native \u2014 no tokens, no className",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "d3-shape": "^3.2.0"
44
44
  },
45
45
  "peerDependencies": {
46
- "@shopify/react-native-skia": ">=2.6",
46
+ "react-native-skia": ">=3",
47
47
  "react": ">=19",
48
48
  "react-native": ">=0.81",
49
49
  "react-native-gesture-handler": ">=2.28",
@@ -53,9 +53,9 @@
53
53
  "devDependencies": {
54
54
  "@delacour/biome-config": "workspace:*",
55
55
  "@delacour/tsconfig": "workspace:*",
56
- "@shopify/react-native-skia": "catalog:",
56
+ "react-native-skia": "catalog:",
57
57
  "@types/bun": "^1.3.8",
58
58
  "@types/react": "catalog:",
59
- "typescript": "^5.9.3"
59
+ "typescript": "~6.0.3"
60
60
  }
61
61
  }
@@ -1,6 +1,6 @@
1
- import { type SkPath, usePathInterpolation } from "@shopify/react-native-skia";
2
1
  import { useEffect, useRef, useState } from "react";
3
2
  import { type SharedValue, useSharedValue, withSpring, withTiming } from "react-native-reanimated";
3
+ import { type SkPath, usePathInterpolation } from "react-native-skia";
4
4
  import { type ChartAnimation, DEFAULT_CHART_ANIMATION } from "./animation.types";
5
5
 
6
6
  export type AnimatedPathOptions = {
@@ -1,5 +1,5 @@
1
- import { Text } from "@shopify/react-native-skia";
2
1
  import type { ReactElement } from "react";
2
+ import { Text } from "react-native-skia";
3
3
  import { anchorX, anchorY } from "../../core/text/label-anchor";
4
4
  import { measureLabelWidth } from "../../skia/font";
5
5
  import { useChartContext } from "../cartesian-chart.context";
@@ -1,5 +1,5 @@
1
- import { Text } from "@shopify/react-native-skia";
2
1
  import type { ReactElement } from "react";
2
+ import { Text } from "react-native-skia";
3
3
  import { anchorX, anchorY } from "../../core/text/label-anchor";
4
4
  import { measureLabelWidth } from "../../skia/font";
5
5
  import { useChartContext } from "../cartesian-chart.context";
@@ -1,7 +1,8 @@
1
- import { Canvas } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useEffect, useMemo } from "react";
3
2
  import { StyleSheet, View } from "react-native";
4
3
  import { useSharedValue } from "react-native-reanimated";
4
+ import { Canvas } from "react-native-skia";
5
+ import "../skia/native-id";
5
6
  import type { ChartRow } from "../core/chart.types";
6
7
  import { buildLinePath } from "../core/curve/build-line";
7
8
  import { toCurvePath } from "../core/interaction/path-segments";
@@ -1,5 +1,5 @@
1
- import type { SkFont } from "@shopify/react-native-skia";
2
1
  import type { ReactNode } from "react";
2
+ import type { SkFont } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../animation/animation.types";
4
4
  import type { ChartBounds, ChartOrientation, ChartPoint, ChartRow, ChartSegment, ChartSize } from "../core/chart.types";
5
5
  import type { CurveType } from "../core/curve/curves";
@@ -1,5 +1,5 @@
1
- import type { SkFont } from "@shopify/react-native-skia";
2
1
  import { useMemo } from "react";
2
+ import type { SkFont } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { DEFAULT_CHART_ANIMATION } from "../../animation/animation.types";
5
5
  import type { ChartRow, ChartSize } from "../../core/chart.types";
@@ -1,5 +1,5 @@
1
- import { LinearGradient, Path, vec } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useMemo } from "react";
2
+ import { LinearGradient, Path, vec } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import type { ChartPoint, ChartSegment } from "../../core/chart.types";
@@ -1,5 +1,5 @@
1
- import { Path } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useEffect, useMemo } from "react";
2
+ import { Path } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import type { ChartOrientation, ChartPoint } from "../../core/chart.types";
@@ -1,5 +1,5 @@
1
- import { Path } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useMemo } from "react";
2
+ import { Path } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import type { ChartOrientation, ChartSegment } from "../../core/chart.types";
@@ -1,5 +1,5 @@
1
- import { Path, type SkFont, Text } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, type ReactNode, useEffect, useMemo } from "react";
2
+ import { Path, type SkFont, Text } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import type { ChartBounds, ChartOrientation, ChartPoint } from "../../core/chart.types";
@@ -1,5 +1,5 @@
1
- import { Path, type SkPath } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useEffect, useMemo } from "react";
2
+ import { Path, type SkPath } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import { resolveBand } from "../../core/geometry/band";
@@ -1,6 +1,6 @@
1
- import { Circle, DashPathEffect, Line } from "@shopify/react-native-skia";
2
1
  import type { ReactElement } from "react";
3
2
  import { useDerivedValue } from "react-native-reanimated";
3
+ import { Circle, DashPathEffect, Line } from "react-native-skia";
4
4
  import type { ChartBounds } from "../../core/chart.types";
5
5
  import type { ChartScrubSeriesState, ChartScrubState } from "../../gesture/gesture.types";
6
6
  import { useChartContext } from "../cartesian-chart.context";
@@ -1,5 +1,5 @@
1
- import { Path, Skia } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useMemo } from "react";
2
+ import { Path, Skia } from "react-native-skia";
3
3
  import { type GridAxis, gridSegments } from "../../core/geometry/grid-lines";
4
4
  import { useChartContext } from "../cartesian-chart.context";
5
5
 
@@ -1,5 +1,5 @@
1
- import { Path } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useMemo } from "react";
2
+ import { Path } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import type { ChartPoint } from "../../core/chart.types";
@@ -1,5 +1,5 @@
1
- import { Path } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useMemo } from "react";
2
+ import { Path } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import type { ChartPoint } from "../../core/chart.types";
@@ -4,7 +4,7 @@ Every number this package computes, and nothing that draws.
4
4
 
5
5
  ## The rule
6
6
 
7
- **No runtime import of `react`, `react-native`, `@shopify/react-native-skia`,
7
+ **No runtime import of `react`, `react-native`, `react-native-skia`,
8
8
  `react-native-reanimated`, `react-native-gesture-handler` or
9
9
  `react-native-worklets`.** Type-only imports are fine. `d3-scale` and
10
10
  `d3-shape` are fine — they are pure ESM JavaScript that Bun can parse.
@@ -1,5 +1,5 @@
1
- import type { SkFont } from "@shopify/react-native-skia";
2
1
  import { useMemo } from "react";
2
+ import type { SkFont } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { DEFAULT_CHART_ANIMATION } from "../../animation/animation.types";
5
5
  import type { ChartRow, ChartSize } from "../../core/chart.types";
@@ -1,5 +1,5 @@
1
- import { Path, type SkPath } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useMemo } from "react";
2
+ import { Path, type SkPath } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import { buildSliceEdgePath } from "../../core/polar/build-slice-path";
@@ -1,5 +1,5 @@
1
- import { type SkFont, Text } from "@shopify/react-native-skia";
2
1
  import type { ReactElement, ReactNode } from "react";
2
+ import { type SkFont, Text } from "react-native-skia";
3
3
  import { sliceLabelPosition } from "../../core/polar/label-position";
4
4
  import type { PieSliceData } from "../../core/polar/polar.types";
5
5
  import { anchorX, anchorY } from "../../core/text/label-anchor";
@@ -1,5 +1,5 @@
1
- import { Path, type SkPath } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, type ReactNode, useMemo } from "react";
2
+ import { Path, type SkPath } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../../animation/animation.types";
4
4
  import { useAnimatedPath } from "../../animation/use-animated-path";
5
5
  import { buildSlicePath } from "../../core/polar/build-slice-path";
@@ -1,7 +1,8 @@
1
- import { Canvas } from "@shopify/react-native-skia";
2
1
  import { type ReactElement, useMemo, useRef } from "react";
3
2
  import { StyleSheet, View } from "react-native";
4
3
  import { Gesture, type GestureType } from "react-native-gesture-handler";
4
+ import { Canvas } from "react-native-skia";
5
+ import "../skia/native-id";
5
6
  import { useCanvasSize } from "../cartesian/hooks/use-canvas-size";
6
7
  import type { ChartRow } from "../core/chart.types";
7
8
  import { sliceIndexAt } from "../core/polar/slice-index-at";
@@ -1,5 +1,5 @@
1
- import type { SkFont } from "@shopify/react-native-skia";
2
1
  import type { ReactNode } from "react";
2
+ import type { SkFont } from "react-native-skia";
3
3
  import type { ChartAnimation } from "../animation/animation.types";
4
4
  import type { ChartRow, ChartSize } from "../core/chart.types";
5
5
  import type { InnerRadius, PieSliceData, PolarPoint } from "../core/polar/polar.types";
@@ -17,7 +17,7 @@ const FORBIDDEN = [
17
17
  "react",
18
18
  "react-native",
19
19
  "react-dom",
20
- "@shopify/react-native-skia",
20
+ "react-native-skia",
21
21
  "react-native-reanimated",
22
22
  "react-native-gesture-handler",
23
23
  "react-native-worklets",
@@ -1,6 +1,6 @@
1
1
  # skia
2
2
 
3
- The only folder that imports Skia values. Four small translators.
3
+ The only folder that imports Skia values. Small translators, plus the canvas-id seed.
4
4
 
5
5
  Everything below `core/` computes numbers; this turns them into things Skia can
6
6
  draw. Keeping the surface this narrow is deliberate — Skia 2.x renamed and
@@ -13,6 +13,8 @@ files and nothing else.
13
13
  | --- | --- |
14
14
  | `build-path.ts` | An SVG path string from `core/curve` into an `SkPath` |
15
15
  | `font.ts` | A system font, label advance widths, and line height |
16
+ | `native-id.ts` | Seeds Skia's canvas-id counter per JS runtime, imported for its effect by both chart roots |
17
+ | `native-id.test.ts` | The seed: floor, spacing between runtimes, idempotence, the 32-bit bound |
16
18
 
17
19
  ## Design
18
20
 
@@ -37,3 +39,11 @@ files and nothing else.
37
39
  - **Line height comes from the font's metrics, not its point size.** An axis
38
40
  gutter has to reserve the full glyph box — ascent plus descent — and the
39
41
  nominal size is neither.
42
+
43
+ - **Each JS runtime numbers its canvases from somewhere new.** Skia's counter restarts at 1000 on
44
+ every reload while its native view registry outlives the runtime, so the reloaded app's first
45
+ canvases reused ids the old views had tombstoned and drew nothing until they re-rendered — a
46
+ chart came up empty after a dev reload or an OTA `Updates.reloadAsync`. `native-id.ts` moves the
47
+ counter to a random range on import, before any canvas asks for an id. It reaches
48
+ `SkiaViewNativeId` by path because the package root does not export it; `react-native-ui` holds
49
+ an identical copy for `Flow` and `Sketch`.
@@ -1,4 +1,4 @@
1
- import { Skia, type SkPath } from "@shopify/react-native-skia";
1
+ import { Skia, type SkPath } from "react-native-skia";
2
2
 
3
3
  /** A path with nothing in it — what an empty series draws. */
4
4
  export function emptyPath(): SkPath {
package/src/skia/font.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { matchFont, type SkFont } from "@shopify/react-native-skia";
2
1
  import { useMemo } from "react";
2
+ import { matchFont, type SkFont } from "react-native-skia";
3
3
  import type { LabelMetrics } from "../core/text/label-anchor";
4
4
 
5
5
  /** Re-exported so a consumer can hold a font without importing Skia itself. */
@@ -0,0 +1,29 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { resolveSkiaNativeId, SKIA_NATIVE_ID_FLOOR } from "./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-ui` keeps an identical copy in `src/lib/skia-native-id.ts` for `Flow` and
40
+ * `Sketch`, which do not depend on this package; change both together.
41
+ */
42
+ SkiaViewNativeId.current = resolveSkiaNativeId(SkiaViewNativeId.current, Math.random());