@depup/react-konva 19.2.3-depup.0 → 19.2.6-depup.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,8 +13,8 @@ npm install @depup/react-konva
13
13
 
14
14
  | Field | Value |
15
15
  |-------|-------|
16
- | Original | [react-konva](https://www.npmjs.com/package/react-konva) @ 19.2.3 |
17
- | Processed | 2026-03-18 |
16
+ | Original | [react-konva](https://www.npmjs.com/package/react-konva) @ 19.2.6 |
17
+ | Processed | 2026-09-06 |
18
18
  | Smoke test | failed |
19
19
  | Deps updated | 0 |
20
20
 
package/changes.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "bumped": {},
3
- "timestamp": "2026-03-18T22:41:31.339Z",
3
+ "timestamp": "2026-09-06T00:59:49.028Z",
4
4
  "totalUpdated": 0
5
5
  }
@@ -14,7 +14,7 @@ import Konva from 'konva/lib/Core.js';
14
14
  import ReactFiberReconciler from 'react-reconciler';
15
15
  import { ConcurrentRoot } from 'react-reconciler/constants.js';
16
16
  import * as HostConfig from './ReactKonvaHostConfig.js';
17
- import { applyNodeProps, toggleStrictMode } from './makeUpdates.js';
17
+ import { applyNodeProps, toggleStrictMode, _injectFlush } from './makeUpdates.js';
18
18
  import { useContextBridge, FiberProvider } from 'its-fine';
19
19
  function usePrevious(value) {
20
20
  const ref = React.useRef({});
@@ -106,38 +106,12 @@ const StageWrap = (props) => {
106
106
  React.useLayoutEffect(() => {
107
107
  _setRef(stage.current);
108
108
  applyNodeProps(stage.current, props, oldProps);
109
- // =============================================================================
110
- // CRITICAL FIX - DO NOT REMOVE
111
- // =============================================================================
112
- // This flushSyncFromReconciler wrapper is CRITICAL for React 19 compatibility.
113
- //
114
- // THE BUG:
115
- // When using useSyncExternalStore (MobX, Zustand, etc.) with react-konva,
116
- // parent component's useLayoutEffect couldn't find newly added Konva nodes.
117
- // The nodes were added to the store, but not yet rendered to the canvas.
118
- //
119
- // ROOT CAUSE:
120
- // React 19's updateContainer can defer Konva reconciler work to a later
121
- // microtask. Combined with Bridge component (from its-fine) and Html components
122
- // that create secondary React roots, this caused child components to render
123
- // AFTER parent's useLayoutEffect completed.
124
- //
125
- // THE FIX:
126
- // flushSyncFromReconciler forces ALL scheduled Konva reconciler work to
127
- // complete synchronously within this useLayoutEffect, ensuring child
128
- // components render before any parent effects run.
129
- //
130
- // WARNING - NOT COVERED BY TESTS:
131
- // This bug CANNOT be reproduced in local test environments (Vitest/Playwright).
132
- // It only manifests in production builds with specific conditions:
133
- // - MobX/useSyncExternalStore for state management
134
- // - Html components with secondary React roots using Bridge
135
- // - Complex component hierarchies (multiple pages/stages)
136
- // The fix was verified in Polotno production app.
137
- // =============================================================================
138
- KonvaRenderer.flushSyncFromReconciler(() => {
139
- KonvaRenderer.updateContainer(React.createElement(Bridge, {}, props.children), fiberRef.current, null);
140
- });
109
+ // updateContainer schedules sync-lane work; with async scheduleMicrotask
110
+ // the work is queued, so flushSyncWork() drains it inline here so that
111
+ // a parent useLayoutEffect can read Konva nodes added by subscribing
112
+ // children. (Lighter than flushSyncFromReconciler no callback wrapper.)
113
+ KonvaRenderer.updateContainer(React.createElement(Bridge, {}, props.children), fiberRef.current, null);
114
+ KonvaRenderer.flushSyncWork();
141
115
  });
142
116
  return React.createElement('div', {
143
117
  ref: container,
@@ -172,9 +146,14 @@ export const RegularPolygon = 'RegularPolygon';
172
146
  export const Arrow = 'Arrow';
173
147
  export const Shape = 'Shape';
174
148
  export const Transformer = 'Transformer';
175
- export const version = '19.2.3';
149
+ export const version = '19.2.6';
176
150
  // @ts-ignore
177
151
  export const KonvaRenderer = ReactFiberReconciler(HostConfig);
152
+ // Konva event handlers (bound in makeUpdates) flush pending reconciler work
153
+ // inline after the user handler returns, so Konva code that synchronously
154
+ // reads node state right after firing an event (Transformer.update) sees the
155
+ // committed result. See wrapEventHandler in makeUpdates.ts.
156
+ _injectFlush(() => KonvaRenderer.flushSyncWork());
178
157
  // Update Stage component declaration
179
158
  export const Stage = React.forwardRef((props, ref) => {
180
159
  return React.createElement(FiberProvider, {}, React.createElement(StageWrap, { ...props, forwardedRef: ref }));
@@ -87,10 +87,12 @@ export function getChildHostContext() {
87
87
  export const scheduleTimeout = setTimeout;
88
88
  export const cancelTimeout = clearTimeout;
89
89
  export const supportsMicrotasks = true;
90
- // Run microtasks synchronously for immediate updates
91
- export const scheduleMicrotask = (fn) => {
92
- fn();
93
- };
90
+ // Async fixes the mobx-react-lite snapshot race. Stage's useLayoutEffect
91
+ // then explicitly calls `flushSyncWork()` to keep parent useLayoutEffect able
92
+ // to read Konva nodes added by subscribing children (see ReactKonvaCore.tsx).
93
+ export const scheduleMicrotask = typeof queueMicrotask === 'function'
94
+ ? queueMicrotask
95
+ : (fn) => Promise.resolve(null).then(fn);
94
96
  export const noTimeout = -1;
95
97
  // export const schedulePassiveEffects = scheduleDeferredCallback;
96
98
  // export const cancelPassiveEffects = cancelDeferredCallback;
@@ -189,6 +191,10 @@ export function setCurrentUpdatePriority(newPriority) {
189
191
  export function getCurrentUpdatePriority() {
190
192
  return currentUpdatePriority;
191
193
  }
194
+ // Load-bearing: pins secondary commits to React's sync lane so they land
195
+ // before the scheduler yields. Required for parent useLayoutEffect to read
196
+ // Konva nodes added by subscribing children. Changing to Default/Continuous/
197
+ // Idle breaks ~18 tests in test/01-mounting and test/16-scheduling-invariants.
192
198
  export function resolveUpdatePriority() {
193
199
  return DiscreteEventPriority;
194
200
  }
package/es/makeUpdates.js CHANGED
@@ -25,6 +25,33 @@ react-konva may get confused with ordering. Just define correct order of element
25
25
  For more info see: https://github.com/konvajs/react-konva/issues/194
26
26
  `;
27
27
  const EMPTY_PROPS = {};
28
+ // Konva internals synchronously read node state right after firing events —
29
+ // e.g. Transformer fires "transform", the user handler calls setState, and
30
+ // Transformer.update() immediately repositions its anchors from the node.
31
+ // React commits async (microtask), so without a flush the chrome is measured
32
+ // from the stale node on every event. Draining the reconciler's pending sync
33
+ // work right after the user handler returns keeps Konva's read-after-fire
34
+ // contract. Injected from ReactKonvaCore to avoid an import cycle.
35
+ let flushPendingWork = () => { };
36
+ export function _injectFlush(fn) {
37
+ flushPendingWork = fn;
38
+ }
39
+ let isFlushing = false;
40
+ function wrapEventHandler(handler) {
41
+ return function (...args) {
42
+ const result = handler.apply(this, args);
43
+ if (!isFlushing) {
44
+ isFlushing = true;
45
+ try {
46
+ flushPendingWork();
47
+ }
48
+ finally {
49
+ isFlushing = false;
50
+ }
51
+ }
52
+ return result;
53
+ };
54
+ }
28
55
  export function applyNodeProps(instance, props, oldProps = EMPTY_PROPS) {
29
56
  // don't use zIndex in react-konva
30
57
  if (!zIndexWarningShowed && 'zIndex' in props) {
@@ -58,7 +85,9 @@ export function applyNodeProps(instance, props, oldProps = EMPTY_PROPS) {
58
85
  eventName.substr(7, 1).toUpperCase() +
59
86
  eventName.substr(8);
60
87
  }
61
- instance.off(eventName, oldProps[key]);
88
+ // the bound listener is a wrapper, not the user handler, so remove by
89
+ // our namespace (react-konva binds at most one listener per event there)
90
+ instance.off(eventName + EVENTS_NAMESPACE);
62
91
  }
63
92
  var toRemove = !props.hasOwnProperty(key);
64
93
  if (toRemove) {
@@ -106,7 +135,7 @@ export function applyNodeProps(instance, props, oldProps = EMPTY_PROPS) {
106
135
  // first clear any existing listeners, it is required for strict mode
107
136
  instance.off(eventName + EVENTS_NAMESPACE);
108
137
  // then attach new one
109
- instance.on(eventName + EVENTS_NAMESPACE, newEvents[eventName]);
138
+ instance.on(eventName + EVENTS_NAMESPACE, wrapEventHandler(newEvents[eventName]));
110
139
  }
111
140
  }
112
141
  export function updatePicture(node) {
@@ -145,38 +145,12 @@ const StageWrap = (props) => {
145
145
  react_1.default.useLayoutEffect(() => {
146
146
  _setRef(stage.current);
147
147
  (0, makeUpdates_js_1.applyNodeProps)(stage.current, props, oldProps);
148
- // =============================================================================
149
- // CRITICAL FIX - DO NOT REMOVE
150
- // =============================================================================
151
- // This flushSyncFromReconciler wrapper is CRITICAL for React 19 compatibility.
152
- //
153
- // THE BUG:
154
- // When using useSyncExternalStore (MobX, Zustand, etc.) with react-konva,
155
- // parent component's useLayoutEffect couldn't find newly added Konva nodes.
156
- // The nodes were added to the store, but not yet rendered to the canvas.
157
- //
158
- // ROOT CAUSE:
159
- // React 19's updateContainer can defer Konva reconciler work to a later
160
- // microtask. Combined with Bridge component (from its-fine) and Html components
161
- // that create secondary React roots, this caused child components to render
162
- // AFTER parent's useLayoutEffect completed.
163
- //
164
- // THE FIX:
165
- // flushSyncFromReconciler forces ALL scheduled Konva reconciler work to
166
- // complete synchronously within this useLayoutEffect, ensuring child
167
- // components render before any parent effects run.
168
- //
169
- // WARNING - NOT COVERED BY TESTS:
170
- // This bug CANNOT be reproduced in local test environments (Vitest/Playwright).
171
- // It only manifests in production builds with specific conditions:
172
- // - MobX/useSyncExternalStore for state management
173
- // - Html components with secondary React roots using Bridge
174
- // - Complex component hierarchies (multiple pages/stages)
175
- // The fix was verified in Polotno production app.
176
- // =============================================================================
177
- exports.KonvaRenderer.flushSyncFromReconciler(() => {
178
- exports.KonvaRenderer.updateContainer(react_1.default.createElement(Bridge, {}, props.children), fiberRef.current, null);
179
- });
148
+ // updateContainer schedules sync-lane work; with async scheduleMicrotask
149
+ // the work is queued, so flushSyncWork() drains it inline here so that
150
+ // a parent useLayoutEffect can read Konva nodes added by subscribing
151
+ // children. (Lighter than flushSyncFromReconciler no callback wrapper.)
152
+ exports.KonvaRenderer.updateContainer(react_1.default.createElement(Bridge, {}, props.children), fiberRef.current, null);
153
+ exports.KonvaRenderer.flushSyncWork();
180
154
  });
181
155
  return react_1.default.createElement('div', {
182
156
  ref: container,
@@ -211,9 +185,14 @@ exports.RegularPolygon = 'RegularPolygon';
211
185
  exports.Arrow = 'Arrow';
212
186
  exports.Shape = 'Shape';
213
187
  exports.Transformer = 'Transformer';
214
- exports.version = '19.2.3';
188
+ exports.version = '19.2.6';
215
189
  // @ts-ignore
216
190
  exports.KonvaRenderer = (0, react_reconciler_1.default)(HostConfig);
191
+ // Konva event handlers (bound in makeUpdates) flush pending reconciler work
192
+ // inline after the user handler returns, so Konva code that synchronously
193
+ // reads node state right after firing an event (Transformer.update) sees the
194
+ // committed result. See wrapEventHandler in makeUpdates.ts.
195
+ (0, makeUpdates_js_1._injectFlush)(() => exports.KonvaRenderer.flushSyncWork());
217
196
  // Update Stage component declaration
218
197
  exports.Stage = react_1.default.forwardRef((props, ref) => {
219
198
  return react_1.default.createElement(its_fine_1.FiberProvider, {}, react_1.default.createElement(StageWrap, { ...props, forwardedRef: ref }));
@@ -143,11 +143,12 @@ function getChildHostContext() {
143
143
  exports.scheduleTimeout = setTimeout;
144
144
  exports.cancelTimeout = clearTimeout;
145
145
  exports.supportsMicrotasks = true;
146
- // Run microtasks synchronously for immediate updates
147
- const scheduleMicrotask = (fn) => {
148
- fn();
149
- };
150
- exports.scheduleMicrotask = scheduleMicrotask;
146
+ // Async fixes the mobx-react-lite snapshot race. Stage's useLayoutEffect
147
+ // then explicitly calls `flushSyncWork()` to keep parent useLayoutEffect able
148
+ // to read Konva nodes added by subscribing children (see ReactKonvaCore.tsx).
149
+ exports.scheduleMicrotask = typeof queueMicrotask === 'function'
150
+ ? queueMicrotask
151
+ : (fn) => Promise.resolve(null).then(fn);
151
152
  exports.noTimeout = -1;
152
153
  // export const schedulePassiveEffects = scheduleDeferredCallback;
153
154
  // export const cancelPassiveEffects = cancelDeferredCallback;
@@ -246,6 +247,10 @@ function setCurrentUpdatePriority(newPriority) {
246
247
  function getCurrentUpdatePriority() {
247
248
  return currentUpdatePriority;
248
249
  }
250
+ // Load-bearing: pins secondary commits to React's sync lane so they land
251
+ // before the scheduler yields. Required for parent useLayoutEffect to read
252
+ // Konva nodes added by subscribing children. Changing to Default/Continuous/
253
+ // Idle breaks ~18 tests in test/01-mounting and test/16-scheduling-invariants.
249
254
  function resolveUpdatePriority() {
250
255
  return constants_js_1.DiscreteEventPriority;
251
256
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EVENTS_NAMESPACE = void 0;
4
4
  exports.toggleStrictMode = toggleStrictMode;
5
+ exports._injectFlush = _injectFlush;
5
6
  exports.applyNodeProps = applyNodeProps;
6
7
  exports.updatePicture = updatePicture;
7
8
  const Global_js_1 = require("konva/lib/Global.js");
@@ -31,6 +32,33 @@ react-konva may get confused with ordering. Just define correct order of element
31
32
  For more info see: https://github.com/konvajs/react-konva/issues/194
32
33
  `;
33
34
  const EMPTY_PROPS = {};
35
+ // Konva internals synchronously read node state right after firing events —
36
+ // e.g. Transformer fires "transform", the user handler calls setState, and
37
+ // Transformer.update() immediately repositions its anchors from the node.
38
+ // React commits async (microtask), so without a flush the chrome is measured
39
+ // from the stale node on every event. Draining the reconciler's pending sync
40
+ // work right after the user handler returns keeps Konva's read-after-fire
41
+ // contract. Injected from ReactKonvaCore to avoid an import cycle.
42
+ let flushPendingWork = () => { };
43
+ function _injectFlush(fn) {
44
+ flushPendingWork = fn;
45
+ }
46
+ let isFlushing = false;
47
+ function wrapEventHandler(handler) {
48
+ return function (...args) {
49
+ const result = handler.apply(this, args);
50
+ if (!isFlushing) {
51
+ isFlushing = true;
52
+ try {
53
+ flushPendingWork();
54
+ }
55
+ finally {
56
+ isFlushing = false;
57
+ }
58
+ }
59
+ return result;
60
+ };
61
+ }
34
62
  function applyNodeProps(instance, props, oldProps = EMPTY_PROPS) {
35
63
  // don't use zIndex in react-konva
36
64
  if (!zIndexWarningShowed && 'zIndex' in props) {
@@ -64,7 +92,9 @@ function applyNodeProps(instance, props, oldProps = EMPTY_PROPS) {
64
92
  eventName.substr(7, 1).toUpperCase() +
65
93
  eventName.substr(8);
66
94
  }
67
- instance.off(eventName, oldProps[key]);
95
+ // the bound listener is a wrapper, not the user handler, so remove by
96
+ // our namespace (react-konva binds at most one listener per event there)
97
+ instance.off(eventName + exports.EVENTS_NAMESPACE);
68
98
  }
69
99
  var toRemove = !props.hasOwnProperty(key);
70
100
  if (toRemove) {
@@ -112,7 +142,7 @@ function applyNodeProps(instance, props, oldProps = EMPTY_PROPS) {
112
142
  // first clear any existing listeners, it is required for strict mode
113
143
  instance.off(eventName + exports.EVENTS_NAMESPACE);
114
144
  // then attach new one
115
- instance.on(eventName + exports.EVENTS_NAMESPACE, newEvents[eventName]);
145
+ instance.on(eventName + exports.EVENTS_NAMESPACE, wrapEventHandler(newEvents[eventName]));
116
146
  }
117
147
  }
118
148
  function updatePicture(node) {
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "license": "MIT",
3
3
  "name": "@depup/react-konva",
4
- "description": "React binding to canvas element via Konva framework (with updated dependencies)",
5
- "version": "19.2.3-depup.0",
4
+ "description": "React components for the Konva 2d canvas library. Build interactive graphics, design editors, and whiteboards with JSX. (with updated dependencies)",
6
5
  "keywords": [
7
6
  "react-konva",
8
7
  "depup",
@@ -13,8 +12,15 @@
13
12
  "react",
14
13
  "canvas",
15
14
  "jsx",
16
- "konva"
15
+ "konva",
16
+ "graphics",
17
+ "design-editor",
18
+ "canvas-editor",
19
+ "whiteboard",
20
+ "transformer"
17
21
  ],
22
+ "homepage": "https://konvajs.org/docs/react/index.html",
23
+ "version": "19.2.6-depup.0",
18
24
  "bugs": "https://github.com/konvajs/react-konva/issues",
19
25
  "main": "lib/ReactKonva.js",
20
26
  "module": "es/ReactKonva.js",
@@ -28,9 +34,6 @@
28
34
  "react-reconciler": "0.33.0",
29
35
  "scheduler": "0.27.0"
30
36
  },
31
- "targets": {
32
- "none": {}
33
- },
34
37
  "funding": [
35
38
  {
36
39
  "type": "patreon",
@@ -54,7 +57,9 @@
54
57
  "@types/react": "19.2.14",
55
58
  "@vitest/browser": "^4.0.18",
56
59
  "@vitest/browser-playwright": "^4.0.18",
57
- "konva": "^10.2.0",
60
+ "konva": "^10.3.3",
61
+ "mobx": "^6.13.5",
62
+ "mobx-react-lite": "^4.1.0",
58
63
  "playwright": "^1.58.2",
59
64
  "react": "^19.2.4",
60
65
  "react-dom": "^19.2.4",
@@ -63,22 +68,23 @@
63
68
  "vitest": "^4.0.18"
64
69
  },
65
70
  "scripts": {
66
- "build": "tsc -outDir ./es && tsc -module commonjs -outDir ./lib && cp ./ReactKonvaCore.d.ts ./lib && cp ./ReactKonvaCore.d.ts ./es && node ./replace-version.js",
71
+ "build": "node ./build.mjs",
67
72
  "test:typings": "tsc --noEmit",
68
73
  "preversion": "npm test",
69
74
  "version": "npm run build",
70
75
  "postversion": "",
71
- "test": "vitest run && npm run test:typings",
76
+ "test": "vitest run && npm run test:prod && npm run test:ssr && npm run test:typings",
77
+ "test:prod": "NODE_ENV=production vitest run",
78
+ "test:ssr": "npm run build && node test/11-ssr.mjs",
72
79
  "test:watch": "vitest",
73
80
  "test:ui": "vitest --ui",
74
81
  "test:debug": "HEADLESS=false vitest"
75
82
  },
76
- "typings": "react-konva.d.ts",
83
+ "typings": "ReactKonvaCore.d.ts",
77
84
  "files": [
78
85
  "README.md",
79
86
  "lib",
80
87
  "es",
81
- "react-konva.d.ts",
82
88
  "ReactKonvaCore.d.ts",
83
89
  "changes.json"
84
90
  ],
@@ -86,8 +92,8 @@
86
92
  "changes": {},
87
93
  "depsUpdated": 0,
88
94
  "originalPackage": "react-konva",
89
- "originalVersion": "19.2.3",
90
- "processedAt": "2026-03-18T22:41:39.066Z",
95
+ "originalVersion": "19.2.6",
96
+ "processedAt": "2026-09-06T00:59:51.663Z",
91
97
  "smokeTest": "failed"
92
98
  }
93
99
  }
package/react-konva.d.ts DELETED
@@ -1 +0,0 @@
1
- export * from './ReactKonvaCore';