first commit

This commit is contained in:
2026-10-17 12:09:36 -04:00
commit 56046a63fc
1149 changed files with 658055 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Anton Lavrenov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+167
View File
@@ -0,0 +1,167 @@
<p align="center">
<img src="https://konvajs.org/img/icon.png" alt="Konva logo" height="60" />
</p>
# React Konva
[![Financial Contributors on Open Collective](https://opencollective.com/konva/all/badge.svg?label=financial+contributors)](https://opencollective.com/konva)
[![npm version](https://badge.fury.io/js/react-konva.svg)](http://badge.fury.io/js/react-konva)
[![Build Status](https://github.com/konvajs/react-konva/actions/workflows/test.yml/badge.svg)](https://github.com/konvajs/react-konva/actions/workflows/test.yml)
React Konva provides declarative React components for the [Konva](https://konvajs.org/) 2D canvas scene graph. Use it to build design editors, whiteboards, diagrams, annotations, and other interactive graphics.
React Konva is MIT licensed. It supports Konva shapes and events. It does not support React Native.
- [React tutorial](https://konvajs.org/docs/react/index.html)
- [Live demos](https://konvajs.org/docs/sandbox.html)
- [Konva API](https://konvajs.org/api/Konva.html)
- [Star the project](https://github.com/konvajs/react-konva)
[![A Konva Transformer around a selected image](https://konvajs.org/assets/demos/image-resize-min.png)](https://konvajs.org/docs/react/Transformer.html)
## Install
React Konva 19.3 requires React and React DOM `^19.3.0`.
For React 19.2, install the latest React Konva 19.2 release.
For React 18, install the latest React Konva 18 release.
```bash
npm install react-konva konva
```
## Quick example
```javascript
import {useState} from 'react';
import {Stage, Layer, Rect} from 'react-konva';
export default function App() {
const [color, setColor] = useState('#00a8e8');
return (
<Stage width={600} height={400}>
<Layer>
<Rect
x={50} y={50} width={120} height={80}
fill={color} draggable
onClick={() => setColor(color === '#00a8e8' ? '#ff7a00' : '#00a8e8')}
/>
</Layer>
</Stage>
);
}
```
> **Building a full design editor?** [Polotno](https://polotno.com/?utm_source=konvajs&utm_medium=readme&utm_content=react-konva) is a commercial design editor SDK built on Konva by the Konva maintainers. It ships templates, text editing, and export, so you integrate an editor instead of building one: `npm install polotno`.
`react-konva` follows the Konva API. See the [Konva overview](https://konvajs.org/docs/overview.html) for the scene graph, properties, and events.
## Core API
`react-konva` supports Konva shapes with the same names. Event props use React names such as `onClick`, `onTouchMove`, and `onDragEnd`.
### Getting reference to Konva objects
Use `ref` to access the underlying Konva node.
```javascript
import React, { useEffect, useRef } from 'react';
import { Circle } from 'react-konva';
const MyShape = () => {
const circleRef = useRef();
useEffect(() => {
// log Konva.Circle instance
console.log(circleRef.current);
}, []);
return <Circle ref={circleRef} radius={50} fill="black" />;
};
```
### React features inside Stage
Components inside `Stage` use React hooks and inherit context from the surrounding React tree.
`Stage` creates a separate canvas root. Place error and Suspense boundaries inside
`Stage`, with Konva elements as fallbacks.
See the [React compatibility matrix](docs/react-compatibility.md) for hook support and renderer limits.
### Strict mode
By default, `react-konva` updates only props that change between renders.
Manual changes, such as a dragged position, persist until the corresponding prop changes.
Strict mode restores all supplied props to their rendered values on each update.
For example, a circle with `x={0}` and `y={0}` returns to that position after a drag and a React update.
This mode is separate from React's `StrictMode`.
To enable strict mode globally:
```javascript
import { useStrictMode } from 'react-konva';
useStrictMode(true);
```
To enable strict mode for one node:
```javascript
<Rect width={50} height={50} fill="black" _useStrictMode />
```
### Minimal bundle
The default package includes all Konva shapes and filters.
For a smaller bundle, use the core entry point and import individual shapes:
```javascript
import { Stage, Layer, Rect } from 'react-konva/lib/ReactKonvaCore';
import 'konva/lib/shapes/Rect';
```
Demo: [https://codesandbox.io/s/6l97wny44z](https://codesandbox.io/s/6l97wny44z)
## Usage with Next.js
Konva 10+ works with Next.js without extra canvas setup. Use a
[Client Component](https://nextjs.org/docs/app/api-reference/directives/use-client) (`'use client'`).
Konva 9 and earlier need extra setup, such as installing `canvas` or
[disabling SSR](https://nextjs.org/docs/app/guides/lazy-loading#skipping-ssr) for the canvas component.
## Event synchronization
With Konva 10.5+, React state updates from native input commit before the event finishes.
To batch MobX reactions too, pass `runInAction`:
```tsx
import { runInAction } from 'mobx';
<Stage eventBatchFunc={runInAction} width={600} height={400}>
{/* Existing layers, shapes, and handlers */}
</Stage>
```
The wrapper must call its callback exactly once, synchronously. Older Konva versions ignore this prop.
With Konva 10.9+, react-konva also batches Transformer updates automatically across each canvas commit.
See the [release notes](RELEASE_NOTES.md) for geometry timing and compatibility details.
## Development and tests
Use Node.js 24 LTS for development.
```sh
npm install
npx playwright install chromium
npm test
```
`npm test` covers development and production builds, performance counts, server rendering, package exports, and types.
Set `BROWSER=firefox` or `BROWSER=webkit` to use another installed Playwright browser.
See the [benchmark guide](benchmarks/README.md) for performance checks and timing comparisons.
## [CHANGELOG](https://github.com/konvajs/react-konva/releases)
+84
View File
@@ -0,0 +1,84 @@
# 19.3.2: Clean release artifacts
Builds now clear previous output before compilation. This prevents stale,
unused files from entering the package after a compiler configuration change.
# 19.3.1: Batched Transformer updates
With Konva 10.9+, react-konva batches automatic Transformer updates across each
canvas commit. Updating many selected nodes triggers one Transformer refresh
after the mutations. Layout effects see the final anchor positions.
This works automatically. Upgrade Konva to enable it:
```sh
npm install react-konva@19.3.1 konva@^10.9.0
```
Older supported Konva versions keep their existing behavior and do not require
the new batching API. React and React DOM still require matching 19.3+ versions
within React 19.
Updates the context bridge and renderer types, with the same React 19.3.0 minimum.
Attribute-change listeners run during mutation. They can read the changed
attributes and Transformer bounds synchronously. To read final anchor positions,
use a layout effect or call `transformer.update()` explicitly in the listener.
## Validation
The suite covers 203 correctness tests per build and 41 performance checks,
including resizing 600 selected shapes. Regression tests cover individual
selections, selected Groups, layout reads, and a missing batching API.
Package checks cover server rendering, both module formats, consumer types,
package exports, and React DevTools.
# 19.3.0: React 19.3 support
## Upgrade requirements
Upgrade React and React DOM to matching 19.3 versions before you install this release.
The minimum React and React DOM version is now 19.3.0.
```sh
npm install react@19.3.0 react-dom@19.3.0 react-konva@19.3.0
```
For React 19.2 projects, pin `react-konva` to `~19.2.7`.
The range `^19.2.7` also allows 19.3.0, which requires the React upgrade.
For React 18 projects, use the React Konva 18 release line.
## Fixes and improvements
- Fixes the `useActionState` crash inside `Stage` with React 19.3. This resolves [#859](https://github.com/konvajs/react-konva/issues/859) through [#860](https://github.com/konvajs/react-konva/pull/860).
- Updates the bundled `react-reconciler` to 0.34.0 and `scheduler` to 0.28.0 for React 19.3.
- Canvas roots inherit React StrictMode checks from the DOM tree.
- Activity and Suspense preserve canvas state and node identity through hiding and reveal. Effects and refs disconnect and reconnect as each boundary requires.
- Stage removal discards pending child updates and runs effect cleanup, including for hidden Stages.
- Canvas components read the surrounding DOM form status through `useFormStatus`, including the submitted data and action.
- `useId` values remain stable on updates and distinct across DOM and Stage roots.
- Both package formats register with React DevTools. Development builds support hook state changes.
## Renderer boundaries
Error boundaries inside `Stage` handle canvas errors and display Konva fallbacks.
DOM error boundaries around `Stage` do not catch errors from canvas children.
Uncaught canvas errors appear in the console with the original error and component stack, including errors during cleanup.
Canvas Suspense boundaries display Konva fallbacks.
Canvas suspension does not activate a surrounding DOM Suspense boundary.
A promise read before Stage renders can activate that DOM fallback.
A DOM `ViewTransition` can animate the Stage container.
A `ViewTransition` inside Stage with Konva nodes or a ref throws a clear error.
Fragment children remain supported, but Fragment refs stay `null` and provide no DOM methods.
The [React compatibility matrix](docs/react-compatibility.md) lists the supported hooks, features, fallbacks, and remaining limits.
Native input synchronization and the fallback for older Konva versions continue from [19.2.7](https://github.com/konvajs/react-konva/blob/d9c73c21a5c164d5551ca62c15d5aa150a420053/RELEASE_NOTES.md).
## Validation
CI passes with Chromium, Firefox, and WebKit in development and production.
The minimum React and React DOM 19.3.0 job also passes.
The suite covers 196 correctness tests per build and 38 performance checks.
Package checks cover both module formats, server rendering, package exports, consumer types, and the DevTools protocol.
+119
View File
@@ -0,0 +1,119 @@
// special file for minimal import
import * as React from 'react';
import * as ReactReconciler from 'react-reconciler';
import Konva from 'konva';
import { useContextBridge } from 'its-fine';
export interface KonvaNodeEvents {
onMouseOver?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseMove?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseOut?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseEnter?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseLeave?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseDown?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseUp?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onWheel?(evt: Konva.KonvaEventObject<WheelEvent>): void;
onClick?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onDblClick?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onTouchStart?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchMove?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchEnd?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchCancel?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTap?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onDblTap?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onDragStart?(evt: Konva.KonvaEventObject<DragEvent>): void;
onDragMove?(evt: Konva.KonvaEventObject<DragEvent>): void;
onDragEnd?(evt: Konva.KonvaEventObject<DragEvent>): void;
onTransform?(evt: Konva.KonvaEventObject<Event>): void;
onTransformStart?(evt: Konva.KonvaEventObject<Event>): void;
onTransformEnd?(evt: Konva.KonvaEventObject<Event>): void;
onContextMenu?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerDown?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerMove?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerUp?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerCancel?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerEnter?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerLeave?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerOver?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerOut?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerClick?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerDblClick?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onGotPointerCapture?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onLostPointerCapture?(evt: Konva.KonvaEventObject<PointerEvent>): void;
}
export interface KonvaNodeComponent<
Node extends Konva.Node,
Props = Konva.NodeConfig
// We use React.ClassAttributes to fake the 'ref' attribute. This will ensure
// consumers get the proper 'Node' type in 'ref' instead of the wrapper
// component type.
> extends React.FC<Props & KonvaNodeEvents & React.ClassAttributes<Node>> {}
export interface StageProps
extends Konva.NodeConfig,
KonvaNodeEvents,
Pick<
React.HTMLAttributes<HTMLDivElement>,
'className' | 'role' | 'style' | 'tabIndex' | 'title'
> {
/**
* Wraps native Konva input inside react-konva's React batch (Konva 10.5+).
* Must call its callback exactly once, synchronously. For example: runInAction.
*/
eventBatchFunc?: (callback: () => void) => void;
}
// Stage is the only React component because the others are stubs that only know how
// to be rendered when they are under stage. Since there is no real backing
// class and are in reality are a string literal we don't want users to actually
// try and use them as a type. By defining them as a variable with an interface
// consumers will not be able to use the values as a type or constructor.
// The down side to this approach, is that typescript thinks the type is a
// function, but if the user tries to call it a runtime exception will occur.
export var Stage: KonvaNodeComponent<Konva.Stage, StageProps>;
export var Layer: KonvaNodeComponent<Konva.Layer, Konva.LayerConfig>;
export var FastLayer: KonvaNodeComponent<Konva.FastLayer, Konva.LayerConfig>;
export var Group: KonvaNodeComponent<Konva.Group, Konva.ContainerConfig>;
export var Label: KonvaNodeComponent<Konva.Label, Konva.LabelConfig>;
/** Shapes */
export var Rect: KonvaNodeComponent<Konva.Rect, Konva.RectConfig>;
export var Circle: KonvaNodeComponent<Konva.Circle, Konva.CircleConfig>;
export var Ellipse: KonvaNodeComponent<Konva.Ellipse, Konva.EllipseConfig>;
export var Wedge: KonvaNodeComponent<Konva.Wedge, Konva.WedgeConfig>;
export var Transformer: KonvaNodeComponent<
Konva.Transformer,
Konva.TransformerConfig
>;
export var Line: KonvaNodeComponent<Konva.Line, Konva.LineConfig>;
export var Sprite: KonvaNodeComponent<Konva.Sprite, Konva.SpriteConfig>;
export var Image: KonvaNodeComponent<Konva.Image, Konva.ImageConfig>;
export var Text: KonvaNodeComponent<Konva.Text, Konva.TextConfig>;
export var TextPath: KonvaNodeComponent<Konva.TextPath, Konva.TextPathConfig>;
export var Star: KonvaNodeComponent<Konva.Star, Konva.StarConfig>;
export var Ring: KonvaNodeComponent<Konva.Ring, Konva.RingConfig>;
export var Arc: KonvaNodeComponent<Konva.Arc, Konva.ArcConfig>;
export var Tag: KonvaNodeComponent<Konva.Tag, Konva.TagConfig>;
export var Path: KonvaNodeComponent<Konva.Path, Konva.PathConfig>;
export var RegularPolygon: KonvaNodeComponent<
Konva.RegularPolygon,
Konva.RegularPolygonConfig
>;
export var Arrow: KonvaNodeComponent<Konva.Arrow, Konva.ArrowConfig>;
export var Shape: KonvaNodeComponent<Konva.Shape, Konva.ShapeConfig>;
export var useStrictMode: (useStrictMode: boolean) => void;
export var KonvaRenderer: ReactReconciler.Reconciler<
any,
any,
any,
any,
any,
any
>;
export var version: string;
export { useContextBridge };
+125
View File
@@ -0,0 +1,125 @@
# React compatibility
This matrix covers React and React DOM 19.3, with react-reconciler 0.34.0.
It records renderer behavior and regression coverage. It does not guarantee every combination of React features or framework integrations.
A `Stage` creates a separate React root for its canvas children. Shape refs contain Konva objects.
Application context and DOM form status cross this boundary.
Error and Suspense boundaries stay within their renderer.
Place an error boundary inside Stage to display a Konva fallback for canvas errors.
Uncaught canvas errors appear in the console with the original error and component stack.
## Hooks
The inventory follows the [React hook reference](https://react.dev/reference/react/hooks) and the installed React exports.
Tests run components inside `Stage` unless a row describes the DOM boundary.
| API | Behavior | Regression coverage |
| --- | --- | --- |
| `useState`, `useReducer` | State updates reach Konva nodes. | [Hook tests](../test/27-react-hooks.test.tsx), [native events](../test/21-native-events.test.tsx) |
| `useRef`, `useImperativeHandle` | Refs expose Konva nodes or custom handles. Replacement and unmount clear refs. | [Ref lifecycle](../test/10-refs-lifecycle.test.tsx) |
| `useContext`, `use(context)` | Context updates cross Stage, including provider shorthand and memoized consumers. | [Hook tests](../test/27-react-hooks.test.tsx) |
| `useEffect`, `useLayoutEffect` | Effects run and clean up. Activity inside Stage disconnects and reconnects effects. | [Boundary tests](../test/28-react-boundaries.test.tsx), [scheduling tests](../test/16-scheduling-invariants.test.tsx) |
| `useInsertionEffect` | Insertion effects precede layout effects and clean up on update and unmount. | [Hook tests](../test/27-react-hooks.test.tsx) |
| `useEffectEvent` | Effect subscriptions read current props without reconnecting. | [Hook tests](../test/27-react-hooks.test.tsx) |
| `useMemo`, `useCallback` | Dependency changes update values and callbacks. Stable dependencies allow memoized children to skip rendering. | [Hook tests](../test/27-react-hooks.test.tsx) |
| `useTransition` | Canvas components expose pending state and commit asynchronous actions. | [Scheduler tests](../test/02-scheduler.test.tsx) |
| `useDeferredValue` | Old canvas content stays visible while deferred content suspends. | [Scheduler tests](../test/02-scheduler.test.tsx) |
| `useSyncExternalStore` | Subscriptions update canvas children and clean up. | [External store tests](../test/06-sync-external-store.test.tsx) |
| `useActionState` | Synchronous and asynchronous actions update canvas state. Rejections reach canvas error boundaries. | [Action tests](../test/25-actions.test.tsx), [error tests](../test/13-error-handling.test.tsx) |
| `useOptimistic` | Optimistic state appears during an action and settles to the saved state. | [Action tests](../test/25-actions.test.tsx) |
| `use(promise)` | Suspense handles pending promises. Error boundaries handle rejected promises. | [Suspense tests](../test/03-suspense.test.tsx), [error tests](../test/13-error-handling.test.tsx) |
| `useId` | IDs stay stable on updates and distinct across DOM and Stage roots. | [Hook tests](../test/27-react-hooks.test.tsx), [hydration test](../test/28-react-boundaries.test.tsx) |
| `useDebugValue` | Safe to call. Built packages register with React DevTools and support hook-state editing in development. | [Hook tests](../test/27-react-hooks.test.tsx), [DevTools protocol tests](../test/29-devtools.mjs). No inspector UI test. |
| React DOM `useFormStatus` | Inherits the surrounding DOM form status, including submitted data, method, and action. Other Stages remain independent. | [Hook tests](../test/27-react-hooks.test.tsx) |
Outside a form, the status is `{ pending: false, data: null, method: null, action: null }`.
Canvas controls can read `useFormStatus` directly without additional props.
React DOM's deprecated `useFormState` alias has no separate regression coverage. Use React's `useActionState` instead.
## Components and rendering
| Feature | Behavior and limits | Regression coverage |
| --- | --- | --- |
| Function and class components, keys, `memo` | React reconciles components and preserves keyed node identity. | [Mounting tests](../test/01-mounting.test.tsx), [hook tests](../test/27-react-hooks.test.tsx), [error tests](../test/13-error-handling.test.tsx) |
| `StrictMode` | Canvas roots inherit DOM StrictMode checks, including state initializers and memo calculations. React Konva's strict prop mode is a separate feature. | [StrictMode tests](../test/04-strict-mode.test.tsx), [action tests](../test/25-actions.test.tsx) |
| Function component `ref` props, `forwardRef`, callback cleanup | Refs reach shapes. Callback cleanup runs on replacement and unmount. | [Ref lifecycle](../test/10-refs-lifecycle.test.tsx) |
| `Fragment` | Children mount, update, reorder, and unmount. Fragment refs remain `null`. DOM Fragment methods are unavailable. | [Host feature tests](../test/26-react-host-features.test.tsx) |
| `Suspense`, `lazy` inside Stage | Fallbacks, retries, and hiding committed nodes work. Fallbacks must contain Konva elements. | [Suspense tests](../test/03-suspense.test.tsx) |
| Error boundaries | Boundaries inside Stage handle canvas errors and display Konva fallbacks. Uncaught errors are reported in the console. DOM boundaries do not catch canvas errors. | [Error tests](../test/13-error-handling.test.tsx) |
| `Activity` inside Stage | Hidden nodes retain state and identity. Nested boundaries preserve visibility and reconnect effects. | [Boundary tests](../test/28-react-boundaries.test.tsx), [pixel tests](../test/18-drawing.test.tsx) |
| `Activity` around Stage | Hide and reveal preserve Stage identity and canvas state. Effects and refs disconnect and reconnect. Hidden Stages dispose safely. | [Boundary tests](../test/28-react-boundaries.test.tsx) |
| `Suspense` around Stage | Promises read above Stage activate the DOM fallback. Mounted canvas state survives hiding. Layout effects disconnect, while passive effects remain active. | [Boundary tests](../test/28-react-boundaries.test.tsx) |
| `ViewTransition` | A DOM wrapper can animate the Stage container. A boundary inside Stage with Konva nodes or a ref throws an actionable error. | [Host feature tests](../test/26-react-host-features.test.tsx), [error tests](../test/13-error-handling.test.tsx) |
| Empty `ViewTransition` inside Stage | Commits finish without animation. Layout and passive effects still complete. | [Host feature tests](../test/26-react-host-features.test.tsx) |
| `startTransition`, `addTransitionType` | Each renderer schedules its own transitions. DOM pending state does not wait for canvas suspension. Transition types do not animate individual canvas shapes. | [Scheduler tests](../test/02-scheduler.test.tsx), [action tests](../test/25-actions.test.tsx) |
| `Profiler` | Canvas commit callbacks work in the profiling build. | [Commit-count benchmarks](../benchmarks/counts.bench.tsx) |
| Konva portals | `KonvaRenderer.createPortal` accepts Konva containers. Context, Activity visibility, target replacement, and disposal have coverage. | [Boundary tests](../test/28-react-boundaries.test.tsx) |
| DOM portals and overlays | DOM content requires React DOM and a DOM container. The Html fixture covers a separate DOM root. | [Portal tests](../test/05-roots-portals.test.tsx) |
| Server rendering and hydration | The server renders an empty Stage container. React DOM hydrates that container, then Konva mounts the canvas children. | [Packaged SSR tests](../test/11-ssr.mjs), [hydration test](../test/28-react-boundaries.test.tsx) |
The lifecycle tests cover Activity and compare canvas effects with DOM siblings under Suspense.
They cover overlapping suspension, replacement of pending children, and deletion before a promise resolves.
An initially hidden Stage creates its canvas when revealed. It does not prerender canvas children before that first reveal.
Development effects can replay in both roots. Cleanup must support repeated setup.
Errors during Stage disposal are reported in the console, including errors from multiple child cleanups.
Event-handler errors use browser error reporting. Error boundaries handle render, effect, and action failures.
## Choosing a Suspense boundary
For a canvas fallback, put the boundary inside Stage:
```jsx
<Stage width={600} height={400}>
<Layer>
<Suspense fallback={<Text text="Loading…" />}>
<CanvasContent />
</Suspense>
</Layer>
</Stage>
```
Without a canvas boundary, Stage uses an empty canvas fallback until its children are ready.
It can replace pending children or unmount without waiting for their promises.
Canvas suspension does not activate a DOM Suspense boundary around Stage.
For a DOM fallback, read the promise above Stage:
```jsx
function Drawing({ drawingPromise }) {
const drawing = use(drawingPromise);
return (
<Stage width={600} height={400}>
<Layer><Rect fill={drawing.fill} width={100} height={100} /></Layer>
</Stage>
);
}
<Suspense fallback={<p>Loading…</p>}>
<Drawing drawingPromise={drawingPromise} />
</Suspense>
```
Keep each promise stable across retries. Create it outside the component that reads it.
With this pattern, a DOM transition can keep the previous canvas visible while data loads.
Alternatively, own transition state inside Stage when canvas components read the promises.
An update passed through Stage props commits in the DOM before the canvas renders those props.
It cannot extend the original DOM transition when a canvas child suspends.
## APIs outside the canvas renderer
DOM elements, text children, forms, stylesheets, and document metadata are not Konva nodes.
Use `<Text text="..." />` for canvas text. Use a DOM overlay for HTML content.
Unknown element types produce a warning and substitute a `Group`. This fallback does not implement HTML behavior.
React DOM resource hints, form reset, and DOM portals operate on real DOM resources.
React DOM's `flushSync` does not independently flush state owned by the separate canvas renderer.
The native Konva event integration coordinates both renderers. [Scheduling tests](../test/16-scheduling-invariants.test.tsx) cover their commit ordering.
Client `cache` calls its function without server memoization. Client `cacheSignal` returns `null`.
These are React client defaults, not canvas caching features.
Element utilities such as `Children`, `cloneElement`, and `isValidElement` use React's implementation.
DevTools protocol tests cover registration, commit notifications, and development hook-state editing in both built package formats.
Browser extension UI, Server Components, Server Functions, streaming resumption, and React Compiler output require separate integration coverage.
This matrix does not claim that coverage. Experimental exports and future React minors are also outside this audit.
+69
View File
@@ -0,0 +1,69 @@
import { flushSync } from 'react-dom';
const pending = new Set();
const pendingUnmounts = new Set();
const enqueue = typeof queueMicrotask === 'function'
? queueMicrotask
: (callback) => Promise.resolve().then(callback);
let depth = 0;
const batches = new WeakMap();
export function addPendingUnmount(callback) {
pendingUnmounts.add(callback);
return () => {
pendingUnmounts.delete(callback);
};
}
// Hidden Stages may be deleted without another layout cleanup. Prepare their
// root removals before either a scheduled render or a Stage's inline flush.
export function prepareUnmounts() {
for (const callback of pendingUnmounts)
callback();
}
export function getEventBatch(batch) {
if (!batch)
return batchEvents;
let wrapped = batches.get(batch);
if (!wrapped) {
// Konva deduplicates shared callbacks when several Stages drag together.
wrapped = (run) => batchEvents(() => batch(run));
batches.set(batch, wrapped);
}
return wrapped;
}
// React's host scheduler stays asynchronous while a handler or subscription is
// running. A completed native input batch can finish those same scheduled jobs.
export function scheduleMicrotask(callback) {
const run = () => {
if (pending.delete(run)) {
prepareUnmounts();
callback();
}
};
pending.add(run);
enqueue(run);
}
export function batchEvents(callback) {
if (depth)
return callback();
depth++;
try {
// DOM-owned state must be scheduled inside this scope. Flush it before
// finishing canvas work, so Stage can transfer the final DOM-owned props.
flushSync(callback);
}
finally {
try {
// Canvas effects can schedule DOM state, and DOM effects can schedule
// more canvas work. Finish that work in a DOM scope too. Empty input
// does not add a second flush, and queued microtasks become no-ops.
while (pending.size) {
flushSync(() => {
for (const run of pending)
run();
});
}
}
finally {
depth--;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
/**
* Based on ReactArt.js
* Copyright (c) 2017-present Lavrenov Anton.
* All rights reserved.
*
* MIT
*/
'use strict';
import 'konva';
export * from './ReactKonvaCore.js';
+119
View File
@@ -0,0 +1,119 @@
// special file for minimal import
import * as React from 'react';
import * as ReactReconciler from 'react-reconciler';
import Konva from 'konva';
import { useContextBridge } from 'its-fine';
export interface KonvaNodeEvents {
onMouseOver?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseMove?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseOut?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseEnter?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseLeave?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseDown?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseUp?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onWheel?(evt: Konva.KonvaEventObject<WheelEvent>): void;
onClick?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onDblClick?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onTouchStart?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchMove?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchEnd?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchCancel?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTap?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onDblTap?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onDragStart?(evt: Konva.KonvaEventObject<DragEvent>): void;
onDragMove?(evt: Konva.KonvaEventObject<DragEvent>): void;
onDragEnd?(evt: Konva.KonvaEventObject<DragEvent>): void;
onTransform?(evt: Konva.KonvaEventObject<Event>): void;
onTransformStart?(evt: Konva.KonvaEventObject<Event>): void;
onTransformEnd?(evt: Konva.KonvaEventObject<Event>): void;
onContextMenu?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerDown?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerMove?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerUp?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerCancel?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerEnter?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerLeave?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerOver?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerOut?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerClick?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerDblClick?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onGotPointerCapture?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onLostPointerCapture?(evt: Konva.KonvaEventObject<PointerEvent>): void;
}
export interface KonvaNodeComponent<
Node extends Konva.Node,
Props = Konva.NodeConfig
// We use React.ClassAttributes to fake the 'ref' attribute. This will ensure
// consumers get the proper 'Node' type in 'ref' instead of the wrapper
// component type.
> extends React.FC<Props & KonvaNodeEvents & React.ClassAttributes<Node>> {}
export interface StageProps
extends Konva.NodeConfig,
KonvaNodeEvents,
Pick<
React.HTMLAttributes<HTMLDivElement>,
'className' | 'role' | 'style' | 'tabIndex' | 'title'
> {
/**
* Wraps native Konva input inside react-konva's React batch (Konva 10.5+).
* Must call its callback exactly once, synchronously. For example: runInAction.
*/
eventBatchFunc?: (callback: () => void) => void;
}
// Stage is the only React component because the others are stubs that only know how
// to be rendered when they are under stage. Since there is no real backing
// class and are in reality are a string literal we don't want users to actually
// try and use them as a type. By defining them as a variable with an interface
// consumers will not be able to use the values as a type or constructor.
// The down side to this approach, is that typescript thinks the type is a
// function, but if the user tries to call it a runtime exception will occur.
export var Stage: KonvaNodeComponent<Konva.Stage, StageProps>;
export var Layer: KonvaNodeComponent<Konva.Layer, Konva.LayerConfig>;
export var FastLayer: KonvaNodeComponent<Konva.FastLayer, Konva.LayerConfig>;
export var Group: KonvaNodeComponent<Konva.Group, Konva.ContainerConfig>;
export var Label: KonvaNodeComponent<Konva.Label, Konva.LabelConfig>;
/** Shapes */
export var Rect: KonvaNodeComponent<Konva.Rect, Konva.RectConfig>;
export var Circle: KonvaNodeComponent<Konva.Circle, Konva.CircleConfig>;
export var Ellipse: KonvaNodeComponent<Konva.Ellipse, Konva.EllipseConfig>;
export var Wedge: KonvaNodeComponent<Konva.Wedge, Konva.WedgeConfig>;
export var Transformer: KonvaNodeComponent<
Konva.Transformer,
Konva.TransformerConfig
>;
export var Line: KonvaNodeComponent<Konva.Line, Konva.LineConfig>;
export var Sprite: KonvaNodeComponent<Konva.Sprite, Konva.SpriteConfig>;
export var Image: KonvaNodeComponent<Konva.Image, Konva.ImageConfig>;
export var Text: KonvaNodeComponent<Konva.Text, Konva.TextConfig>;
export var TextPath: KonvaNodeComponent<Konva.TextPath, Konva.TextPathConfig>;
export var Star: KonvaNodeComponent<Konva.Star, Konva.StarConfig>;
export var Ring: KonvaNodeComponent<Konva.Ring, Konva.RingConfig>;
export var Arc: KonvaNodeComponent<Konva.Arc, Konva.ArcConfig>;
export var Tag: KonvaNodeComponent<Konva.Tag, Konva.TagConfig>;
export var Path: KonvaNodeComponent<Konva.Path, Konva.PathConfig>;
export var RegularPolygon: KonvaNodeComponent<
Konva.RegularPolygon,
Konva.RegularPolygonConfig
>;
export var Arrow: KonvaNodeComponent<Konva.Arrow, Konva.ArrowConfig>;
export var Shape: KonvaNodeComponent<Konva.Shape, Konva.ShapeConfig>;
export var useStrictMode: (useStrictMode: boolean) => void;
export var KonvaRenderer: ReactReconciler.Reconciler<
any,
any,
any,
any,
any,
any
>;
export var version: string;
export { useContextBridge };
+208
View File
@@ -0,0 +1,208 @@
/**
* Based on ReactArt.js
* Copyright (c) 2017-present Lavrenov Anton.
* All rights reserved.
*
* MIT
*/
'use strict';
import React from 'react';
import { useFormStatus } from 'react-dom';
const [reactMajor, reactMinor] = React.version.split('.').map(Number);
if (reactMajor !== 19 || reactMinor < 3) {
throw new Error('react-konva requires React 19.3 or later within React 19. ' +
'Install matching versions of react and react-dom. ' +
'For React 19.2, use react-konva 19.2. For React 18, use react-konva 18.');
}
import Konva from 'konva/lib/Core.js';
import ReactFiberReconciler from 'react-reconciler';
import { ConcurrentRoot } from 'react-reconciler/constants.js';
import * as HostConfig from './ReactKonvaHostConfig.js';
import { getEventBatch, addPendingUnmount, prepareUnmounts } from './EventBatch.js';
import { applyNodeProps, toggleStrictMode, EVENTS_NAMESPACE, } from './makeUpdates.js';
import { useContextBridge, useFiber, traverseFiber, FiberProvider } from 'its-fine';
function SuspendCanvas({ promise }) {
throw promise;
}
const StageWrap = (props) => {
const container = React.useRef(null);
const stage = React.useRef(null);
const fiberRef = React.useRef(null);
const children = React.useRef(null);
const suspension = React.useRef(null);
// DOM and Konva reconcilers have separate useId counters.
const identifierPrefix = React.useId();
const formStatus = useFormStatus();
const previousProps = React.useRef({});
// Keep the render-time snapshot when React replays layout effects.
const oldProps = previousProps.current;
const Bridge = useContextBridge();
const fiber = useFiber();
const isStrictMode = !!traverseFiber(fiber, true, (node) => node.type === React.StrictMode);
const effectsDisconnected = React.useRef(false);
const isUnmounting = React.useRef(false);
const cancelUnmount = React.useRef(null);
// Insertion effects stay mounted during hiding and StrictMode replay.
// Activity has already disconnected layout and passive effects when hidden.
// Its remaining insertion cleanups must still run inside a DOM commit.
React.useInsertionEffect(() => {
isUnmounting.current = false;
return () => {
isUnmounting.current = true;
if (effectsDisconnected.current && stage.current)
destroyStage();
};
}, []);
const destroyStage = () => {
cancelUnmount.current?.();
cancelUnmount.current = null;
prepareUnmounts();
// Discard queued child work before destroying the Stage, including updates
// from external stores whose objects are no longer alive.
KonvaRenderer.flushSyncFromReconciler(() => {
KonvaRenderer.updateContainer(null, fiberRef.current, null);
});
stage.current?.destroy();
stage.current?.off(EVENTS_NAMESPACE);
stage.current = null;
suspension.current?.resolve();
suspension.current = null;
};
const setCanvasVisibility = (mode) => {
if (mode === 'suspended') {
if (!suspension.current) {
let resolve;
const promise = new Promise((done) => {
resolve = done;
});
suspension.current = { promise, resolve };
}
}
else {
suspension.current?.resolve();
suspension.current = null;
}
KonvaRenderer.updateContainer(React.createElement(React.Activity, {
mode: mode === 'hidden' ? 'hidden' : 'visible',
children: React.createElement(React.Suspense, { fallback: null }, children.current, suspension.current &&
React.createElement(SuspendCanvas, {
promise: suspension.current.promise,
})),
}), fiberRef.current, null);
prepareUnmounts();
// Parent layout effects must see the committed canvas nodes and refs.
KonvaRenderer.flushSyncWork();
};
React.useLayoutEffect(() => {
cancelUnmount.current?.();
cancelUnmount.current = null;
// Reconnecting layout effects reuses the existing Stage.
if (!stage.current) {
stage.current = new Konva.Stage({
width: props.width,
height: props.height,
container: container.current,
});
fiberRef.current = KonvaRenderer.createContainer(stage.current, ConcurrentRoot, null, isStrictMode, null, identifierPrefix, console.error, console.error, console.error, null, null);
}
return () => {
if (!isUnmounting.current) {
// If this hidden Stage is deleted, discard queued child work before
// another root or the scheduler flushes it.
const unmount = () => {
if (!isUnmounting.current)
return;
cancelUnmount.current?.();
cancelUnmount.current = null;
KonvaRenderer.updateContainer(null, fiberRef.current, null);
};
cancelUnmount.current = addPendingUnmount(unmount);
// Suspense disconnects layout effects but keeps passive effects.
setCanvasVisibility('suspended');
}
else {
destroyStage();
}
};
}, []);
React.useEffect(() => {
effectsDisconnected.current = false;
return () => {
effectsDisconnected.current = true;
// Activity and StrictMode also disconnect the DOM passive effects.
if (!stage.current)
return;
if (isUnmounting.current)
destroyStage();
else
setCanvasVisibility('hidden');
};
}, []);
React.useLayoutEffect(() => {
// Older Konva versions use the normal asynchronous React scheduler.
if (typeof stage.current.eventBatchFunc === 'function') {
stage.current.eventBatchFunc(getEventBatch(props.eventBatchFunc));
}
}, [props.eventBatchFunc]);
React.useImperativeHandle(props.forwardedRef, () => stage.current, []);
React.useLayoutEffect(() => {
previousProps.current = props;
applyNodeProps(stage.current, props, oldProps);
// updateContainer schedules sync-lane work; with async scheduleMicrotask
// the work is queued, so flushSyncWork() drains it inline here so that
// a parent useLayoutEffect can read Konva nodes added by subscribing
// children. (Lighter than flushSyncFromReconciler — no callback wrapper.)
children.current = React.createElement(HostConfig.HostTransitionContext.Provider, { value: formStatus }, React.createElement(Bridge, {}, props.children));
setCanvasVisibility('visible');
});
return React.createElement('div', {
ref: container,
id: props.id,
accessKey: props.accessKey,
className: props.className,
role: props.role,
style: props.style,
tabIndex: props.tabIndex,
title: props.title,
});
};
export const Layer = 'Layer';
export const FastLayer = 'FastLayer';
export const Group = 'Group';
export const Label = 'Label';
export const Rect = 'Rect';
export const Circle = 'Circle';
export const Ellipse = 'Ellipse';
export const Wedge = 'Wedge';
export const Line = 'Line';
export const Sprite = 'Sprite';
export const Image = 'Image';
export const Text = 'Text';
export const TextPath = 'TextPath';
export const Star = 'Star';
export const Ring = 'Ring';
export const Arc = 'Arc';
export const Tag = 'Tag';
export const Path = 'Path';
export const RegularPolygon = 'RegularPolygon';
export const Arrow = 'Arrow';
export const Shape = 'Shape';
export const Transformer = 'Transformer';
export const version = '19.3.2';
const rendererConfig = {
...HostConfig,
// React's public context type omits fields used by the reconciler.
HostTransitionContext: HostConfig.HostTransitionContext,
rendererVersion: version,
rendererPackageName: 'react-konva',
extraDevToolsConfig: null,
};
export const KonvaRenderer = ReactFiberReconciler(rendererConfig);
KonvaRenderer.injectIntoDevTools();
export const Stage = React.forwardRef((props, ref) => {
return React.createElement(FiberProvider, {}, React.createElement(StageWrap, { ...props, forwardedRef: ref }));
});
export const useStrictMode = toggleStrictMode;
// export useContextBridge from its-fine for reuse in react-konva-utils
// so react-konva-utils don't use its own version of its-fine (it is possible on pnpm)
export { useContextBridge };
+247
View File
@@ -0,0 +1,247 @@
import React from 'react';
import Konva from 'konva/lib/Core.js';
import { applyNodeProps, updatePicture, EVENTS_NAMESPACE, } from './makeUpdates.js';
export { unstable_now as now, unstable_IdlePriority as idlePriority, unstable_runWithPriority as run, } from 'scheduler';
import {
// NoEventPriority,
DefaultEventPriority, DiscreteEventPriority, } from 'react-reconciler/constants.js';
const NO_CONTEXT = {};
// for react-spring capability
Konva.Node.prototype._applyProps = applyNodeProps;
// let currentUpdatePriority: number = NoEventPriority;
let currentUpdatePriority = DefaultEventPriority;
export function appendInitialChild(parentInstance, child) {
if (typeof child === 'string') {
// Noop for string children of Text (eg <Text>foo</Text>)
console.error(`Do not use plain text as child of Konva.Node. You are using text: ${child}`);
return;
}
parentInstance.add(child);
updatePicture(parentInstance);
}
export function createInstance(type, props, internalInstanceHandle) {
let NodeClass = Konva[type];
if (!NodeClass) {
console.error(`Konva has no node with the type ${type}. Group will be used instead. If you use minimal version of react-konva, just import required nodes into Konva: "import "konva/lib/shapes/${type}" If you want to render DOM elements as part of canvas tree take a look into this demo: https://konvajs.github.io/docs/react/DOM_Portal.html`);
NodeClass = Konva.Group;
}
// we need to split props into events and non events
// we we can pass non events into constructor directly
// that way the performance should be better
// we we apply change "applyNodeProps"
// then it will trigger change events on first run
// but we don't need them!
const propsWithoutEvents = {};
const propsWithOnlyEvents = {};
for (var key in props) {
// ignore ref
if (key === 'ref') {
continue;
}
var isEvent = key.slice(0, 2) === 'on';
if (isEvent) {
propsWithOnlyEvents[key] = props[key];
}
else {
propsWithoutEvents[key] = props[key];
}
}
const instance = new NodeClass(propsWithoutEvents);
applyNodeProps(instance, propsWithOnlyEvents);
return instance;
}
export function createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
console.error(`Text components are not supported for now in ReactKonva. Your text is: "${text}"`);
}
export function finalizeInitialChildren(domElement, type, props) {
return false;
}
export function getPublicInstance(instance) {
return instance;
}
// Fragments group Konva children but have no DOM instance or Fragment methods.
// This matches React's noop renderer.
export function createFragmentInstance() {
return null;
}
export function updateFragmentInstanceFiber() { }
export function commitNewChildToFragmentInstance() { }
export function deleteChildFromFragmentInstance() { }
let finishUpdateBatch;
export function prepareForCommit() {
// Older supported Konva versions do not expose commit batching.
finishUpdateBatch = Konva.beginUpdateBatch?.();
return null;
}
export function preparePortalMount() {
return null;
}
export function resetAfterCommit() {
const finish = finishUpdateBatch;
finishUpdateBatch = undefined;
// React layout effects must see the final Transformer geometry.
finish?.();
}
export function resetTextContent(domElement) {
// Noop
}
export function getRootHostContext() {
return NO_CONTEXT;
}
export function getChildHostContext() {
return NO_CONTEXT;
}
export const scheduleTimeout = setTimeout;
export const cancelTimeout = clearTimeout;
export const supportsMicrotasks = true;
// Async — fixes the mobx-react-lite snapshot race. Stage's useLayoutEffect
// then explicitly calls `flushSyncWork()` to keep parent useLayoutEffect able
// to read Konva nodes added by subscribing children (see ReactKonvaCore.tsx).
export { scheduleMicrotask } from './EventBatch.js';
export const noTimeout = -1;
// export const schedulePassiveEffects = scheduleDeferredCallback;
// export const cancelPassiveEffects = cancelDeferredCallback;
export function shouldSetTextContent(type, props) {
return false;
}
// The Konva renderer is secondary to the React DOM renderer.
export const isPrimaryRenderer = false;
export const warnsIfNotActing = false;
export const supportsMutation = true;
export const supportsPersistence = false;
export const supportsHydration = false;
export function appendChild(parentInstance, child) {
if (child.parent === parentInstance) {
child.moveToTop();
}
else {
parentInstance.add(child);
}
updatePicture(parentInstance);
}
export const appendChildToContainer = appendChild;
export function insertBefore(parentInstance, child, beforeChild) {
// child._remove() will not stop dragging
// but child.remove() will stop it, but we don't need it
// removing will reset zIndexes
child._remove();
parentInstance.add(child);
child.setZIndex(beforeChild.getZIndex());
updatePicture(parentInstance);
}
export const insertInContainerBefore = insertBefore;
export function removeChild(parentInstance, child) {
child.destroy();
child.off(EVENTS_NAMESPACE);
updatePicture(parentInstance);
}
export const removeChildFromContainer = removeChild;
export function commitTextUpdate(textInstance, oldText, newText) {
console.error(`Text components are not yet supported in ReactKonva. You text is: "${newText}"`);
}
export function commitMount(instance, type, newProps) {
// Noop
}
export function commitUpdate(instance, type, oldProps, newProps) {
applyNodeProps(instance, newProps, oldProps);
}
export function hideInstance(instance) {
instance.hide();
updatePicture(instance);
}
export function hideTextInstance(textInstance) {
// Noop
}
export function unhideInstance(instance, props) {
if (props.visible == null || props.visible) {
instance.show();
updatePicture(instance);
}
}
export function unhideTextInstance(textInstance, text) {
// Noop
}
export function clearContainer(container) {
// Noop
}
export function detachDeletedInstance(instance) {
// React visits every deleted host node here, including descendants of a
// removed container. Konva.destroy() alone does not release their listeners.
instance.off(EVENTS_NAMESPACE);
}
export function getInstanceFromNode() {
return null;
}
export function bindToConsole(methodName, args) {
return console[methodName].bind(console, ...args);
}
export function beforeActiveInstanceBlur() { }
export function afterActiveInstanceBlur() { }
export function prepareScopeUpdate() { }
export function getInstanceFromScope() {
return null;
}
export function setCurrentUpdatePriority(newPriority) {
currentUpdatePriority = newPriority;
}
export function getCurrentUpdatePriority() {
return currentUpdatePriority;
}
// Load-bearing: pins secondary commits to React's sync lane so they land
// before the scheduler yields. Required for parent useLayoutEffect to read
// Konva nodes added by subscribing children. Changing to Default/Continuous/
// Idle breaks ~18 tests in test/01-mounting and test/16-scheduling-invariants.
export function resolveUpdatePriority() {
return DiscreteEventPriority;
}
export function shouldAttemptEagerTransition() {
return false;
}
export function trackSchedulerEvent() { }
export function resolveEventType() {
return null;
}
export function resolveEventTimeStamp() {
return -1.1;
}
export function requestPostPaintCallback() { }
export function maySuspendCommit() {
// React 19.3 calls this during render only for nodes inside ViewTransition.
// Reject before commit, where a view-transition error can escape boundaries.
throw new Error('ViewTransition is not supported inside a Stage. ' +
'Place ViewTransition around Stage to animate its DOM container.');
}
export const createViewTransitionInstance = maySuspendCommit;
export const maySuspendCommitOnUpdate = maySuspendCommit;
export const maySuspendCommitInSyncRender = maySuspendCommit;
// Empty ViewTransition boundaries can reach commit without any Konva nodes.
// Finish those commits without animation so ref errors can reach a boundary.
export function startViewTransition(suspendedState, rootContainer, transitionTypes, mutationCallback, layoutCallback, afterMutationCallback, spawnedWorkCallback, passiveCallback, errorCallback, blockedCallback, finishedAnimation) {
mutationCallback();
layoutCallback();
finishedAnimation?.();
spawnedWorkCallback();
return null;
}
export function preloadInstance() {
return true;
}
export function startSuspendingCommit() { }
export function suspendInstance() { }
// React also calls this for Suspense retries without a ViewTransition component.
export function suspendOnActiveViewTransition() { }
export function waitForCommitToBeReady() {
return null;
}
export function getSuspendedCommitReason() {
return null;
}
// Stage provides the surrounding DOM form status. Standalone roots stay idle.
export const NotPendingTransition = Object.freeze({
pending: false,
data: null,
method: null,
action: null,
});
export const HostTransitionContext = /* @__PURE__ */ React.createContext(NotPendingTransition);
export function resetFormInstance() { }
+122
View File
@@ -0,0 +1,122 @@
import { Konva } from 'konva/lib/Global.js';
const propsToSkip = {
children: true,
ref: true,
key: true,
style: true,
forwardedRef: true,
// Stage's native input boundary is owned by the renderer.
eventBatchFunc: true,
unstable_applyCache: true,
unstable_applyDrawHitFromCache: true,
};
let zIndexWarningShowed = false;
let dragWarningShowed = false;
export const EVENTS_NAMESPACE = '.react-konva-event';
let useStrictMode = false;
export function toggleStrictMode(value) {
useStrictMode = value;
}
const DRAGGABLE_WARNING = `ReactKonva: You have a Konva node with draggable = true and position defined but no onDragMove or onDragEnd events are handled.
Position of a node will be changed during drag&drop, so you should update state of the react app as well.
Consider to add onDragMove or onDragEnd events.
For more info see: https://github.com/konvajs/react-konva/issues/256
`;
const Z_INDEX_WARNING = `ReactKonva: You are using "zIndex" attribute for a Konva node.
react-konva may get confused with ordering. Just define correct order of elements in your render function of a component.
For more info see: https://github.com/konvajs/react-konva/issues/194
`;
const EMPTY_PROPS = {};
export function applyNodeProps(instance, props, oldProps = EMPTY_PROPS) {
// don't use zIndex in react-konva
if (!zIndexWarningShowed && 'zIndex' in props) {
console.warn(Z_INDEX_WARNING);
zIndexWarningShowed = true;
}
// check correct draggable usage
if (!dragWarningShowed && props.draggable) {
var hasPosition = props.x !== undefined || props.y !== undefined;
var hasEvents = props.onDragEnd || props.onDragMove;
if (hasPosition && !hasEvents) {
console.warn(DRAGGABLE_WARNING);
dragWarningShowed = true;
}
}
var updatedProps = {};
var hasUpdates = false;
// check old props
// we need to unset properties that are not in new props
// and remove all events
for (var key in oldProps) {
if (propsToSkip[key]) {
continue;
}
var isEvent = key.slice(0, 2) === 'on';
var propChanged = oldProps[key] !== props[key];
// if that is a changed event, we need to remove it
if (isEvent && propChanged) {
var eventName = key.substr(2).toLowerCase();
if (eventName.substr(0, 7) === 'content') {
eventName =
'content' +
eventName.substr(7, 1).toUpperCase() +
eventName.substr(8);
}
// Remove by namespace because react-konva binds at most one listener
// for each event in that namespace.
instance.off(eventName + EVENTS_NAMESPACE);
}
var toRemove = !props.hasOwnProperty(key);
if (!isEvent && toRemove) {
updatedProps[key] = undefined;
hasUpdates = true;
}
}
var strictUpdate = useStrictMode || props._useStrictMode;
const newEvents = {};
for (var key in props) {
if (propsToSkip[key]) {
continue;
}
var isEvent = key.slice(0, 2) === 'on';
var toAdd = oldProps[key] !== props[key];
if (isEvent && toAdd) {
var eventName = key.substr(2).toLowerCase();
if (eventName.substr(0, 7) === 'content') {
eventName =
'content' +
eventName.substr(7, 1).toUpperCase() +
eventName.substr(8);
}
// check that event is not undefined
if (props[key]) {
newEvents[eventName] = props[key];
}
}
if (!isEvent &&
(props[key] !== oldProps[key] ||
(strictUpdate && props[key] !== instance.getAttr(key)))) {
hasUpdates = true;
updatedProps[key] = props[key];
}
}
if (hasUpdates) {
instance.setAttrs(updatedProps);
updatePicture(instance);
}
// subscribe to events AFTER we set attrs
// we need it to fix https://github.com/konvajs/react-konva/issues/471
// settings attrs may add events. Like "draggable: true" will add "mousedown" listener
for (var eventName in newEvents) {
// first clear any existing listeners, it is required for strict mode
instance.off(eventName + EVENTS_NAMESPACE);
// then attach new one
instance.on(eventName + EVENTS_NAMESPACE, newEvents[eventName]);
}
}
export function updatePicture(node) {
if (!Konva.autoDrawEnabled) {
var drawingNode = node.getLayer() || node.getStage();
drawingNode && drawingNode.batchDraw();
}
}
+1
View File
@@ -0,0 +1 @@
{"type":"module"}
+76
View File
@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addPendingUnmount = addPendingUnmount;
exports.prepareUnmounts = prepareUnmounts;
exports.getEventBatch = getEventBatch;
exports.scheduleMicrotask = scheduleMicrotask;
exports.batchEvents = batchEvents;
const react_dom_1 = require("react-dom");
const pending = new Set();
const pendingUnmounts = new Set();
const enqueue = typeof queueMicrotask === 'function'
? queueMicrotask
: (callback) => Promise.resolve().then(callback);
let depth = 0;
const batches = new WeakMap();
function addPendingUnmount(callback) {
pendingUnmounts.add(callback);
return () => {
pendingUnmounts.delete(callback);
};
}
// Hidden Stages may be deleted without another layout cleanup. Prepare their
// root removals before either a scheduled render or a Stage's inline flush.
function prepareUnmounts() {
for (const callback of pendingUnmounts)
callback();
}
function getEventBatch(batch) {
if (!batch)
return batchEvents;
let wrapped = batches.get(batch);
if (!wrapped) {
// Konva deduplicates shared callbacks when several Stages drag together.
wrapped = (run) => batchEvents(() => batch(run));
batches.set(batch, wrapped);
}
return wrapped;
}
// React's host scheduler stays asynchronous while a handler or subscription is
// running. A completed native input batch can finish those same scheduled jobs.
function scheduleMicrotask(callback) {
const run = () => {
if (pending.delete(run)) {
prepareUnmounts();
callback();
}
};
pending.add(run);
enqueue(run);
}
function batchEvents(callback) {
if (depth)
return callback();
depth++;
try {
// DOM-owned state must be scheduled inside this scope. Flush it before
// finishing canvas work, so Stage can transfer the final DOM-owned props.
(0, react_dom_1.flushSync)(callback);
}
finally {
try {
// Canvas effects can schedule DOM state, and DOM effects can schedule
// more canvas work. Finish that work in a DOM scope too. Empty input
// does not add a second flush, and queued microtasks become no-ops.
while (pending.size) {
(0, react_dom_1.flushSync)(() => {
for (const run of pending)
run();
});
}
}
finally {
depth--;
}
}
}
+25
View File
@@ -0,0 +1,25 @@
/**
* Based on ReactArt.js
* Copyright (c) 2017-present Lavrenov Anton.
* All rights reserved.
*
* MIT
*/
'use strict';
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
require("konva");
__exportStar(require("./ReactKonvaCore.js"), exports);
+119
View File
@@ -0,0 +1,119 @@
// special file for minimal import
import * as React from 'react';
import * as ReactReconciler from 'react-reconciler';
import Konva from 'konva';
import { useContextBridge } from 'its-fine';
export interface KonvaNodeEvents {
onMouseOver?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseMove?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseOut?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseEnter?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseLeave?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseDown?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onMouseUp?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onWheel?(evt: Konva.KonvaEventObject<WheelEvent>): void;
onClick?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onDblClick?(evt: Konva.KonvaEventObject<MouseEvent>): void;
onTouchStart?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchMove?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchEnd?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTouchCancel?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onTap?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onDblTap?(evt: Konva.KonvaEventObject<TouchEvent>): void;
onDragStart?(evt: Konva.KonvaEventObject<DragEvent>): void;
onDragMove?(evt: Konva.KonvaEventObject<DragEvent>): void;
onDragEnd?(evt: Konva.KonvaEventObject<DragEvent>): void;
onTransform?(evt: Konva.KonvaEventObject<Event>): void;
onTransformStart?(evt: Konva.KonvaEventObject<Event>): void;
onTransformEnd?(evt: Konva.KonvaEventObject<Event>): void;
onContextMenu?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerDown?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerMove?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerUp?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerCancel?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerEnter?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerLeave?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerOver?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerOut?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerClick?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onPointerDblClick?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onGotPointerCapture?(evt: Konva.KonvaEventObject<PointerEvent>): void;
onLostPointerCapture?(evt: Konva.KonvaEventObject<PointerEvent>): void;
}
export interface KonvaNodeComponent<
Node extends Konva.Node,
Props = Konva.NodeConfig
// We use React.ClassAttributes to fake the 'ref' attribute. This will ensure
// consumers get the proper 'Node' type in 'ref' instead of the wrapper
// component type.
> extends React.FC<Props & KonvaNodeEvents & React.ClassAttributes<Node>> {}
export interface StageProps
extends Konva.NodeConfig,
KonvaNodeEvents,
Pick<
React.HTMLAttributes<HTMLDivElement>,
'className' | 'role' | 'style' | 'tabIndex' | 'title'
> {
/**
* Wraps native Konva input inside react-konva's React batch (Konva 10.5+).
* Must call its callback exactly once, synchronously. For example: runInAction.
*/
eventBatchFunc?: (callback: () => void) => void;
}
// Stage is the only React component because the others are stubs that only know how
// to be rendered when they are under stage. Since there is no real backing
// class and are in reality are a string literal we don't want users to actually
// try and use them as a type. By defining them as a variable with an interface
// consumers will not be able to use the values as a type or constructor.
// The down side to this approach, is that typescript thinks the type is a
// function, but if the user tries to call it a runtime exception will occur.
export var Stage: KonvaNodeComponent<Konva.Stage, StageProps>;
export var Layer: KonvaNodeComponent<Konva.Layer, Konva.LayerConfig>;
export var FastLayer: KonvaNodeComponent<Konva.FastLayer, Konva.LayerConfig>;
export var Group: KonvaNodeComponent<Konva.Group, Konva.ContainerConfig>;
export var Label: KonvaNodeComponent<Konva.Label, Konva.LabelConfig>;
/** Shapes */
export var Rect: KonvaNodeComponent<Konva.Rect, Konva.RectConfig>;
export var Circle: KonvaNodeComponent<Konva.Circle, Konva.CircleConfig>;
export var Ellipse: KonvaNodeComponent<Konva.Ellipse, Konva.EllipseConfig>;
export var Wedge: KonvaNodeComponent<Konva.Wedge, Konva.WedgeConfig>;
export var Transformer: KonvaNodeComponent<
Konva.Transformer,
Konva.TransformerConfig
>;
export var Line: KonvaNodeComponent<Konva.Line, Konva.LineConfig>;
export var Sprite: KonvaNodeComponent<Konva.Sprite, Konva.SpriteConfig>;
export var Image: KonvaNodeComponent<Konva.Image, Konva.ImageConfig>;
export var Text: KonvaNodeComponent<Konva.Text, Konva.TextConfig>;
export var TextPath: KonvaNodeComponent<Konva.TextPath, Konva.TextPathConfig>;
export var Star: KonvaNodeComponent<Konva.Star, Konva.StarConfig>;
export var Ring: KonvaNodeComponent<Konva.Ring, Konva.RingConfig>;
export var Arc: KonvaNodeComponent<Konva.Arc, Konva.ArcConfig>;
export var Tag: KonvaNodeComponent<Konva.Tag, Konva.TagConfig>;
export var Path: KonvaNodeComponent<Konva.Path, Konva.PathConfig>;
export var RegularPolygon: KonvaNodeComponent<
Konva.RegularPolygon,
Konva.RegularPolygonConfig
>;
export var Arrow: KonvaNodeComponent<Konva.Arrow, Konva.ArrowConfig>;
export var Shape: KonvaNodeComponent<Konva.Shape, Konva.ShapeConfig>;
export var useStrictMode: (useStrictMode: boolean) => void;
export var KonvaRenderer: ReactReconciler.Reconciler<
any,
any,
any,
any,
any,
any
>;
export var version: string;
export { useContextBridge };
+244
View File
@@ -0,0 +1,244 @@
/**
* Based on ReactArt.js
* Copyright (c) 2017-present Lavrenov Anton.
* All rights reserved.
*
* MIT
*/
'use strict';
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.useContextBridge = exports.useStrictMode = exports.Stage = exports.KonvaRenderer = exports.version = exports.Transformer = exports.Shape = exports.Arrow = exports.RegularPolygon = exports.Path = exports.Tag = exports.Arc = exports.Ring = exports.Star = exports.TextPath = exports.Text = exports.Image = exports.Sprite = exports.Line = exports.Wedge = exports.Ellipse = exports.Circle = exports.Rect = exports.Label = exports.Group = exports.FastLayer = exports.Layer = void 0;
const react_1 = __importDefault(require("react"));
const react_dom_1 = require("react-dom");
const [reactMajor, reactMinor] = react_1.default.version.split('.').map(Number);
if (reactMajor !== 19 || reactMinor < 3) {
throw new Error('react-konva requires React 19.3 or later within React 19. ' +
'Install matching versions of react and react-dom. ' +
'For React 19.2, use react-konva 19.2. For React 18, use react-konva 18.');
}
const Core_js_1 = __importDefault(require("konva/lib/Core.js"));
const react_reconciler_1 = __importDefault(require("react-reconciler"));
const constants_js_1 = require("react-reconciler/constants.js");
const HostConfig = __importStar(require("./ReactKonvaHostConfig.js"));
const EventBatch_js_1 = require("./EventBatch.js");
const makeUpdates_js_1 = require("./makeUpdates.js");
const its_fine_1 = require("its-fine");
Object.defineProperty(exports, "useContextBridge", { enumerable: true, get: function () { return its_fine_1.useContextBridge; } });
function SuspendCanvas({ promise }) {
throw promise;
}
const StageWrap = (props) => {
const container = react_1.default.useRef(null);
const stage = react_1.default.useRef(null);
const fiberRef = react_1.default.useRef(null);
const children = react_1.default.useRef(null);
const suspension = react_1.default.useRef(null);
// DOM and Konva reconcilers have separate useId counters.
const identifierPrefix = react_1.default.useId();
const formStatus = (0, react_dom_1.useFormStatus)();
const previousProps = react_1.default.useRef({});
// Keep the render-time snapshot when React replays layout effects.
const oldProps = previousProps.current;
const Bridge = (0, its_fine_1.useContextBridge)();
const fiber = (0, its_fine_1.useFiber)();
const isStrictMode = !!(0, its_fine_1.traverseFiber)(fiber, true, (node) => node.type === react_1.default.StrictMode);
const effectsDisconnected = react_1.default.useRef(false);
const isUnmounting = react_1.default.useRef(false);
const cancelUnmount = react_1.default.useRef(null);
// Insertion effects stay mounted during hiding and StrictMode replay.
// Activity has already disconnected layout and passive effects when hidden.
// Its remaining insertion cleanups must still run inside a DOM commit.
react_1.default.useInsertionEffect(() => {
isUnmounting.current = false;
return () => {
isUnmounting.current = true;
if (effectsDisconnected.current && stage.current)
destroyStage();
};
}, []);
const destroyStage = () => {
cancelUnmount.current?.();
cancelUnmount.current = null;
(0, EventBatch_js_1.prepareUnmounts)();
// Discard queued child work before destroying the Stage, including updates
// from external stores whose objects are no longer alive.
exports.KonvaRenderer.flushSyncFromReconciler(() => {
exports.KonvaRenderer.updateContainer(null, fiberRef.current, null);
});
stage.current?.destroy();
stage.current?.off(makeUpdates_js_1.EVENTS_NAMESPACE);
stage.current = null;
suspension.current?.resolve();
suspension.current = null;
};
const setCanvasVisibility = (mode) => {
if (mode === 'suspended') {
if (!suspension.current) {
let resolve;
const promise = new Promise((done) => {
resolve = done;
});
suspension.current = { promise, resolve };
}
}
else {
suspension.current?.resolve();
suspension.current = null;
}
exports.KonvaRenderer.updateContainer(react_1.default.createElement(react_1.default.Activity, {
mode: mode === 'hidden' ? 'hidden' : 'visible',
children: react_1.default.createElement(react_1.default.Suspense, { fallback: null }, children.current, suspension.current &&
react_1.default.createElement(SuspendCanvas, {
promise: suspension.current.promise,
})),
}), fiberRef.current, null);
(0, EventBatch_js_1.prepareUnmounts)();
// Parent layout effects must see the committed canvas nodes and refs.
exports.KonvaRenderer.flushSyncWork();
};
react_1.default.useLayoutEffect(() => {
cancelUnmount.current?.();
cancelUnmount.current = null;
// Reconnecting layout effects reuses the existing Stage.
if (!stage.current) {
stage.current = new Core_js_1.default.Stage({
width: props.width,
height: props.height,
container: container.current,
});
fiberRef.current = exports.KonvaRenderer.createContainer(stage.current, constants_js_1.ConcurrentRoot, null, isStrictMode, null, identifierPrefix, console.error, console.error, console.error, null, null);
}
return () => {
if (!isUnmounting.current) {
// If this hidden Stage is deleted, discard queued child work before
// another root or the scheduler flushes it.
const unmount = () => {
if (!isUnmounting.current)
return;
cancelUnmount.current?.();
cancelUnmount.current = null;
exports.KonvaRenderer.updateContainer(null, fiberRef.current, null);
};
cancelUnmount.current = (0, EventBatch_js_1.addPendingUnmount)(unmount);
// Suspense disconnects layout effects but keeps passive effects.
setCanvasVisibility('suspended');
}
else {
destroyStage();
}
};
}, []);
react_1.default.useEffect(() => {
effectsDisconnected.current = false;
return () => {
effectsDisconnected.current = true;
// Activity and StrictMode also disconnect the DOM passive effects.
if (!stage.current)
return;
if (isUnmounting.current)
destroyStage();
else
setCanvasVisibility('hidden');
};
}, []);
react_1.default.useLayoutEffect(() => {
// Older Konva versions use the normal asynchronous React scheduler.
if (typeof stage.current.eventBatchFunc === 'function') {
stage.current.eventBatchFunc((0, EventBatch_js_1.getEventBatch)(props.eventBatchFunc));
}
}, [props.eventBatchFunc]);
react_1.default.useImperativeHandle(props.forwardedRef, () => stage.current, []);
react_1.default.useLayoutEffect(() => {
previousProps.current = props;
(0, makeUpdates_js_1.applyNodeProps)(stage.current, props, oldProps);
// updateContainer schedules sync-lane work; with async scheduleMicrotask
// the work is queued, so flushSyncWork() drains it inline here so that
// a parent useLayoutEffect can read Konva nodes added by subscribing
// children. (Lighter than flushSyncFromReconciler — no callback wrapper.)
children.current = react_1.default.createElement(HostConfig.HostTransitionContext.Provider, { value: formStatus }, react_1.default.createElement(Bridge, {}, props.children));
setCanvasVisibility('visible');
});
return react_1.default.createElement('div', {
ref: container,
id: props.id,
accessKey: props.accessKey,
className: props.className,
role: props.role,
style: props.style,
tabIndex: props.tabIndex,
title: props.title,
});
};
exports.Layer = 'Layer';
exports.FastLayer = 'FastLayer';
exports.Group = 'Group';
exports.Label = 'Label';
exports.Rect = 'Rect';
exports.Circle = 'Circle';
exports.Ellipse = 'Ellipse';
exports.Wedge = 'Wedge';
exports.Line = 'Line';
exports.Sprite = 'Sprite';
exports.Image = 'Image';
exports.Text = 'Text';
exports.TextPath = 'TextPath';
exports.Star = 'Star';
exports.Ring = 'Ring';
exports.Arc = 'Arc';
exports.Tag = 'Tag';
exports.Path = 'Path';
exports.RegularPolygon = 'RegularPolygon';
exports.Arrow = 'Arrow';
exports.Shape = 'Shape';
exports.Transformer = 'Transformer';
exports.version = '19.3.2';
const rendererConfig = {
...HostConfig,
// React's public context type omits fields used by the reconciler.
HostTransitionContext: HostConfig.HostTransitionContext,
rendererVersion: exports.version,
rendererPackageName: 'react-konva',
extraDevToolsConfig: null,
};
exports.KonvaRenderer = (0, react_reconciler_1.default)(rendererConfig);
exports.KonvaRenderer.injectIntoDevTools();
exports.Stage = react_1.default.forwardRef((props, ref) => {
return react_1.default.createElement(its_fine_1.FiberProvider, {}, react_1.default.createElement(StageWrap, { ...props, forwardedRef: ref }));
});
exports.useStrictMode = makeUpdates_js_1.toggleStrictMode;
+306
View File
@@ -0,0 +1,306 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HostTransitionContext = exports.NotPendingTransition = exports.maySuspendCommitInSyncRender = exports.maySuspendCommitOnUpdate = exports.createViewTransitionInstance = exports.removeChildFromContainer = exports.insertInContainerBefore = exports.appendChildToContainer = exports.supportsHydration = exports.supportsPersistence = exports.supportsMutation = exports.warnsIfNotActing = exports.isPrimaryRenderer = exports.noTimeout = exports.scheduleMicrotask = exports.supportsMicrotasks = exports.cancelTimeout = exports.scheduleTimeout = exports.run = exports.idlePriority = exports.now = void 0;
exports.appendInitialChild = appendInitialChild;
exports.createInstance = createInstance;
exports.createTextInstance = createTextInstance;
exports.finalizeInitialChildren = finalizeInitialChildren;
exports.getPublicInstance = getPublicInstance;
exports.createFragmentInstance = createFragmentInstance;
exports.updateFragmentInstanceFiber = updateFragmentInstanceFiber;
exports.commitNewChildToFragmentInstance = commitNewChildToFragmentInstance;
exports.deleteChildFromFragmentInstance = deleteChildFromFragmentInstance;
exports.prepareForCommit = prepareForCommit;
exports.preparePortalMount = preparePortalMount;
exports.resetAfterCommit = resetAfterCommit;
exports.resetTextContent = resetTextContent;
exports.getRootHostContext = getRootHostContext;
exports.getChildHostContext = getChildHostContext;
exports.shouldSetTextContent = shouldSetTextContent;
exports.appendChild = appendChild;
exports.insertBefore = insertBefore;
exports.removeChild = removeChild;
exports.commitTextUpdate = commitTextUpdate;
exports.commitMount = commitMount;
exports.commitUpdate = commitUpdate;
exports.hideInstance = hideInstance;
exports.hideTextInstance = hideTextInstance;
exports.unhideInstance = unhideInstance;
exports.unhideTextInstance = unhideTextInstance;
exports.clearContainer = clearContainer;
exports.detachDeletedInstance = detachDeletedInstance;
exports.getInstanceFromNode = getInstanceFromNode;
exports.bindToConsole = bindToConsole;
exports.beforeActiveInstanceBlur = beforeActiveInstanceBlur;
exports.afterActiveInstanceBlur = afterActiveInstanceBlur;
exports.prepareScopeUpdate = prepareScopeUpdate;
exports.getInstanceFromScope = getInstanceFromScope;
exports.setCurrentUpdatePriority = setCurrentUpdatePriority;
exports.getCurrentUpdatePriority = getCurrentUpdatePriority;
exports.resolveUpdatePriority = resolveUpdatePriority;
exports.shouldAttemptEagerTransition = shouldAttemptEagerTransition;
exports.trackSchedulerEvent = trackSchedulerEvent;
exports.resolveEventType = resolveEventType;
exports.resolveEventTimeStamp = resolveEventTimeStamp;
exports.requestPostPaintCallback = requestPostPaintCallback;
exports.maySuspendCommit = maySuspendCommit;
exports.startViewTransition = startViewTransition;
exports.preloadInstance = preloadInstance;
exports.startSuspendingCommit = startSuspendingCommit;
exports.suspendInstance = suspendInstance;
exports.suspendOnActiveViewTransition = suspendOnActiveViewTransition;
exports.waitForCommitToBeReady = waitForCommitToBeReady;
exports.getSuspendedCommitReason = getSuspendedCommitReason;
exports.resetFormInstance = resetFormInstance;
const react_1 = __importDefault(require("react"));
const Core_js_1 = __importDefault(require("konva/lib/Core.js"));
const makeUpdates_js_1 = require("./makeUpdates.js");
var scheduler_1 = require("scheduler");
Object.defineProperty(exports, "now", { enumerable: true, get: function () { return scheduler_1.unstable_now; } });
Object.defineProperty(exports, "idlePriority", { enumerable: true, get: function () { return scheduler_1.unstable_IdlePriority; } });
Object.defineProperty(exports, "run", { enumerable: true, get: function () { return scheduler_1.unstable_runWithPriority; } });
const constants_js_1 = require("react-reconciler/constants.js");
const NO_CONTEXT = {};
// for react-spring capability
Core_js_1.default.Node.prototype._applyProps = makeUpdates_js_1.applyNodeProps;
// let currentUpdatePriority: number = NoEventPriority;
let currentUpdatePriority = constants_js_1.DefaultEventPriority;
function appendInitialChild(parentInstance, child) {
if (typeof child === 'string') {
// Noop for string children of Text (eg <Text>foo</Text>)
console.error(`Do not use plain text as child of Konva.Node. You are using text: ${child}`);
return;
}
parentInstance.add(child);
(0, makeUpdates_js_1.updatePicture)(parentInstance);
}
function createInstance(type, props, internalInstanceHandle) {
let NodeClass = Core_js_1.default[type];
if (!NodeClass) {
console.error(`Konva has no node with the type ${type}. Group will be used instead. If you use minimal version of react-konva, just import required nodes into Konva: "import "konva/lib/shapes/${type}" If you want to render DOM elements as part of canvas tree take a look into this demo: https://konvajs.github.io/docs/react/DOM_Portal.html`);
NodeClass = Core_js_1.default.Group;
}
// we need to split props into events and non events
// we we can pass non events into constructor directly
// that way the performance should be better
// we we apply change "applyNodeProps"
// then it will trigger change events on first run
// but we don't need them!
const propsWithoutEvents = {};
const propsWithOnlyEvents = {};
for (var key in props) {
// ignore ref
if (key === 'ref') {
continue;
}
var isEvent = key.slice(0, 2) === 'on';
if (isEvent) {
propsWithOnlyEvents[key] = props[key];
}
else {
propsWithoutEvents[key] = props[key];
}
}
const instance = new NodeClass(propsWithoutEvents);
(0, makeUpdates_js_1.applyNodeProps)(instance, propsWithOnlyEvents);
return instance;
}
function createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
console.error(`Text components are not supported for now in ReactKonva. Your text is: "${text}"`);
}
function finalizeInitialChildren(domElement, type, props) {
return false;
}
function getPublicInstance(instance) {
return instance;
}
// Fragments group Konva children but have no DOM instance or Fragment methods.
// This matches React's noop renderer.
function createFragmentInstance() {
return null;
}
function updateFragmentInstanceFiber() { }
function commitNewChildToFragmentInstance() { }
function deleteChildFromFragmentInstance() { }
let finishUpdateBatch;
function prepareForCommit() {
// Older supported Konva versions do not expose commit batching.
finishUpdateBatch = Core_js_1.default.beginUpdateBatch?.();
return null;
}
function preparePortalMount() {
return null;
}
function resetAfterCommit() {
const finish = finishUpdateBatch;
finishUpdateBatch = undefined;
// React layout effects must see the final Transformer geometry.
finish?.();
}
function resetTextContent(domElement) {
// Noop
}
function getRootHostContext() {
return NO_CONTEXT;
}
function getChildHostContext() {
return NO_CONTEXT;
}
exports.scheduleTimeout = setTimeout;
exports.cancelTimeout = clearTimeout;
exports.supportsMicrotasks = true;
// Async — fixes the mobx-react-lite snapshot race. Stage's useLayoutEffect
// then explicitly calls `flushSyncWork()` to keep parent useLayoutEffect able
// to read Konva nodes added by subscribing children (see ReactKonvaCore.tsx).
var EventBatch_js_1 = require("./EventBatch.js");
Object.defineProperty(exports, "scheduleMicrotask", { enumerable: true, get: function () { return EventBatch_js_1.scheduleMicrotask; } });
exports.noTimeout = -1;
// export const schedulePassiveEffects = scheduleDeferredCallback;
// export const cancelPassiveEffects = cancelDeferredCallback;
function shouldSetTextContent(type, props) {
return false;
}
// The Konva renderer is secondary to the React DOM renderer.
exports.isPrimaryRenderer = false;
exports.warnsIfNotActing = false;
exports.supportsMutation = true;
exports.supportsPersistence = false;
exports.supportsHydration = false;
function appendChild(parentInstance, child) {
if (child.parent === parentInstance) {
child.moveToTop();
}
else {
parentInstance.add(child);
}
(0, makeUpdates_js_1.updatePicture)(parentInstance);
}
exports.appendChildToContainer = appendChild;
function insertBefore(parentInstance, child, beforeChild) {
// child._remove() will not stop dragging
// but child.remove() will stop it, but we don't need it
// removing will reset zIndexes
child._remove();
parentInstance.add(child);
child.setZIndex(beforeChild.getZIndex());
(0, makeUpdates_js_1.updatePicture)(parentInstance);
}
exports.insertInContainerBefore = insertBefore;
function removeChild(parentInstance, child) {
child.destroy();
child.off(makeUpdates_js_1.EVENTS_NAMESPACE);
(0, makeUpdates_js_1.updatePicture)(parentInstance);
}
exports.removeChildFromContainer = removeChild;
function commitTextUpdate(textInstance, oldText, newText) {
console.error(`Text components are not yet supported in ReactKonva. You text is: "${newText}"`);
}
function commitMount(instance, type, newProps) {
// Noop
}
function commitUpdate(instance, type, oldProps, newProps) {
(0, makeUpdates_js_1.applyNodeProps)(instance, newProps, oldProps);
}
function hideInstance(instance) {
instance.hide();
(0, makeUpdates_js_1.updatePicture)(instance);
}
function hideTextInstance(textInstance) {
// Noop
}
function unhideInstance(instance, props) {
if (props.visible == null || props.visible) {
instance.show();
(0, makeUpdates_js_1.updatePicture)(instance);
}
}
function unhideTextInstance(textInstance, text) {
// Noop
}
function clearContainer(container) {
// Noop
}
function detachDeletedInstance(instance) {
// React visits every deleted host node here, including descendants of a
// removed container. Konva.destroy() alone does not release their listeners.
instance.off(makeUpdates_js_1.EVENTS_NAMESPACE);
}
function getInstanceFromNode() {
return null;
}
function bindToConsole(methodName, args) {
return console[methodName].bind(console, ...args);
}
function beforeActiveInstanceBlur() { }
function afterActiveInstanceBlur() { }
function prepareScopeUpdate() { }
function getInstanceFromScope() {
return null;
}
function setCurrentUpdatePriority(newPriority) {
currentUpdatePriority = newPriority;
}
function getCurrentUpdatePriority() {
return currentUpdatePriority;
}
// Load-bearing: pins secondary commits to React's sync lane so they land
// before the scheduler yields. Required for parent useLayoutEffect to read
// Konva nodes added by subscribing children. Changing to Default/Continuous/
// Idle breaks ~18 tests in test/01-mounting and test/16-scheduling-invariants.
function resolveUpdatePriority() {
return constants_js_1.DiscreteEventPriority;
}
function shouldAttemptEagerTransition() {
return false;
}
function trackSchedulerEvent() { }
function resolveEventType() {
return null;
}
function resolveEventTimeStamp() {
return -1.1;
}
function requestPostPaintCallback() { }
function maySuspendCommit() {
// React 19.3 calls this during render only for nodes inside ViewTransition.
// Reject before commit, where a view-transition error can escape boundaries.
throw new Error('ViewTransition is not supported inside a Stage. ' +
'Place ViewTransition around Stage to animate its DOM container.');
}
exports.createViewTransitionInstance = maySuspendCommit;
exports.maySuspendCommitOnUpdate = maySuspendCommit;
exports.maySuspendCommitInSyncRender = maySuspendCommit;
// Empty ViewTransition boundaries can reach commit without any Konva nodes.
// Finish those commits without animation so ref errors can reach a boundary.
function startViewTransition(suspendedState, rootContainer, transitionTypes, mutationCallback, layoutCallback, afterMutationCallback, spawnedWorkCallback, passiveCallback, errorCallback, blockedCallback, finishedAnimation) {
mutationCallback();
layoutCallback();
finishedAnimation?.();
spawnedWorkCallback();
return null;
}
function preloadInstance() {
return true;
}
function startSuspendingCommit() { }
function suspendInstance() { }
// React also calls this for Suspense retries without a ViewTransition component.
function suspendOnActiveViewTransition() { }
function waitForCommitToBeReady() {
return null;
}
function getSuspendedCommitReason() {
return null;
}
// Stage provides the surrounding DOM form status. Standalone roots stay idle.
exports.NotPendingTransition = Object.freeze({
pending: false,
data: null,
method: null,
action: null,
});
exports.HostTransitionContext = react_1.default.createContext(exports.NotPendingTransition);
function resetFormInstance() { }
+128
View File
@@ -0,0 +1,128 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EVENTS_NAMESPACE = void 0;
exports.toggleStrictMode = toggleStrictMode;
exports.applyNodeProps = applyNodeProps;
exports.updatePicture = updatePicture;
const Global_js_1 = require("konva/lib/Global.js");
const propsToSkip = {
children: true,
ref: true,
key: true,
style: true,
forwardedRef: true,
// Stage's native input boundary is owned by the renderer.
eventBatchFunc: true,
unstable_applyCache: true,
unstable_applyDrawHitFromCache: true,
};
let zIndexWarningShowed = false;
let dragWarningShowed = false;
exports.EVENTS_NAMESPACE = '.react-konva-event';
let useStrictMode = false;
function toggleStrictMode(value) {
useStrictMode = value;
}
const DRAGGABLE_WARNING = `ReactKonva: You have a Konva node with draggable = true and position defined but no onDragMove or onDragEnd events are handled.
Position of a node will be changed during drag&drop, so you should update state of the react app as well.
Consider to add onDragMove or onDragEnd events.
For more info see: https://github.com/konvajs/react-konva/issues/256
`;
const Z_INDEX_WARNING = `ReactKonva: You are using "zIndex" attribute for a Konva node.
react-konva may get confused with ordering. Just define correct order of elements in your render function of a component.
For more info see: https://github.com/konvajs/react-konva/issues/194
`;
const EMPTY_PROPS = {};
function applyNodeProps(instance, props, oldProps = EMPTY_PROPS) {
// don't use zIndex in react-konva
if (!zIndexWarningShowed && 'zIndex' in props) {
console.warn(Z_INDEX_WARNING);
zIndexWarningShowed = true;
}
// check correct draggable usage
if (!dragWarningShowed && props.draggable) {
var hasPosition = props.x !== undefined || props.y !== undefined;
var hasEvents = props.onDragEnd || props.onDragMove;
if (hasPosition && !hasEvents) {
console.warn(DRAGGABLE_WARNING);
dragWarningShowed = true;
}
}
var updatedProps = {};
var hasUpdates = false;
// check old props
// we need to unset properties that are not in new props
// and remove all events
for (var key in oldProps) {
if (propsToSkip[key]) {
continue;
}
var isEvent = key.slice(0, 2) === 'on';
var propChanged = oldProps[key] !== props[key];
// if that is a changed event, we need to remove it
if (isEvent && propChanged) {
var eventName = key.substr(2).toLowerCase();
if (eventName.substr(0, 7) === 'content') {
eventName =
'content' +
eventName.substr(7, 1).toUpperCase() +
eventName.substr(8);
}
// Remove by namespace because react-konva binds at most one listener
// for each event in that namespace.
instance.off(eventName + exports.EVENTS_NAMESPACE);
}
var toRemove = !props.hasOwnProperty(key);
if (!isEvent && toRemove) {
updatedProps[key] = undefined;
hasUpdates = true;
}
}
var strictUpdate = useStrictMode || props._useStrictMode;
const newEvents = {};
for (var key in props) {
if (propsToSkip[key]) {
continue;
}
var isEvent = key.slice(0, 2) === 'on';
var toAdd = oldProps[key] !== props[key];
if (isEvent && toAdd) {
var eventName = key.substr(2).toLowerCase();
if (eventName.substr(0, 7) === 'content') {
eventName =
'content' +
eventName.substr(7, 1).toUpperCase() +
eventName.substr(8);
}
// check that event is not undefined
if (props[key]) {
newEvents[eventName] = props[key];
}
}
if (!isEvent &&
(props[key] !== oldProps[key] ||
(strictUpdate && props[key] !== instance.getAttr(key)))) {
hasUpdates = true;
updatedProps[key] = props[key];
}
}
if (hasUpdates) {
instance.setAttrs(updatedProps);
updatePicture(instance);
}
// subscribe to events AFTER we set attrs
// we need it to fix https://github.com/konvajs/react-konva/issues/471
// settings attrs may add events. Like "draggable: true" will add "mousedown" listener
for (var eventName in newEvents) {
// first clear any existing listeners, it is required for strict mode
instance.off(eventName + exports.EVENTS_NAMESPACE);
// then attach new one
instance.on(eventName + exports.EVENTS_NAMESPACE, newEvents[eventName]);
}
}
function updatePicture(node) {
if (!Global_js_1.Konva.autoDrawEnabled) {
var drawingNode = node.getLayer() || node.getStage();
drawingNode && drawingNode.batchDraw();
}
}
+114
View File
@@ -0,0 +1,114 @@
{
"license": "MIT",
"name": "react-konva",
"description": "React components for the Konva 2d canvas library. Build interactive graphics, design editors, and whiteboards with JSX.",
"keywords": [
"react",
"canvas",
"jsx",
"konva",
"graphics",
"design-editor",
"canvas-editor",
"whiteboard",
"transformer"
],
"homepage": "https://konvajs.org/docs/react/index.html",
"version": "19.3.2",
"bugs": "https://github.com/konvajs/react-konva/issues",
"main": "lib/ReactKonva.js",
"module": "es/ReactKonva.js",
"exports": {
".": {
"import": {
"types": "./es/ReactKonvaCore.d.ts",
"default": "./es/ReactKonva.js"
},
"require": {
"types": "./lib/ReactKonvaCore.d.ts",
"default": "./lib/ReactKonva.js"
}
},
"./lib/*.js": {
"import": "./es/*.js",
"require": "./lib/*.js"
},
"./lib/*.d.ts": "./lib/*.d.ts",
"./lib/*": {
"import": "./es/*.js",
"require": "./lib/*.js"
},
"./es/*.js": "./es/*.js",
"./es/*.d.ts": "./es/*.d.ts",
"./es/package.json": "./es/package.json",
"./es/*": "./es/*.js",
"./*": "./*"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/konvajs/react-konva.git"
},
"dependencies": {
"@types/react-reconciler": "0.34.0",
"its-fine": "^2.1.1",
"react-reconciler": "0.34.0",
"scheduler": "0.28.0"
},
"funding": [
{
"type": "patreon",
"url": "https://www.patreon.com/lavrton"
},
{
"type": "opencollective",
"url": "https://opencollective.com/konva"
},
{
"type": "github",
"url": "https://github.com/sponsors/lavrton"
}
],
"peerDependencies": {
"konva": "^8.0.1 || ^7.2.5 || ^9.0.0 || ^10.0.0",
"react": "^19.3.0",
"react-dom": "^19.3.0"
},
"devDependencies": {
"@types/react": "19.3.0",
"@vitest/browser": "^5.0.4",
"@vitest/browser-playwright": "^5.0.4",
"konva": "^10.9.0",
"mobx": "^7.0.6",
"mobx-react-lite": "^5.1.0",
"playwright": "^1.64.1",
"react": "^19.3.1",
"react-dom": "^19.3.1",
"typescript": "^7.0.2",
"use-image": "^1.1.4",
"vitest": "^5.0.4"
},
"scripts": {
"build": "node ./build.mjs",
"test:typings": "tsc --noEmit && tsc -p test/types/tsconfig.json",
"preversion": "npm test",
"version": "npm run build",
"test": "npm run test:browser && npm run test:performance && npm run test:ssr && npm run test:typings",
"test:browser": "vitest run && npm run test:prod",
"test:performance": "NODE_ENV=production vitest run --config benchmarks/vitest.counts.config.mts",
"test:prod": "NODE_ENV=production vitest run",
"test:ssr": "npm run build && node --no-experimental-detect-module test/11-ssr.mjs && node test/24-package-exports.mjs && node test/29-devtools.mjs",
"bench:events": "node benchmarks/run.mjs",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:debug": "HEADLESS=false vitest"
},
"typings": "ReactKonvaCore.d.ts",
"files": [
"README.md",
"docs/react-compatibility.md",
"RELEASE_NOTES.md",
"lib",
"es",
"ReactKonvaCore.d.ts"
]
}