@yorab/react-paint 0.18.1 → 1.0.0-beta.1

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.
Files changed (247) hide show
  1. package/README.md +266 -22
  2. package/dist/_chunks/canvas-CDYlr833.js +1 -0
  3. package/dist/_chunks/editor-BARYd5Od.js +1 -0
  4. package/dist/canvas/components/Canvas.d.ts +15 -3
  5. package/dist/canvas/components/EditTextBox.d.ts +4 -4
  6. package/dist/canvas/constants/app.d.ts +32 -17
  7. package/dist/canvas/constants/grid.d.ts +7 -0
  8. package/dist/canvas/constants/picture.d.ts +8 -0
  9. package/dist/canvas/constants/shapes.d.ts +2 -2
  10. package/dist/canvas/constants/tools.d.ts +12 -2
  11. package/dist/canvas/constants/zoom.d.ts +4 -2
  12. package/dist/canvas/context/PaintContext.d.ts +9 -0
  13. package/dist/canvas/hooks/useAnnotationsStyle.d.ts +5 -0
  14. package/dist/canvas/hooks/useAutoScroll.d.ts +4 -0
  15. package/dist/canvas/hooks/useCanvasRenderer.d.ts +17 -0
  16. package/dist/canvas/hooks/useDrawableCanvas.d.ts +8 -5
  17. package/dist/canvas/hooks/useDropPicture.d.ts +7 -0
  18. package/dist/canvas/hooks/useKeyboard.d.ts +6 -32
  19. package/dist/canvas/hooks/usePaintState.d.ts +9 -0
  20. package/dist/canvas/hooks/usePinchZoomAndMove.d.ts +3 -13
  21. package/dist/canvas/hooks/useReactPaint.d.ts +11 -163
  22. package/dist/canvas/hooks/useResizeObserver.d.ts +1 -0
  23. package/dist/canvas/hooks/useShapeEvents.d.ts +7 -0
  24. package/dist/canvas/hooks/useZoom.d.ts +4 -17
  25. package/dist/canvas/index.d.ts +24 -17
  26. package/dist/canvas/index.js +1 -1
  27. package/dist/canvas/store/createStore.d.ts +14 -0
  28. package/dist/canvas/store/eventEmitter.d.ts +11 -0
  29. package/dist/canvas/store/internal.d.ts +5 -0
  30. package/dist/canvas/store/paintStore.d.ts +898 -0
  31. package/dist/canvas/store/selectors.d.ts +855 -0
  32. package/dist/canvas/store/shapesReducer.d.ts +66 -0
  33. package/dist/canvas/store/types.d.ts +72 -0
  34. package/dist/canvas/types/Canvas.d.ts +19 -0
  35. package/dist/{src/common → canvas}/types/Mode.d.ts +8 -15
  36. package/dist/{common → canvas}/types/Settings.d.ts +6 -1
  37. package/dist/{common → canvas}/types/Shapes.d.ts +27 -21
  38. package/dist/canvas/types/editor.d.ts +388 -0
  39. package/dist/{common → canvas}/types/tools.d.ts +10 -6
  40. package/dist/{common → canvas}/utils/array.d.ts +2 -0
  41. package/dist/canvas/utils/canvas.d.ts +18 -2
  42. package/dist/canvas/utils/click.d.ts +17 -0
  43. package/dist/canvas/utils/data.d.ts +6 -5
  44. package/dist/canvas/utils/equality.d.ts +5 -0
  45. package/dist/canvas/utils/file.d.ts +36 -266
  46. package/dist/canvas/utils/grid.d.ts +1 -1
  47. package/dist/canvas/utils/intersect.d.ts +24 -14
  48. package/dist/canvas/utils/object.d.ts +11 -0
  49. package/dist/{src/common/utils/selection.d.ts → canvas/utils/selectedShapes.d.ts} +2 -2
  50. package/dist/canvas/utils/selection/anchors.d.ts +16 -0
  51. package/dist/canvas/utils/selection/groupSelection.d.ts +3 -3
  52. package/dist/canvas/utils/selection/lineSelection.d.ts +3 -10
  53. package/dist/canvas/utils/selection/rectSelection.d.ts +6 -11
  54. package/dist/canvas/utils/selection/selectionFrame.d.ts +2 -2
  55. package/dist/canvas/utils/selection.d.ts +7 -5
  56. package/dist/canvas/utils/shapeInput.d.ts +45 -0
  57. package/dist/canvas/utils/shapePatch.d.ts +10 -0
  58. package/dist/canvas/utils/shapes/brush.d.ts +6 -6
  59. package/dist/canvas/utils/shapes/circle.d.ts +5 -5
  60. package/dist/canvas/utils/shapes/curve.d.ts +4 -4
  61. package/dist/canvas/utils/shapes/ellipse.d.ts +5 -5
  62. package/dist/canvas/utils/shapes/group.d.ts +9 -3
  63. package/dist/canvas/utils/shapes/index.d.ts +31 -5
  64. package/dist/canvas/utils/shapes/line.d.ts +5 -5
  65. package/dist/canvas/utils/shapes/path.d.ts +16 -8
  66. package/dist/canvas/utils/shapes/picture.d.ts +7 -5
  67. package/dist/canvas/utils/shapes/polygon.d.ts +4 -4
  68. package/dist/canvas/utils/shapes/rectangle.d.ts +5 -5
  69. package/dist/canvas/utils/shapes/text.d.ts +12 -6
  70. package/dist/canvas/utils/shapes/triangle.d.ts +2 -2
  71. package/dist/canvas/utils/tools.d.ts +12 -3
  72. package/dist/canvas/utils/transform.d.ts +5 -4
  73. package/dist/canvas/utils/trigo.d.ts +17 -1
  74. package/dist/canvas/utils/util.d.ts +14 -0
  75. package/dist/canvas/utils/zoom.d.ts +29 -15
  76. package/dist/editor/components/common/Button.d.ts +0 -1
  77. package/dist/editor/components/common/FileInput.d.ts +0 -1
  78. package/dist/editor/components/common/Loading.d.ts +1 -2
  79. package/dist/editor/components/common/Menu.d.ts +3 -2
  80. package/dist/editor/components/common/Modal.d.ts +1 -2
  81. package/dist/editor/components/common/Panel.d.ts +1 -2
  82. package/dist/editor/components/common/Snackbar.d.ts +2 -3
  83. package/dist/editor/components/settings/ClosedPointsField.d.ts +1 -1
  84. package/dist/editor/components/settings/ColorField.d.ts +1 -2
  85. package/dist/editor/components/settings/DeleteButton.d.ts +3 -4
  86. package/dist/editor/components/settings/FontFamilyField.d.ts +1 -2
  87. package/dist/editor/components/settings/LayoutButton.d.ts +1 -2
  88. package/dist/editor/components/settings/Layouts.d.ts +4 -15
  89. package/dist/editor/components/settings/LineArrowField.d.ts +1 -2
  90. package/dist/editor/components/settings/LineTypeField.d.ts +2 -3
  91. package/dist/editor/components/settings/RangeField.d.ts +1 -2
  92. package/dist/editor/components/settings/SelectField.d.ts +1 -2
  93. package/dist/editor/components/settings/SettingsBar.d.ts +3 -18
  94. package/dist/editor/components/settings/ToggleField.d.ts +2 -1
  95. package/dist/editor/components/settings/ZoomButton.d.ts +1 -2
  96. package/dist/editor/components/settings/ZoomPanel.d.ts +4 -0
  97. package/dist/editor/components/toolbox/ActionButton.d.ts +9 -0
  98. package/dist/editor/components/toolbox/ContextMenu.d.ts +5 -24
  99. package/dist/editor/components/toolbox/ExportModal.d.ts +1 -2
  100. package/dist/editor/components/toolbox/LoadFileTool.d.ts +4 -4
  101. package/dist/editor/components/toolbox/MenuGroup.d.ts +1 -7
  102. package/dist/editor/components/toolbox/PictureUrlInput.d.ts +2 -2
  103. package/dist/editor/components/toolbox/Tool.d.ts +3 -3
  104. package/dist/editor/components/toolbox/Toolbar.d.ts +5 -16
  105. package/dist/editor/components/toolbox/ToolbarGroup.d.ts +2 -3
  106. package/dist/editor/constants/options.d.ts +11 -1
  107. package/dist/editor/constants/snackbar.d.ts +1 -1
  108. package/dist/editor/constants/style.d.ts +0 -9
  109. package/dist/editor/constants/tools.d.ts +12 -32
  110. package/dist/editor/editor.css +1 -0
  111. package/dist/editor/hooks/useDrag.d.ts +3 -3
  112. package/dist/editor/hooks/useMenu.d.ts +3 -2
  113. package/dist/editor/hooks/usePressOutside.d.ts +7 -0
  114. package/dist/editor/hooks/useSnackbar.d.ts +1 -1
  115. package/dist/editor/index.d.ts +12 -22
  116. package/dist/editor/index.js +1 -1
  117. package/dist/editor/types/tools.d.ts +7 -0
  118. package/dist/editor/utils/dom.d.ts +1 -0
  119. package/dist/editor/utils/style.d.ts +7 -1
  120. package/dist/editor/utils/toolbar.d.ts +1 -2
  121. package/dist/editor/utils/util.d.ts +5 -0
  122. package/dist/index.d.ts +2 -5
  123. package/dist/react-paint.css +1 -1
  124. package/dist/react-paint.js +1 -1
  125. package/dist/react-paint.umd.cjs +1 -1
  126. package/package.json +37 -26
  127. package/dist/_chunks/array-DuJem3vg.js +0 -1
  128. package/dist/_chunks/canvas-DF1RWuxW.js +0 -1
  129. package/dist/_chunks/editor-DuVskl0R.js +0 -1
  130. package/dist/canvas/context/CanvasContext.d.ts +0 -55
  131. package/dist/canvas/hooks/useComponent.d.ts +0 -12
  132. package/dist/canvas/hooks/useDoubleClick.d.ts +0 -5
  133. package/dist/canvas/hooks/useEventListener.d.ts +0 -2
  134. package/dist/canvas/hooks/useShapes.d.ts +0 -44
  135. package/dist/common/hooks/useDebounce.d.ts +0 -2
  136. package/dist/common/types/Canvas.d.ts +0 -8
  137. package/dist/common/types/Mode.d.ts +0 -64
  138. package/dist/common/types/react.d.ts +0 -6
  139. package/dist/common/utils/dom.d.ts +0 -1
  140. package/dist/common/utils/object.d.ts +0 -4
  141. package/dist/common/utils/selection.d.ts +0 -4
  142. package/dist/common/utils/util.d.ts +0 -3
  143. package/dist/editor/constants/grid.d.ts +0 -7
  144. package/dist/src/canvas/components/Canvas.d.ts +0 -4
  145. package/dist/src/canvas/components/EditTextBox.d.ts +0 -13
  146. package/dist/src/canvas/constants/app.d.ts +0 -37
  147. package/dist/src/canvas/constants/grid.d.ts +0 -1
  148. package/dist/src/canvas/constants/keyboard.d.ts +0 -27
  149. package/dist/src/canvas/constants/picture.d.ts +0 -1
  150. package/dist/src/canvas/constants/rendering.d.ts +0 -3
  151. package/dist/src/canvas/constants/shapes.d.ts +0 -10
  152. package/dist/src/canvas/constants/tools.d.ts +0 -4
  153. package/dist/src/canvas/constants/zoom.d.ts +0 -5
  154. package/dist/src/canvas/context/CanvasContext.d.ts +0 -55
  155. package/dist/src/canvas/hooks/useComponent.d.ts +0 -12
  156. package/dist/src/canvas/hooks/useDoubleClick.d.ts +0 -5
  157. package/dist/src/canvas/hooks/useDrawableCanvas.d.ts +0 -6
  158. package/dist/src/canvas/hooks/useEventListener.d.ts +0 -2
  159. package/dist/src/canvas/hooks/useKeyboard.d.ts +0 -33
  160. package/dist/src/canvas/hooks/usePinchZoomAndMove.d.ts +0 -17
  161. package/dist/src/canvas/hooks/useReactPaint.d.ts +0 -164
  162. package/dist/src/canvas/hooks/useResizeObserver.d.ts +0 -6
  163. package/dist/src/canvas/hooks/useShapes.d.ts +0 -44
  164. package/dist/src/canvas/hooks/useZoom.d.ts +0 -22
  165. package/dist/src/canvas/index.d.ts +0 -19
  166. package/dist/src/canvas/utils/canvas.d.ts +0 -15
  167. package/dist/src/canvas/utils/data.d.ts +0 -5
  168. package/dist/src/canvas/utils/file.d.ts +0 -274
  169. package/dist/src/canvas/utils/grid.d.ts +0 -2
  170. package/dist/src/canvas/utils/intersect.d.ts +0 -27
  171. package/dist/src/canvas/utils/selection/groupSelection.d.ts +0 -4
  172. package/dist/src/canvas/utils/selection/lineSelection.d.ts +0 -19
  173. package/dist/src/canvas/utils/selection/rectSelection.d.ts +0 -20
  174. package/dist/src/canvas/utils/selection/selectionFrame.d.ts +0 -3
  175. package/dist/src/canvas/utils/selection.d.ts +0 -12
  176. package/dist/src/canvas/utils/shapes/brush.d.ts +0 -21
  177. package/dist/src/canvas/utils/shapes/circle.d.ts +0 -18
  178. package/dist/src/canvas/utils/shapes/curve.d.ts +0 -17
  179. package/dist/src/canvas/utils/shapes/ellipse.d.ts +0 -18
  180. package/dist/src/canvas/utils/shapes/group.d.ts +0 -33
  181. package/dist/src/canvas/utils/shapes/index.d.ts +0 -35
  182. package/dist/src/canvas/utils/shapes/line.d.ts +0 -19
  183. package/dist/src/canvas/utils/shapes/path.d.ts +0 -29
  184. package/dist/src/canvas/utils/shapes/picture.d.ts +0 -13
  185. package/dist/src/canvas/utils/shapes/polygon.d.ts +0 -18
  186. package/dist/src/canvas/utils/shapes/rectangle.d.ts +0 -22
  187. package/dist/src/canvas/utils/shapes/text.d.ts +0 -27
  188. package/dist/src/canvas/utils/shapes/triangle.d.ts +0 -4
  189. package/dist/src/canvas/utils/transform.d.ts +0 -15
  190. package/dist/src/canvas/utils/trigo.d.ts +0 -19
  191. package/dist/src/canvas/utils/zoom.d.ts +0 -58
  192. package/dist/src/common/hooks/useDebounce.d.ts +0 -2
  193. package/dist/src/common/types/Canvas.d.ts +0 -8
  194. package/dist/src/common/types/Settings.d.ts +0 -24
  195. package/dist/src/common/types/Shapes.d.ts +0 -153
  196. package/dist/src/common/types/react.d.ts +0 -6
  197. package/dist/src/common/types/tools.d.ts +0 -164
  198. package/dist/src/common/types/utils.d.ts +0 -4
  199. package/dist/src/common/utils/array.d.ts +0 -2
  200. package/dist/src/common/utils/dom.d.ts +0 -1
  201. package/dist/src/common/utils/object.d.ts +0 -4
  202. package/dist/src/common/utils/util.d.ts +0 -3
  203. package/dist/src/editor/components/common/Button.d.ts +0 -16
  204. package/dist/src/editor/components/common/FileInput.d.ts +0 -13
  205. package/dist/src/editor/components/common/Loading.d.ts +0 -6
  206. package/dist/src/editor/components/common/Menu.d.ts +0 -10
  207. package/dist/src/editor/components/common/Modal.d.ts +0 -11
  208. package/dist/src/editor/components/common/Panel.d.ts +0 -15
  209. package/dist/src/editor/components/common/Snackbar.d.ts +0 -7
  210. package/dist/src/editor/components/settings/ClosedPointsField.d.ts +0 -10
  211. package/dist/src/editor/components/settings/ColorField.d.ts +0 -15
  212. package/dist/src/editor/components/settings/DeleteButton.d.ts +0 -8
  213. package/dist/src/editor/components/settings/FontFamilyField.d.ts +0 -12
  214. package/dist/src/editor/components/settings/LayoutButton.d.ts +0 -7
  215. package/dist/src/editor/components/settings/Layouts.d.ts +0 -19
  216. package/dist/src/editor/components/settings/LineArrowField.d.ts +0 -12
  217. package/dist/src/editor/components/settings/LineTypeField.d.ts +0 -16
  218. package/dist/src/editor/components/settings/RangeField.d.ts +0 -19
  219. package/dist/src/editor/components/settings/SelectField.d.ts +0 -18
  220. package/dist/src/editor/components/settings/SettingsBar.d.ts +0 -26
  221. package/dist/src/editor/components/settings/ToggleField.d.ts +0 -13
  222. package/dist/src/editor/components/settings/ZoomButton.d.ts +0 -8
  223. package/dist/src/editor/components/toolbox/ContextMenu.d.ts +0 -25
  224. package/dist/src/editor/components/toolbox/ExportModal.d.ts +0 -7
  225. package/dist/src/editor/components/toolbox/LoadFileTool.d.ts +0 -11
  226. package/dist/src/editor/components/toolbox/MenuGroup.d.ts +0 -27
  227. package/dist/src/editor/components/toolbox/PictureUrlInput.d.ts +0 -7
  228. package/dist/src/editor/components/toolbox/Tool.d.ts +0 -11
  229. package/dist/src/editor/components/toolbox/Toolbar.d.ts +0 -26
  230. package/dist/src/editor/components/toolbox/ToolbarGroup.d.ts +0 -14
  231. package/dist/src/editor/constants/grid.d.ts +0 -7
  232. package/dist/src/editor/constants/icons.d.ts +0 -43
  233. package/dist/src/editor/constants/loading.d.ts +0 -1
  234. package/dist/src/editor/constants/options.d.ts +0 -13
  235. package/dist/src/editor/constants/snackbar.d.ts +0 -7
  236. package/dist/src/editor/constants/style.d.ts +0 -14
  237. package/dist/src/editor/constants/tools.d.ts +0 -32
  238. package/dist/src/editor/hooks/useDrag.d.ts +0 -15
  239. package/dist/src/editor/hooks/useMenu.d.ts +0 -11
  240. package/dist/src/editor/hooks/useSnackbar.d.ts +0 -10
  241. package/dist/src/editor/index.d.ts +0 -25
  242. package/dist/src/editor/types/snackbar.d.ts +0 -7
  243. package/dist/src/editor/utils/style.d.ts +0 -2
  244. package/dist/src/editor/utils/toolbar.d.ts +0 -11
  245. package/dist/src/index.d.ts +0 -5
  246. /package/dist/{common → canvas}/types/utils.d.ts +0 -0
  247. /package/dist/{canvas → editor}/hooks/useDebounce.d.ts +0 -0
package/README.md CHANGED
@@ -3,13 +3,20 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@yorab/react-paint)](https://www.npmjs.com/package/@yorab/react-paint)
4
4
  [![License: MIT](https://img.shields.io/npm/l/@yorab/react-paint)](https://github.com/YoRab/react-paint-editor/blob/main/LICENSE.txt)
5
5
  [![Build and Test](https://github.com/YoRab/react-paint-editor/actions/workflows/build-and-test.yaml/badge.svg)](https://github.com/YoRab/react-paint-editor/actions/workflows/build-and-test.yaml)
6
+ [![Coverage](https://img.shields.io/endpoint?url=https%3A%2F%2Fyorab.github.io%2Freact-paint-editor%2Fcoverage%2Fbadge.json)](https://yorab.github.io/react-paint-editor/coverage/)
7
+ [![Socket Badge](https://socket.dev/api/badge/npm/package/@yorab/react-paint)](https://socket.dev/npm/package/@yorab/react-paint)
8
+ [![Checked with Biome](https://img.shields.io/badge/Checked_with-Biome-60a5fa?style=flat&logo=biome)](https://biomejs.dev)
6
9
 
7
10
  An open-source canvas-based library for React used for image annotation or as a digital whiteboard.
8
11
 
12
+ ![The React Paint editor: a drawing of a tree on a hill, with the trunk selected](https://raw.githubusercontent.com/YoRab/react-paint-editor/main/docs/screenshot.png)
13
+
14
+ **[Live demo and documentation](https://yorab.github.io/react-paint-editor/)**
15
+
9
16
  ## Features
10
17
 
11
18
  - **Rich Drawing Tools** - Brush, shapes (rectangles, circles, ellipses, polygons, curves), lines, text, and picture support
12
- - **Interactive Canvas** - Full support for zoom, pan, pinch gestures, and infinite or fixed-size canvases
19
+ - **Interactive Canvas** - Full support for zoom, pan, pinch gestures, and infinite or fixed-size canvases, scaled to their container or filling it
13
20
  - **Shape Manipulation** - Select, move, resize, rotate, and transform shapes with intuitive controls
14
21
  - **Customizable Styling** - Control colors, line widths, opacity, line styles, arrows, and fonts
15
22
  - **Layer Management** - Built-in layer manipulation panel for organizing your drawings
@@ -26,16 +33,38 @@ An open-source canvas-based library for React used for image annotation or as a
26
33
  npm install @yorab/react-paint
27
34
  ```
28
35
 
29
- **Note:** React Paint requires `react` and `react-dom` (version 18.x) as peer dependencies.
36
+ **Note:** React Paint requires `react` and `react-dom` (version 18 or 19) as peer dependencies.
30
37
 
31
38
  ### Import Styles
32
39
 
33
- Don't forget to import the CSS file in your application:
40
+ When you use the `Editor`, don't forget to import the CSS file in your application:
34
41
 
35
42
  ```tsx
36
43
  import '@yorab/react-paint/react-paint.css'
37
44
  ```
38
45
 
46
+ The `Canvas` needs no stylesheet: its styles are inline, see [Customizing the canvas styles](#customizing-the-canvas-styles).
47
+
48
+ ## Modules
49
+
50
+ The library is made of two independent modules:
51
+
52
+ - **canvas**: the `Canvas` component and the `useReactPaint` hook, which holds the whole state and logic.
53
+ - **editor**: the `Editor` component (toolbar, settings bar, layers panel, context menu).
54
+
55
+ They only communicate through the handle returned by `useReactPaint`, given to `Canvas` and `Editor`, or to your own editor.
56
+
57
+ When you don't use `Editor`, it is removed from your bundle, even when importing from the package root. You can also import each module separately, only the editor has a stylesheet:
58
+
59
+ ```tsx
60
+ import { Canvas, useReactPaint } from '@yorab/react-paint/canvas'
61
+
62
+ import { Editor } from '@yorab/react-paint/editor'
63
+ import '@yorab/react-paint/editor/editor.css'
64
+ ```
65
+
66
+ Tree-shaking applies to the ES modules build. The CommonJS (UMD) build, used with `require`, contains both modules.
67
+
39
68
  ## Usage
40
69
 
41
70
  ### Basic Usage
@@ -45,21 +74,23 @@ import { Canvas, Editor, useReactPaint } from '@yorab/react-paint'
45
74
  import '@yorab/react-paint/react-paint.css'
46
75
 
47
76
  function MyPaintApp() {
48
- const { editorProps, canvasProps } = useReactPaint()
77
+ const paint = useReactPaint()
49
78
 
50
79
  return (
51
- <Editor editorProps={editorProps}>
52
- <Canvas canvasProps={canvasProps} />
80
+ <Editor paint={paint}>
81
+ <Canvas paint={paint} />
53
82
  </Editor>
54
83
  )
55
84
  }
56
85
  ```
57
86
 
87
+ `paint` keeps the same identity for the whole life of the component, and the component calling `useReactPaint` does not render again when the drawing changes.
88
+
58
89
  ### Advanced Usage
59
90
 
60
91
  ```tsx
61
92
  import { useEffect, useState } from 'react'
62
- import { Canvas, Editor, useReactPaint, type DrawableShape, type StateData } from '@yorab/react-paint'
93
+ import { Canvas, Editor, useReactPaint, type DrawableShape } from '@yorab/react-paint'
63
94
  import '@yorab/react-paint/react-paint.css'
64
95
 
65
96
  const SHAPES_INIT: DrawableShape[] = [
@@ -86,13 +117,12 @@ const SHAPES_INIT: DrawableShape[] = [
86
117
  function AdvancedPaintApp() {
87
118
  const [shapes, setShapes] = useState<DrawableShape[] | undefined>(SHAPES_INIT)
88
119
 
89
- const { editorProps, canvasProps, registerEvent, unregisterEvent } = useReactPaint({
120
+ const paint = useReactPaint({
90
121
  width: 1920,
91
122
  height: 1080,
92
123
  shapes,
93
124
  options: {
94
- canGrow: true,
95
- canShrink: true,
125
+ resize: 'scale',
96
126
  brushAlgo: 'quadratic',
97
127
  clearCallback: 'defaultShapes'
98
128
  }
@@ -107,25 +137,216 @@ function AdvancedPaintApp() {
107
137
  canvasBackgroundColor: '#ffffff'
108
138
  }
109
139
 
110
- useEffect(() => {
111
- const onDataChanged = (data: StateData, source: 'user' | 'remote') => {
112
- setShapes(data.shapes)
113
- }
114
- registerEvent('dataChanged', onDataChanged)
115
-
116
- return () => {
117
- unregisterEvent('dataChanged', onDataChanged)
118
- }
119
- }, [registerEvent, unregisterEvent])
140
+ // registerEvent returns the function that unregisters the listener
141
+ useEffect(() => paint.registerEvent('dataChanged', data => setShapes(data.shapes)), [paint])
120
142
 
121
143
  return (
122
- <Editor editorProps={editorProps} options={editorOptions}>
123
- <Canvas canvasProps={canvasProps} options={canvasOptions} />
144
+ <Editor paint={paint} options={editorOptions}>
145
+ <Canvas paint={paint} options={canvasOptions} />
146
+ </Editor>
147
+ )
148
+ }
149
+ ```
150
+
151
+ Options are compared by value: they can be written inline.
152
+
153
+ ### Size of the canvas
154
+
155
+ The `resize` option sets how the canvas follows its container. The first four values keep its logical size, `width`×`height`, and show the whole drawing, its width following the container within their bounds:
156
+
157
+ | `resize` | Width of the canvas on the page |
158
+ | --- | --- |
159
+ | `'scaleDown'` (default) | the width of the container, at most `width` pixels |
160
+ | `'scale'` | the width of the container |
161
+ | `'scaleUp'` | the width of the container, at least `width` pixels |
162
+ | `'none'` | always `width` pixels |
163
+
164
+ With `resize: 'extend'`, the canvas fills its container at the scale 1 and its visible area follows the container, redrawn in the same frame: a whiteboard needs no ResizeObserver. The container must have a height. `width` and `height` stay the size of the drawing:
165
+
166
+ - with `size: 'infinite'`, they are the area exported by the `defaultView` of `exportPicture` and the size given in the exported data. The top left corner of the view stays still when the container is resized.
167
+ - with `size: 'fixed'`, they are the page: it is centered with a mask around it when the container is larger, and the view is kept inside it otherwise.
168
+
169
+ ```tsx
170
+ const paint = useReactPaint({ options: { size: 'infinite', resize: 'extend', canZoom: 'always' } })
171
+
172
+ return (
173
+ <div style={{ height: '100vh' }}>
174
+ <Editor paint={paint}>
175
+ <Canvas paint={paint} />
124
176
  </Editor>
177
+ </div>
178
+ )
179
+ ```
180
+
181
+ `--react-paint-app-canvaswidth` and `--react-paint-app-row-aspectratio` have no effect with `extend`. A page larger than its container is scrolled with the wheel when `canZoom` is `'always'`, and in edit mode by dragging with the space key or the wheel button pressed.
182
+
183
+ ### Reading the state
184
+
185
+ `usePaintState(paint, selector?, isEqual?)` reads the state during a render, and renders the component again only when the selected value changes. Without selector, it returns the whole `EditorState`. In an event handler, read it with `paint.getState()`.
186
+
187
+ ```tsx
188
+ import { shallowEqual, usePaintState } from '@yorab/react-paint'
189
+
190
+ const canUndo = usePaintState(paint, state => state.history.canUndo)
191
+ const zoom = usePaintState(paint, state => state.view.zoom)
192
+ // a selector building an object or an array needs an equality function
193
+ const { shapes, selectedShapes } = usePaintState(paint, state => ({ shapes: state.shapes, selectedShapes: state.selectedShapes }), shallowEqual)
194
+ ```
195
+
196
+ The handle also gives `actions` (the commands), `registerEvent` / `unregisterEvent`, `batch`, `getShape`, `getBounds`, `getShapesAt`, `toCanvasPosition` / `toClientPosition`, `resetCanvas`, `getCurrentImage` and `getCurrentData`. See [Driving the canvas from the code](#driving-the-canvas-from-the-code).
197
+
198
+ Besides the drawing, the state gives the options in effect (`state.options`), whether dropped images are added (`state.withDropPicture`) and the part of the drawing displayed (`state.view.visibleArea`). The zoom stays between the first and the last of the `ZOOM_LEVELS`, the levels of `actions.setZoom`.
199
+
200
+ ### Custom Editor
201
+
202
+ The handle is a public contract: `usePaintState` gives everything an editor needs to render (shapes, selection, active tool, history, zoom, hovered shape, context menu...) and `actions` holds the commands to act on the canvas. Set `paint.scopeRef` on the root element of your editor: it defines the area where keyboard shortcuts are active, which is only the canvas otherwise. The pointer always works over the canvas.
203
+
204
+ Under a `PaintProvider`, any component can get the handle with `usePaint`: each part of the editor reads only what it shows.
205
+
206
+ ```tsx
207
+ import { Canvas, PaintProvider, type ReactPaint, shallowEqual, usePaint, usePaintState, useReactPaint } from '@yorab/react-paint/canvas'
208
+
209
+ const MyToolbar = () => {
210
+ const paint = usePaint()
211
+ const { activeTool, tools, canUndo } = usePaintState(
212
+ paint,
213
+ state => ({ activeTool: state.activeTool, tools: state.tools, canUndo: state.history.canUndo }),
214
+ shallowEqual
215
+ )
216
+ const selectedIds = usePaintState(paint, state => state.selectedShapeIds)
217
+
218
+ return (
219
+ <div>
220
+ {tools.map(tool => (
221
+ <button key={tool.id} aria-pressed={activeTool.id === tool.id} onClick={() => paint.actions.selectTool(tool.id)}>
222
+ {tool.label ?? tool.type}
223
+ </button>
224
+ ))}
225
+ <button disabled={!canUndo} onClick={paint.actions.undo}>Undo</button>
226
+ <button disabled={!selectedIds.length} onClick={() => paint.actions.updateShapesStyle(selectedIds, 'strokeColor', 'red')}>Red</button>
227
+ </div>
228
+ )
229
+ }
230
+
231
+ const MyEditor = ({ paint, children }: { paint: ReactPaint; children: React.ReactNode }) => (
232
+ <PaintProvider paint={paint}>
233
+ <div ref={paint.scopeRef}>
234
+ <MyToolbar />
235
+ {children}
236
+ </div>
237
+ </PaintProvider>
238
+ )
239
+
240
+ function MyPaintApp() {
241
+ const paint = useReactPaint()
242
+
243
+ return (
244
+ <MyEditor paint={paint}>
245
+ <Canvas paint={paint} />
246
+ </MyEditor>
125
247
  )
126
248
  }
127
249
  ```
128
250
 
251
+ The canvas can be used alone, with `<Canvas paint={paint} />`: the pointer works as soon as it is over it, and keyboard shortcuts while the focus is inside it. The `Editor` component provides the handle to its children too, and a `PaintProvider` lets you place parts of your interface, like a toolbar, outside of the editor.
252
+
253
+ ### Driving the canvas from the code
254
+
255
+ Everything the editor and the canvas gestures do can be done from the code, without the `Editor` and even without a mounted `Canvas`. Shapes are designated by their ids, which are kept in the exported data and when the data are loaded again.
256
+
257
+ ```tsx
258
+ const paint = useReactPaint()
259
+
260
+ // a single step of the history, and a single dataChanged event, for the whole batch
261
+ const [rectId, textId] = paint.batch(() =>
262
+ paint.actions.addShapes([
263
+ { type: 'rect', x: 60, y: 60, width: 160, height: 100, style: { fillColor: 'yellow' }, data: { comment: 'to check' } },
264
+ // the size of a text is computed from its font size
265
+ { type: 'text', x: 280, y: 80, value: ['Created', 'from code'], fontSize: 28 }
266
+ ])
267
+ )
268
+
269
+ paint.actions.updateShapes([{ id: rectId, x: 20, width: 200, style: { strokeColor: 'red' } }])
270
+ paint.actions.rotateShapes([rectId, textId], Math.PI / 12)
271
+ paint.actions.resizeShapes([textId], { x: 300, y: 300, width: 200, height: 80 })
272
+
273
+ paint.registerEvent('shapeClicked', (shape, { canvasPosition }) => console.log(shape.id, shape.data, canvasPosition))
274
+
275
+ // a popover placed at the top right corner of a shape, with position: fixed
276
+ const box = paint.getBounds([rectId])
277
+ const popoverPosition = box && paint.toClientPosition([box.x + box.width, box.y])
278
+ ```
279
+
280
+ | Operation | API |
281
+ | --- | --- |
282
+ | Create, update, remove shapes | `actions.addShapes(shapes, { select, index })`, `actions.updateShapes(patches)`, `actions.removeShapes(ids)` |
283
+ | Move, turn, resize, flip | `actions.translateShapes(ids, [dx, dy])`, `actions.rotateShapes(ids, angle, { center })`, `actions.resizeShapes(ids, box)`, `actions.transformShapes(ids, action)` |
284
+ | Style of shapes, default style of tools | `actions.setShapesStyle(ids, style, { updateTool })`, `actions.setToolStyle(toolId, style)` |
285
+ | Order | `actions.moveShapes(ids, 'first' \| 'last' \| 'forward' \| 'backward')`, `actions.setShapeIndex(id, index)`, `actions.reorderShapes(ids)` |
286
+ | Points of lines, polygons and curves | `actions.insertShapePoint`, `actions.moveShapePoint`, `actions.removeShapePoint` |
287
+ | Visibility, lock | `actions.setShapesVisibility(ids, visible)`, `actions.setShapesLocked(ids, locked)` |
288
+ | Selection | `actions.selectShapes(ids, { mode: 'replace' \| 'add' \| 'remove' })`, `actions.selectAllShapes()`, `actions.clearSelection()` |
289
+ | Text edition, context menu | `actions.editText(id)`, `actions.openContextMenu({ clientPosition, ids })`, `actions.closeContextMenu()` |
290
+ | Duplicate, copy, cut, paste | `actions.duplicateShapes`, `actions.copy`, `actions.cut`, `actions.paste` |
291
+ | History | `actions.undo()`, `actions.redo()`, `paint.batch(fn)` |
292
+ | View and grid | `actions.zoomTo(level, { center })`, `actions.zoomToShapes(ids, { padding })`, `actions.setZoom`, `actions.resetZoom`, `actions.panBy([dx, dy])`, `actions.setOffset`, `actions.setGridGap(GRID_PRESETS.medium)` |
293
+ | Options and settings | `actions.setOptions(options)`, `actions.resetOptions()`, `actions.resetSettings()` |
294
+ | Data and images | `actions.loadData(data)`, `actions.loadFile(file)`, `paint.getCurrentData()`, `paint.getCurrentImage({ view, format, quality, backgroundColor, backgroundImage })`, `actions.exportPicture`, `actions.exportData` |
295
+ | Read the shapes | `paint.getShape(id)`, `paint.getBounds(ids)`, `paint.getShapesAt(point)`, `usePaintState(paint, state => state.shapes)` |
296
+ | Positions on the screen | `paint.toClientPosition(point)`, `paint.toCanvasPosition({ x, y })`, `state.view.visibleArea` |
297
+
298
+ - The actions changing shapes add a step to the history. Give `{ save: false }` for continuous changes, then call `actions.saveShapes()`, and `{ source: 'remote' }` to apply changes received from elsewhere: `dataChanged` gives this source.
299
+ - `addShapes` and `updateShapes` check their input and throw an error, without changing anything, when a shape is invalid. The style of a new shape is completed with the default style of its tool.
300
+ - The `data` field of a shape holds data of your application. It is not drawn, and is kept in the history, the copies and the exported data.
301
+ - The exported data (`getCurrentData`, `dataChanged`, `exportData`) round the positions and the sizes to 3 decimals, and the angles and the factors (`rotation`, `scaleX`, `scaleY`, `ratio`) to 6, which changes nothing visible. The shapes keep their full precision while drawing, and `data` and `style` are exported as they are.
302
+ - Coordinates are in the units of the drawing, angles are in radians, clockwise. The actions work in every mode: `viewer` and `disabled` only restrict the user.
303
+ - `shapeClicked` and `shapeHovered` are also emitted in viewer mode, to make the shapes clickable. `canvasClicked` and `canvasDoubleClicked` are emitted for any click on the drawing, with the shape under the pointer or `undefined`, for instance to add a pin to an empty area.
304
+ - `addShapes` returns before the pictures are loaded: `pictureLoaded` tells when one is drawn. `pictureLoadFailed` tells that the image of a picture cannot be loaded: a picture of loaded data is then left out.
305
+ - `dataChanged(data, source, changes)` gives the shapes added, updated (with their previous version) and removed since the previous state, to send only what changed: `changes.added`, `changes.updated`, `changes.removed`, and `changes.reordered`. An undo gives the reverse changes.
306
+ - `setOptions` overrides options of `useReactPaint` until `resetOptions`. `resetSettings` restores what was changed while editing: the default styles of the tools, the grid, the zoom and the active tool.
307
+
308
+ ### Picture annotation
309
+
310
+ To lay a picture under the canvas, give it the style returned by `useAnnotationsStyle(paint)`: it follows the zoom and the moves of the view. With `resize: 'extend'`, give the picture the size of the drawing in pixels, `width`×`height`, instead of the size of the canvas. To export the picture with the shapes, give the image element as `backgroundImage` to `paint.getCurrentImage` or `paint.actions.exportPicture`.
311
+
312
+ ### Customizing the canvas styles
313
+
314
+ The styles of the canvas are inline. Some of its properties read a CSS variable: set it on any parent element (your wrapper, the editor, or the `style` prop of `Canvas`) to override the value computed by the canvas, without `!important`.
315
+
316
+ ```css
317
+ .my-wrapper {
318
+ --react-paint-app-canvas-bg: #fafafa;
319
+ }
320
+ ```
321
+
322
+ | Variable | Element | Property |
323
+ | --- | --- | --- |
324
+ | `--react-paint-app-canvas-bg` | drawing area | `background-color`, instead of the `canvasBackgroundColor` option |
325
+ | `--react-paint-canvas-cursor` | drawing area | `cursor`: replaces every cursor of the gestures |
326
+ | `--react-paint-app-canvaswidth` | root | `max-width` with `resize` `'scaleDown'` and `'none'`, `min-width` with `'scaleUp'` and `'none'`, as a unitless number of pixels. Set on the editor or one of its parents, it bounds the editor too |
327
+ | `--react-paint-app-row-aspectratio` | root | `aspect-ratio`. Not with `resize: 'extend'` |
328
+ | `--react-paint-canvas-edittextbox-color`, `-opacity`, `-fontfamily` | text edition box | `color`, `opacity`, `font-family` |
329
+ | `--react-paint-canvas-edittextbox-transform`, `-fontsize`, `-padding` | text edition box | `transform`, `font-size`, `padding`: they follow the edited text, overriding them moves the box away from it |
330
+
331
+ The other properties of the canvas elements are not set inline, or keep the canvas working (stacked canvases, touch): style the root with the `className` and `style` props of `Canvas`.
332
+
333
+ ### Performance
334
+
335
+ - Select precise slices of the state: a component reading `state.history.canUndo` does not render while a shape is dragged, one reading `state.shapes` does.
336
+ - When a selector builds an object or an array, give `shallowEqual` (or your own equality function) as third argument, otherwise the component renders on each change of the state.
337
+ - Read `state.interaction` (gesture running, hovered shape and part of the selection, selection frame, modifier keys) only when you need it: it changes on each move of the pointer.
338
+ - In event handlers, read the state with `paint.getState()` instead of subscribing to it.
339
+
340
+ ### Upgrading from 0.x
341
+
342
+ Version 1 replaces `editorProps` and `canvasProps` with a single handle and moves some options. See the [migration guide](https://github.com/YoRab/react-paint-editor/blob/main/MIGRATION.md).
343
+
344
+ ## Compatibility
345
+
346
+ - **React**: 18 or 19, the only (peer) dependencies.
347
+ - **Browsers**: Chrome and Edge 98+, Firefox 94+, Safari 15.4+, with a mouse, a pen or touch (pinch to zoom). The automated tests run in Chromium.
348
+ - **Server-side rendering**: the components can be rendered on the server, the canvas draws once hydrated. The package has no `'use client'` directive: with the Next.js App Router, import it from a `'use client'` file.
349
+
129
350
  ## Documentation
130
351
 
131
352
  For complete documentation, API reference, examples, and interactive demos, visit our **Storybook**:
@@ -139,10 +360,33 @@ The Storybook includes:
139
360
  - Usage examples
140
361
  - Configuration options
141
362
 
363
+ Looking for the documentation of version 0.x? It lives on the [`v0-stable` branch](https://github.com/YoRab/react-paint-editor/tree/v0-stable). To move to version 1, see the [migration guide](https://github.com/YoRab/react-paint-editor/blob/main/MIGRATION.md).
364
+
365
+ ## Security
366
+
367
+ A drawing loaded from a source you do not control is untrusted, filter its pictures with the `resolvePictureSrc` option. The `icon` of a tool is rendered as HTML and must be trusted. See the [security model](https://github.com/YoRab/react-paint-editor/blob/main/SECURITY.md#security-model), and the [security policy](https://github.com/YoRab/react-paint-editor/blob/main/SECURITY.md#reporting-a-vulnerability) to report a vulnerability.
368
+
142
369
  ## Resources
143
370
 
144
371
  - [Changelog](https://github.com/YoRab/react-paint-editor/blob/main/Changelog.md)
145
372
  - [License](https://github.com/YoRab/react-paint-editor/blob/main/LICENSE.txt) (MIT)
146
373
  - [GitHub Repository](https://github.com/YoRab/react-paint-editor)
374
+ - Questions and ideas: [GitHub Discussions](https://github.com/YoRab/react-paint-editor/discussions)
375
+ - Bug reports: [Issues](https://github.com/YoRab/react-paint-editor/issues)
147
376
 
148
377
  ## Contributing
378
+
379
+ Issues and pull requests are welcome. For a large change, open a discussion or an issue first.
380
+
381
+ The project uses Node 24 and pnpm (the version is set by the `packageManager` field, enable it with `corepack enable`). Pull requests target the `main` branch.
382
+
383
+ ```bash
384
+ pnpm install
385
+ pnpm start # Storybook on http://localhost:6006
386
+ pnpm test # unit tests
387
+ npx playwright install chromium # once, before the UI tests
388
+ pnpm test-storybook # interaction and accessibility tests, in Chromium
389
+ pnpm lint # Biome, fixes what it can
390
+ ```
391
+
392
+ Every pull request runs the [Build and Test workflow](https://github.com/YoRab/react-paint-editor/blob/main/.github/workflows/build-and-test.yaml).