@fieldnotes/react 0.1.0 → 0.1.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.
- package/README.md +171 -0
- package/dist/index.cjs +135 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +44 -2
- package/dist/index.d.ts +44 -2
- package/dist/index.js +134 -3
- package/dist/index.js.map +1 -1
- package/package.json +43 -42
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# @fieldnotes/react
|
|
2
|
+
|
|
3
|
+
React bindings for the [Field Notes](https://github.com/IrakliDevelop/fieldnotes) infinite canvas SDK. Embed React components directly onto an infinite, pannable, zoomable canvas.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @fieldnotes/core @fieldnotes/react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires React 18+.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { FieldNotesCanvas } from '@fieldnotes/react';
|
|
17
|
+
import { HandTool, SelectTool, PencilTool } from '@fieldnotes/core';
|
|
18
|
+
|
|
19
|
+
function App() {
|
|
20
|
+
return (
|
|
21
|
+
<FieldNotesCanvas
|
|
22
|
+
tools={[new HandTool(), new SelectTool(), new PencilTool()]}
|
|
23
|
+
defaultTool="select"
|
|
24
|
+
style={{ width: '100vw', height: '100vh' }}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Your container needs a defined size — the canvas fills it.
|
|
31
|
+
|
|
32
|
+
## Embedding React Components
|
|
33
|
+
|
|
34
|
+
The main feature — render any React component as a canvas element that pans, zooms, and resizes with the canvas:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { FieldNotesCanvas, CanvasElement } from '@fieldnotes/react';
|
|
38
|
+
import { SelectTool } from '@fieldnotes/core';
|
|
39
|
+
|
|
40
|
+
function App() {
|
|
41
|
+
return (
|
|
42
|
+
<FieldNotesCanvas
|
|
43
|
+
tools={[new SelectTool()]}
|
|
44
|
+
defaultTool="select"
|
|
45
|
+
style={{ width: '100vw', height: '100vh' }}
|
|
46
|
+
>
|
|
47
|
+
<CanvasElement position={{ x: 100, y: 200 }} size={{ w: 300, h: 200 }}>
|
|
48
|
+
<MyCard />
|
|
49
|
+
</CanvasElement>
|
|
50
|
+
|
|
51
|
+
<CanvasElement position={{ x: 500, y: 100 }}>
|
|
52
|
+
<button onClick={() => console.log('clicked!')}>Interactive button on the canvas</button>
|
|
53
|
+
</CanvasElement>
|
|
54
|
+
</FieldNotesCanvas>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Embedded React components are fully interactive — clicks, inputs, forms all work normally.
|
|
60
|
+
|
|
61
|
+
## Hooks
|
|
62
|
+
|
|
63
|
+
### `useViewport()`
|
|
64
|
+
|
|
65
|
+
Access the core `Viewport` instance for imperative operations:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { useViewport } from '@fieldnotes/react';
|
|
69
|
+
|
|
70
|
+
function Toolbar() {
|
|
71
|
+
const viewport = useViewport();
|
|
72
|
+
|
|
73
|
+
return <button onClick={() => viewport.undo()}>Undo</button>;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Must be used inside `<FieldNotesCanvas>`.
|
|
78
|
+
|
|
79
|
+
### `useActiveTool()`
|
|
80
|
+
|
|
81
|
+
Reactive current tool name — re-renders when the tool changes:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useActiveTool, useViewport } from '@fieldnotes/react';
|
|
85
|
+
|
|
86
|
+
function ToolIndicator() {
|
|
87
|
+
const tool = useActiveTool();
|
|
88
|
+
const viewport = useViewport();
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div>
|
|
92
|
+
<span>Current: {tool}</span>
|
|
93
|
+
<button onClick={() => viewport.toolManager.setTool('pencil', viewport.toolContext)}>
|
|
94
|
+
Pencil
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `useCamera()`
|
|
102
|
+
|
|
103
|
+
Reactive camera state (position + zoom) — re-renders on pan/zoom:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
import { useCamera } from '@fieldnotes/react';
|
|
107
|
+
|
|
108
|
+
function CameraInfo() {
|
|
109
|
+
const { x, y, zoom } = useCamera();
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<span>
|
|
113
|
+
{zoom.toFixed(2)}x at ({x.toFixed(0)}, {y.toFixed(0)})
|
|
114
|
+
</span>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Component API
|
|
120
|
+
|
|
121
|
+
### `<FieldNotesCanvas>`
|
|
122
|
+
|
|
123
|
+
| Prop | Type | Description |
|
|
124
|
+
| ------------- | ------------------------------ | --------------------------------------- |
|
|
125
|
+
| `options` | `ViewportOptions` | Camera and background config |
|
|
126
|
+
| `tools` | `Tool[]` | Tools to register on mount |
|
|
127
|
+
| `defaultTool` | `string` | Tool to activate on mount |
|
|
128
|
+
| `className` | `string` | CSS class for the container div |
|
|
129
|
+
| `style` | `CSSProperties` | Inline styles for the container div |
|
|
130
|
+
| `onReady` | `(viewport: Viewport) => void` | Called after Viewport is created |
|
|
131
|
+
| `children` | `ReactNode` | Child components (have access to hooks) |
|
|
132
|
+
| `ref` | `Ref<FieldNotesCanvasRef>` | Exposes `{ viewport }` |
|
|
133
|
+
|
|
134
|
+
### `<CanvasElement>`
|
|
135
|
+
|
|
136
|
+
| Prop | Type | Default | Description |
|
|
137
|
+
| ---------- | -------------------------- | -------------------- | ---------------------------------- |
|
|
138
|
+
| `position` | `{ x: number; y: number }` | required | World-space position |
|
|
139
|
+
| `size` | `{ w: number; h: number }` | `{ w: 200, h: 150 }` | Element size in world-space pixels |
|
|
140
|
+
| `children` | `ReactNode` | required | React content to render on canvas |
|
|
141
|
+
|
|
142
|
+
Position and size updates are reactive — change the props and the element moves/resizes on the canvas.
|
|
143
|
+
|
|
144
|
+
## Accessing the Viewport Directly
|
|
145
|
+
|
|
146
|
+
For advanced use cases, use a ref:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { useRef } from 'react';
|
|
150
|
+
import { FieldNotesCanvas, type FieldNotesCanvasRef } from '@fieldnotes/react';
|
|
151
|
+
|
|
152
|
+
function App() {
|
|
153
|
+
const canvasRef = useRef<FieldNotesCanvasRef>(null);
|
|
154
|
+
|
|
155
|
+
const exportState = () => {
|
|
156
|
+
const json = canvasRef.current?.viewport?.exportJSON();
|
|
157
|
+
console.log(json);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
<FieldNotesCanvas ref={canvasRef} style={{ width: '100vw', height: '100vh' }} />
|
|
163
|
+
<button onClick={exportState}>Export</button>
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -20,12 +20,144 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
23
|
+
CanvasElement: () => CanvasElement,
|
|
24
|
+
FieldNotesCanvas: () => FieldNotesCanvas,
|
|
25
|
+
ViewportContext: () => ViewportContext,
|
|
26
|
+
useActiveTool: () => useActiveTool,
|
|
27
|
+
useCamera: () => useCamera,
|
|
28
|
+
useViewport: () => useViewport
|
|
24
29
|
});
|
|
25
30
|
module.exports = __toCommonJS(index_exports);
|
|
26
|
-
|
|
31
|
+
|
|
32
|
+
// src/field-notes-canvas.tsx
|
|
33
|
+
var import_react2 = require("react");
|
|
34
|
+
var import_core = require("@fieldnotes/core");
|
|
35
|
+
|
|
36
|
+
// src/context.ts
|
|
37
|
+
var import_react = require("react");
|
|
38
|
+
var ViewportContext = (0, import_react.createContext)(null);
|
|
39
|
+
|
|
40
|
+
// src/field-notes-canvas.tsx
|
|
41
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
42
|
+
var FieldNotesCanvas = (0, import_react2.forwardRef)(
|
|
43
|
+
function FieldNotesCanvas2({ options, tools, defaultTool, className, style, children, onReady }, ref) {
|
|
44
|
+
const containerRef = (0, import_react2.useRef)(null);
|
|
45
|
+
const [viewport, setViewport] = (0, import_react2.useState)(null);
|
|
46
|
+
(0, import_react2.useImperativeHandle)(ref, () => ({ viewport }), [viewport]);
|
|
47
|
+
(0, import_react2.useEffect)(() => {
|
|
48
|
+
const el = containerRef.current;
|
|
49
|
+
if (!el) return;
|
|
50
|
+
const vp = new import_core.Viewport(el, options);
|
|
51
|
+
if (tools) {
|
|
52
|
+
for (const tool of tools) {
|
|
53
|
+
vp.toolManager.register(tool);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (defaultTool) {
|
|
57
|
+
vp.toolManager.setTool(defaultTool, vp.toolContext);
|
|
58
|
+
}
|
|
59
|
+
setViewport(vp);
|
|
60
|
+
onReady?.(vp);
|
|
61
|
+
return () => {
|
|
62
|
+
vp.destroy();
|
|
63
|
+
setViewport(null);
|
|
64
|
+
};
|
|
65
|
+
}, []);
|
|
66
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className, style, children: viewport && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewportContext.Provider, { value: viewport, children }) });
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// src/canvas-element.tsx
|
|
71
|
+
var import_react4 = require("react");
|
|
72
|
+
var import_react_dom = require("react-dom");
|
|
73
|
+
|
|
74
|
+
// src/use-viewport.ts
|
|
75
|
+
var import_react3 = require("react");
|
|
76
|
+
function useViewport() {
|
|
77
|
+
const viewport = (0, import_react3.useContext)(ViewportContext);
|
|
78
|
+
if (!viewport) {
|
|
79
|
+
throw new Error("useViewport must be used inside <FieldNotesCanvas>");
|
|
80
|
+
}
|
|
81
|
+
return viewport;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/canvas-element.tsx
|
|
85
|
+
function CanvasElement({ position, size, children }) {
|
|
86
|
+
const viewport = useViewport();
|
|
87
|
+
const [portalTarget, setPortalTarget] = (0, import_react4.useState)(null);
|
|
88
|
+
const elementIdRef = (0, import_react4.useRef)(null);
|
|
89
|
+
(0, import_react4.useEffect)(() => {
|
|
90
|
+
const container = document.createElement("div");
|
|
91
|
+
Object.assign(container.style, {
|
|
92
|
+
width: "100%",
|
|
93
|
+
height: "100%"
|
|
94
|
+
});
|
|
95
|
+
viewport.domLayer.appendChild(container);
|
|
96
|
+
const id = viewport.addHtmlElement(container, position, size);
|
|
97
|
+
elementIdRef.current = id;
|
|
98
|
+
setPortalTarget(container);
|
|
99
|
+
return () => {
|
|
100
|
+
if (elementIdRef.current) {
|
|
101
|
+
viewport.store.remove(elementIdRef.current);
|
|
102
|
+
viewport.requestRender();
|
|
103
|
+
elementIdRef.current = null;
|
|
104
|
+
}
|
|
105
|
+
setPortalTarget(null);
|
|
106
|
+
};
|
|
107
|
+
}, [viewport]);
|
|
108
|
+
(0, import_react4.useEffect)(() => {
|
|
109
|
+
const id = elementIdRef.current;
|
|
110
|
+
if (!id) return;
|
|
111
|
+
viewport.store.update(id, { position });
|
|
112
|
+
if (size) {
|
|
113
|
+
viewport.store.update(id, { size });
|
|
114
|
+
}
|
|
115
|
+
viewport.requestRender();
|
|
116
|
+
}, [viewport, position.x, position.y, size?.w, size?.h]);
|
|
117
|
+
if (!portalTarget) return null;
|
|
118
|
+
return (0, import_react_dom.createPortal)(children, portalTarget);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/use-active-tool.ts
|
|
122
|
+
var import_react5 = require("react");
|
|
123
|
+
function useActiveTool() {
|
|
124
|
+
const viewport = useViewport();
|
|
125
|
+
const subscribe = (0, import_react5.useCallback)(
|
|
126
|
+
(onStoreChange) => viewport.toolManager.onChange(() => onStoreChange()),
|
|
127
|
+
[viewport]
|
|
128
|
+
);
|
|
129
|
+
const getSnapshot = (0, import_react5.useCallback)(() => viewport.toolManager.activeTool?.name ?? "", [viewport]);
|
|
130
|
+
return (0, import_react5.useSyncExternalStore)(subscribe, getSnapshot);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/use-camera.ts
|
|
134
|
+
var import_react6 = require("react");
|
|
135
|
+
function useCamera() {
|
|
136
|
+
const viewport = useViewport();
|
|
137
|
+
const cachedRef = (0, import_react6.useRef)({ x: 0, y: 0, zoom: 1 });
|
|
138
|
+
const subscribe = (0, import_react6.useCallback)(
|
|
139
|
+
(onStoreChange) => viewport.camera.onChange(onStoreChange),
|
|
140
|
+
[viewport]
|
|
141
|
+
);
|
|
142
|
+
const getSnapshot = (0, import_react6.useCallback)(() => {
|
|
143
|
+
const { position, zoom } = viewport.camera;
|
|
144
|
+
const cached = cachedRef.current;
|
|
145
|
+
if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {
|
|
146
|
+
return cached;
|
|
147
|
+
}
|
|
148
|
+
const next = { x: position.x, y: position.y, zoom };
|
|
149
|
+
cachedRef.current = next;
|
|
150
|
+
return next;
|
|
151
|
+
}, [viewport]);
|
|
152
|
+
return (0, import_react6.useSyncExternalStore)(subscribe, getSnapshot);
|
|
153
|
+
}
|
|
27
154
|
// Annotate the CommonJS export names for ESM import in node:
|
|
28
155
|
0 && (module.exports = {
|
|
29
|
-
|
|
156
|
+
CanvasElement,
|
|
157
|
+
FieldNotesCanvas,
|
|
158
|
+
ViewportContext,
|
|
159
|
+
useActiveTool,
|
|
160
|
+
useCamera,
|
|
161
|
+
useViewport
|
|
30
162
|
});
|
|
31
163
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/field-notes-canvas.tsx","../src/context.ts","../src/canvas-element.tsx","../src/use-viewport.ts","../src/use-active-tool.ts","../src/use-camera.ts"],"sourcesContent":["export { FieldNotesCanvas } from './field-notes-canvas';\r\nexport type { FieldNotesCanvasProps, FieldNotesCanvasRef } from './field-notes-canvas';\r\nexport { CanvasElement } from './canvas-element';\r\nexport type { CanvasElementProps } from './canvas-element';\r\nexport { useViewport } from './use-viewport';\r\nexport { useActiveTool } from './use-active-tool';\r\nexport { useCamera } from './use-camera';\r\nexport type { CameraState } from './use-camera';\r\nexport { ViewportContext } from './context';\r\n","import {\r\n useRef,\r\n useEffect,\r\n useState,\r\n forwardRef,\r\n useImperativeHandle,\r\n type ReactNode,\r\n type CSSProperties,\r\n} from 'react';\r\nimport { Viewport } from '@fieldnotes/core';\r\nimport type { ViewportOptions, Tool } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport interface FieldNotesCanvasProps {\r\n options?: ViewportOptions;\r\n tools?: Tool[];\r\n defaultTool?: string;\r\n className?: string;\r\n style?: CSSProperties;\r\n children?: ReactNode;\r\n onReady?: (viewport: Viewport) => void;\r\n}\r\n\r\nexport interface FieldNotesCanvasRef {\r\n viewport: Viewport | null;\r\n}\r\n\r\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\r\n function FieldNotesCanvas(\r\n { options, tools, defaultTool, className, style, children, onReady },\r\n ref,\r\n ) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [viewport, setViewport] = useState<Viewport | null>(null);\r\n\r\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\r\n\r\n useEffect(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const vp = new Viewport(el, options);\r\n\r\n if (tools) {\r\n for (const tool of tools) {\r\n vp.toolManager.register(tool);\r\n }\r\n }\r\n\r\n if (defaultTool) {\r\n vp.toolManager.setTool(defaultTool, vp.toolContext);\r\n }\r\n\r\n setViewport(vp);\r\n onReady?.(vp);\r\n\r\n return () => {\r\n vp.destroy();\r\n setViewport(null);\r\n };\r\n }, []);\r\n\r\n return (\r\n <div ref={containerRef} className={className} style={style}>\r\n {viewport && (\r\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\r\n )}\r\n </div>\r\n );\r\n },\r\n);\r\n","import { createContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\n\r\nexport const ViewportContext = createContext<Viewport | null>(null);\r\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CanvasElementProps {\r\n position: { x: number; y: number };\r\n size?: { w: number; h: number };\r\n children: ReactNode;\r\n}\r\n\r\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\r\n const viewport = useViewport();\r\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\r\n const elementIdRef = useRef<string | null>(null);\r\n\r\n useEffect(() => {\r\n const container = document.createElement('div');\r\n Object.assign(container.style, {\r\n width: '100%',\r\n height: '100%',\r\n });\r\n\r\n // Append to domLayer immediately so portal children are queryable in the document\r\n // before the viewport render loop fires via requestAnimationFrame.\r\n viewport.domLayer.appendChild(container);\r\n\r\n const id = viewport.addHtmlElement(container, position, size);\r\n elementIdRef.current = id;\r\n setPortalTarget(container);\r\n\r\n return () => {\r\n if (elementIdRef.current) {\r\n viewport.store.remove(elementIdRef.current);\r\n viewport.requestRender();\r\n elementIdRef.current = null;\r\n }\r\n setPortalTarget(null);\r\n };\r\n }, [viewport]);\r\n\r\n useEffect(() => {\r\n const id = elementIdRef.current;\r\n if (!id) return;\r\n viewport.store.update(id, { position });\r\n if (size) {\r\n viewport.store.update(id, { size });\r\n }\r\n viewport.requestRender();\r\n }, [viewport, position.x, position.y, size?.w, size?.h]);\r\n\r\n if (!portalTarget) return null;\r\n return createPortal(children, portalTarget);\r\n}\r\n","import { useContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport function useViewport(): Viewport {\r\n const viewport = useContext(ViewportContext);\r\n if (!viewport) {\r\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\r\n }\r\n return viewport;\r\n}\r\n","import { useCallback, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport function useActiveTool(): string {\r\n const viewport = useViewport();\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.toolManager.onChange(() => onStoreChange()),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? '', [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CameraState {\r\n x: number;\r\n y: number;\r\n zoom: number;\r\n}\r\n\r\nexport function useCamera(): CameraState {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<CameraState>({ x: 0, y: 0, zoom: 1 });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.camera.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): CameraState => {\r\n const { position, zoom } = viewport.camera;\r\n const cached = cachedRef.current;\r\n if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {\r\n return cached;\r\n }\r\n const next = { x: position.x, y: position.y, zoom };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n"],"mappings":"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","names":["import_react","FieldNotesCanvas","import_react","import_react","import_react","import_react"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
import { ViewportOptions, Tool, Viewport } from '@fieldnotes/core';
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
interface FieldNotesCanvasProps {
|
|
6
|
+
options?: ViewportOptions;
|
|
7
|
+
tools?: Tool[];
|
|
8
|
+
defaultTool?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
style?: CSSProperties;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
onReady?: (viewport: Viewport) => void;
|
|
13
|
+
}
|
|
14
|
+
interface FieldNotesCanvasRef {
|
|
15
|
+
viewport: Viewport | null;
|
|
16
|
+
}
|
|
17
|
+
declare const FieldNotesCanvas: react.ForwardRefExoticComponent<FieldNotesCanvasProps & react.RefAttributes<FieldNotesCanvasRef>>;
|
|
18
|
+
|
|
19
|
+
interface CanvasElementProps {
|
|
20
|
+
position: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
size?: {
|
|
25
|
+
w: number;
|
|
26
|
+
h: number;
|
|
27
|
+
};
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
declare function CanvasElement({ position, size, children }: CanvasElementProps): react.ReactPortal | null;
|
|
31
|
+
|
|
32
|
+
declare function useViewport(): Viewport;
|
|
33
|
+
|
|
34
|
+
declare function useActiveTool(): string;
|
|
35
|
+
|
|
36
|
+
interface CameraState {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
zoom: number;
|
|
40
|
+
}
|
|
41
|
+
declare function useCamera(): CameraState;
|
|
42
|
+
|
|
43
|
+
declare const ViewportContext: react.Context<Viewport | null>;
|
|
44
|
+
|
|
45
|
+
export { type CameraState, CanvasElement, type CanvasElementProps, FieldNotesCanvas, type FieldNotesCanvasProps, type FieldNotesCanvasRef, ViewportContext, useActiveTool, useCamera, useViewport };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
-
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
import { ViewportOptions, Tool, Viewport } from '@fieldnotes/core';
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
interface FieldNotesCanvasProps {
|
|
6
|
+
options?: ViewportOptions;
|
|
7
|
+
tools?: Tool[];
|
|
8
|
+
defaultTool?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
style?: CSSProperties;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
onReady?: (viewport: Viewport) => void;
|
|
13
|
+
}
|
|
14
|
+
interface FieldNotesCanvasRef {
|
|
15
|
+
viewport: Viewport | null;
|
|
16
|
+
}
|
|
17
|
+
declare const FieldNotesCanvas: react.ForwardRefExoticComponent<FieldNotesCanvasProps & react.RefAttributes<FieldNotesCanvasRef>>;
|
|
18
|
+
|
|
19
|
+
interface CanvasElementProps {
|
|
20
|
+
position: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
size?: {
|
|
25
|
+
w: number;
|
|
26
|
+
h: number;
|
|
27
|
+
};
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
}
|
|
30
|
+
declare function CanvasElement({ position, size, children }: CanvasElementProps): react.ReactPortal | null;
|
|
31
|
+
|
|
32
|
+
declare function useViewport(): Viewport;
|
|
33
|
+
|
|
34
|
+
declare function useActiveTool(): string;
|
|
35
|
+
|
|
36
|
+
interface CameraState {
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
zoom: number;
|
|
40
|
+
}
|
|
41
|
+
declare function useCamera(): CameraState;
|
|
42
|
+
|
|
43
|
+
declare const ViewportContext: react.Context<Viewport | null>;
|
|
44
|
+
|
|
45
|
+
export { type CameraState, CanvasElement, type CanvasElementProps, FieldNotesCanvas, type FieldNotesCanvasProps, type FieldNotesCanvasRef, ViewportContext, useActiveTool, useCamera, useViewport };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,137 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
1
|
+
// src/field-notes-canvas.tsx
|
|
2
|
+
import {
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
forwardRef,
|
|
7
|
+
useImperativeHandle
|
|
8
|
+
} from "react";
|
|
9
|
+
import { Viewport } from "@fieldnotes/core";
|
|
10
|
+
|
|
11
|
+
// src/context.ts
|
|
12
|
+
import { createContext } from "react";
|
|
13
|
+
var ViewportContext = createContext(null);
|
|
14
|
+
|
|
15
|
+
// src/field-notes-canvas.tsx
|
|
16
|
+
import { jsx } from "react/jsx-runtime";
|
|
17
|
+
var FieldNotesCanvas = forwardRef(
|
|
18
|
+
function FieldNotesCanvas2({ options, tools, defaultTool, className, style, children, onReady }, ref) {
|
|
19
|
+
const containerRef = useRef(null);
|
|
20
|
+
const [viewport, setViewport] = useState(null);
|
|
21
|
+
useImperativeHandle(ref, () => ({ viewport }), [viewport]);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const el = containerRef.current;
|
|
24
|
+
if (!el) return;
|
|
25
|
+
const vp = new Viewport(el, options);
|
|
26
|
+
if (tools) {
|
|
27
|
+
for (const tool of tools) {
|
|
28
|
+
vp.toolManager.register(tool);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (defaultTool) {
|
|
32
|
+
vp.toolManager.setTool(defaultTool, vp.toolContext);
|
|
33
|
+
}
|
|
34
|
+
setViewport(vp);
|
|
35
|
+
onReady?.(vp);
|
|
36
|
+
return () => {
|
|
37
|
+
vp.destroy();
|
|
38
|
+
setViewport(null);
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className, style, children: viewport && /* @__PURE__ */ jsx(ViewportContext.Provider, { value: viewport, children }) });
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// src/canvas-element.tsx
|
|
46
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
47
|
+
import { createPortal } from "react-dom";
|
|
48
|
+
|
|
49
|
+
// src/use-viewport.ts
|
|
50
|
+
import { useContext } from "react";
|
|
51
|
+
function useViewport() {
|
|
52
|
+
const viewport = useContext(ViewportContext);
|
|
53
|
+
if (!viewport) {
|
|
54
|
+
throw new Error("useViewport must be used inside <FieldNotesCanvas>");
|
|
55
|
+
}
|
|
56
|
+
return viewport;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/canvas-element.tsx
|
|
60
|
+
function CanvasElement({ position, size, children }) {
|
|
61
|
+
const viewport = useViewport();
|
|
62
|
+
const [portalTarget, setPortalTarget] = useState2(null);
|
|
63
|
+
const elementIdRef = useRef2(null);
|
|
64
|
+
useEffect2(() => {
|
|
65
|
+
const container = document.createElement("div");
|
|
66
|
+
Object.assign(container.style, {
|
|
67
|
+
width: "100%",
|
|
68
|
+
height: "100%"
|
|
69
|
+
});
|
|
70
|
+
viewport.domLayer.appendChild(container);
|
|
71
|
+
const id = viewport.addHtmlElement(container, position, size);
|
|
72
|
+
elementIdRef.current = id;
|
|
73
|
+
setPortalTarget(container);
|
|
74
|
+
return () => {
|
|
75
|
+
if (elementIdRef.current) {
|
|
76
|
+
viewport.store.remove(elementIdRef.current);
|
|
77
|
+
viewport.requestRender();
|
|
78
|
+
elementIdRef.current = null;
|
|
79
|
+
}
|
|
80
|
+
setPortalTarget(null);
|
|
81
|
+
};
|
|
82
|
+
}, [viewport]);
|
|
83
|
+
useEffect2(() => {
|
|
84
|
+
const id = elementIdRef.current;
|
|
85
|
+
if (!id) return;
|
|
86
|
+
viewport.store.update(id, { position });
|
|
87
|
+
if (size) {
|
|
88
|
+
viewport.store.update(id, { size });
|
|
89
|
+
}
|
|
90
|
+
viewport.requestRender();
|
|
91
|
+
}, [viewport, position.x, position.y, size?.w, size?.h]);
|
|
92
|
+
if (!portalTarget) return null;
|
|
93
|
+
return createPortal(children, portalTarget);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/use-active-tool.ts
|
|
97
|
+
import { useCallback, useSyncExternalStore } from "react";
|
|
98
|
+
function useActiveTool() {
|
|
99
|
+
const viewport = useViewport();
|
|
100
|
+
const subscribe = useCallback(
|
|
101
|
+
(onStoreChange) => viewport.toolManager.onChange(() => onStoreChange()),
|
|
102
|
+
[viewport]
|
|
103
|
+
);
|
|
104
|
+
const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? "", [viewport]);
|
|
105
|
+
return useSyncExternalStore(subscribe, getSnapshot);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/use-camera.ts
|
|
109
|
+
import { useCallback as useCallback2, useRef as useRef3, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
110
|
+
function useCamera() {
|
|
111
|
+
const viewport = useViewport();
|
|
112
|
+
const cachedRef = useRef3({ x: 0, y: 0, zoom: 1 });
|
|
113
|
+
const subscribe = useCallback2(
|
|
114
|
+
(onStoreChange) => viewport.camera.onChange(onStoreChange),
|
|
115
|
+
[viewport]
|
|
116
|
+
);
|
|
117
|
+
const getSnapshot = useCallback2(() => {
|
|
118
|
+
const { position, zoom } = viewport.camera;
|
|
119
|
+
const cached = cachedRef.current;
|
|
120
|
+
if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {
|
|
121
|
+
return cached;
|
|
122
|
+
}
|
|
123
|
+
const next = { x: position.x, y: position.y, zoom };
|
|
124
|
+
cachedRef.current = next;
|
|
125
|
+
return next;
|
|
126
|
+
}, [viewport]);
|
|
127
|
+
return useSyncExternalStore2(subscribe, getSnapshot);
|
|
128
|
+
}
|
|
3
129
|
export {
|
|
4
|
-
|
|
130
|
+
CanvasElement,
|
|
131
|
+
FieldNotesCanvas,
|
|
132
|
+
ViewportContext,
|
|
133
|
+
useActiveTool,
|
|
134
|
+
useCamera,
|
|
135
|
+
useViewport
|
|
5
136
|
};
|
|
6
137
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/
|
|
1
|
+
{"version":3,"sources":["../src/field-notes-canvas.tsx","../src/context.ts","../src/canvas-element.tsx","../src/use-viewport.ts","../src/use-active-tool.ts","../src/use-camera.ts"],"sourcesContent":["import {\r\n useRef,\r\n useEffect,\r\n useState,\r\n forwardRef,\r\n useImperativeHandle,\r\n type ReactNode,\r\n type CSSProperties,\r\n} from 'react';\r\nimport { Viewport } from '@fieldnotes/core';\r\nimport type { ViewportOptions, Tool } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport interface FieldNotesCanvasProps {\r\n options?: ViewportOptions;\r\n tools?: Tool[];\r\n defaultTool?: string;\r\n className?: string;\r\n style?: CSSProperties;\r\n children?: ReactNode;\r\n onReady?: (viewport: Viewport) => void;\r\n}\r\n\r\nexport interface FieldNotesCanvasRef {\r\n viewport: Viewport | null;\r\n}\r\n\r\nexport const FieldNotesCanvas = forwardRef<FieldNotesCanvasRef, FieldNotesCanvasProps>(\r\n function FieldNotesCanvas(\r\n { options, tools, defaultTool, className, style, children, onReady },\r\n ref,\r\n ) {\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const [viewport, setViewport] = useState<Viewport | null>(null);\r\n\r\n useImperativeHandle(ref, () => ({ viewport }), [viewport]);\r\n\r\n useEffect(() => {\r\n const el = containerRef.current;\r\n if (!el) return;\r\n\r\n const vp = new Viewport(el, options);\r\n\r\n if (tools) {\r\n for (const tool of tools) {\r\n vp.toolManager.register(tool);\r\n }\r\n }\r\n\r\n if (defaultTool) {\r\n vp.toolManager.setTool(defaultTool, vp.toolContext);\r\n }\r\n\r\n setViewport(vp);\r\n onReady?.(vp);\r\n\r\n return () => {\r\n vp.destroy();\r\n setViewport(null);\r\n };\r\n }, []);\r\n\r\n return (\r\n <div ref={containerRef} className={className} style={style}>\r\n {viewport && (\r\n <ViewportContext.Provider value={viewport}>{children}</ViewportContext.Provider>\r\n )}\r\n </div>\r\n );\r\n },\r\n);\r\n","import { createContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\n\r\nexport const ViewportContext = createContext<Viewport | null>(null);\r\n","import { useEffect, useRef, useState, type ReactNode } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CanvasElementProps {\r\n position: { x: number; y: number };\r\n size?: { w: number; h: number };\r\n children: ReactNode;\r\n}\r\n\r\nexport function CanvasElement({ position, size, children }: CanvasElementProps) {\r\n const viewport = useViewport();\r\n const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);\r\n const elementIdRef = useRef<string | null>(null);\r\n\r\n useEffect(() => {\r\n const container = document.createElement('div');\r\n Object.assign(container.style, {\r\n width: '100%',\r\n height: '100%',\r\n });\r\n\r\n // Append to domLayer immediately so portal children are queryable in the document\r\n // before the viewport render loop fires via requestAnimationFrame.\r\n viewport.domLayer.appendChild(container);\r\n\r\n const id = viewport.addHtmlElement(container, position, size);\r\n elementIdRef.current = id;\r\n setPortalTarget(container);\r\n\r\n return () => {\r\n if (elementIdRef.current) {\r\n viewport.store.remove(elementIdRef.current);\r\n viewport.requestRender();\r\n elementIdRef.current = null;\r\n }\r\n setPortalTarget(null);\r\n };\r\n }, [viewport]);\r\n\r\n useEffect(() => {\r\n const id = elementIdRef.current;\r\n if (!id) return;\r\n viewport.store.update(id, { position });\r\n if (size) {\r\n viewport.store.update(id, { size });\r\n }\r\n viewport.requestRender();\r\n }, [viewport, position.x, position.y, size?.w, size?.h]);\r\n\r\n if (!portalTarget) return null;\r\n return createPortal(children, portalTarget);\r\n}\r\n","import { useContext } from 'react';\r\nimport type { Viewport } from '@fieldnotes/core';\r\nimport { ViewportContext } from './context';\r\n\r\nexport function useViewport(): Viewport {\r\n const viewport = useContext(ViewportContext);\r\n if (!viewport) {\r\n throw new Error('useViewport must be used inside <FieldNotesCanvas>');\r\n }\r\n return viewport;\r\n}\r\n","import { useCallback, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport function useActiveTool(): string {\r\n const viewport = useViewport();\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.toolManager.onChange(() => onStoreChange()),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback(() => viewport.toolManager.activeTool?.name ?? '', [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n","import { useCallback, useRef, useSyncExternalStore } from 'react';\r\nimport { useViewport } from './use-viewport';\r\n\r\nexport interface CameraState {\r\n x: number;\r\n y: number;\r\n zoom: number;\r\n}\r\n\r\nexport function useCamera(): CameraState {\r\n const viewport = useViewport();\r\n const cachedRef = useRef<CameraState>({ x: 0, y: 0, zoom: 1 });\r\n\r\n const subscribe = useCallback(\r\n (onStoreChange: () => void) => viewport.camera.onChange(onStoreChange),\r\n [viewport],\r\n );\r\n\r\n const getSnapshot = useCallback((): CameraState => {\r\n const { position, zoom } = viewport.camera;\r\n const cached = cachedRef.current;\r\n if (cached.x === position.x && cached.y === position.y && cached.zoom === zoom) {\r\n return cached;\r\n }\r\n const next = { x: position.x, y: position.y, zoom };\r\n cachedRef.current = next;\r\n return next;\r\n }, [viewport]);\r\n\r\n return useSyncExternalStore(subscribe, getSnapshot);\r\n}\r\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,gBAAgB;;;ACTzB,SAAS,qBAAqB;AAGvB,IAAM,kBAAkB,cAA+B,IAAI;;;AD8DxD;AAtCH,IAAM,mBAAmB;AAAA,EAC9B,SAASA,kBACP,EAAE,SAAS,OAAO,aAAa,WAAW,OAAO,UAAU,QAAQ,GACnE,KACA;AACA,UAAM,eAAe,OAAuB,IAAI;AAChD,UAAM,CAAC,UAAU,WAAW,IAAI,SAA0B,IAAI;AAE9D,wBAAoB,KAAK,OAAO,EAAE,SAAS,IAAI,CAAC,QAAQ,CAAC;AAEzD,cAAU,MAAM;AACd,YAAM,KAAK,aAAa;AACxB,UAAI,CAAC,GAAI;AAET,YAAM,KAAK,IAAI,SAAS,IAAI,OAAO;AAEnC,UAAI,OAAO;AACT,mBAAW,QAAQ,OAAO;AACxB,aAAG,YAAY,SAAS,IAAI;AAAA,QAC9B;AAAA,MACF;AAEA,UAAI,aAAa;AACf,WAAG,YAAY,QAAQ,aAAa,GAAG,WAAW;AAAA,MACpD;AAEA,kBAAY,EAAE;AACd,gBAAU,EAAE;AAEZ,aAAO,MAAM;AACX,WAAG,QAAQ;AACX,oBAAY,IAAI;AAAA,MAClB;AAAA,IACF,GAAG,CAAC,CAAC;AAEL,WACE,oBAAC,SAAI,KAAK,cAAc,WAAsB,OAC3C,sBACC,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,UAAW,UAAS,GAEzD;AAAA,EAEJ;AACF;;;AEtEA,SAAS,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAgC;AAC5D,SAAS,oBAAoB;;;ACD7B,SAAS,kBAAkB;AAIpB,SAAS,cAAwB;AACtC,QAAM,WAAW,WAAW,eAAe;AAC3C,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,SAAO;AACT;;;ADAO,SAAS,cAAc,EAAE,UAAU,MAAM,SAAS,GAAuB;AAC9E,QAAM,WAAW,YAAY;AAC7B,QAAM,CAAC,cAAc,eAAe,IAAIC,UAA6B,IAAI;AACzE,QAAM,eAAeC,QAAsB,IAAI;AAE/C,EAAAC,WAAU,MAAM;AACd,UAAM,YAAY,SAAS,cAAc,KAAK;AAC9C,WAAO,OAAO,UAAU,OAAO;AAAA,MAC7B,OAAO;AAAA,MACP,QAAQ;AAAA,IACV,CAAC;AAID,aAAS,SAAS,YAAY,SAAS;AAEvC,UAAM,KAAK,SAAS,eAAe,WAAW,UAAU,IAAI;AAC5D,iBAAa,UAAU;AACvB,oBAAgB,SAAS;AAEzB,WAAO,MAAM;AACX,UAAI,aAAa,SAAS;AACxB,iBAAS,MAAM,OAAO,aAAa,OAAO;AAC1C,iBAAS,cAAc;AACvB,qBAAa,UAAU;AAAA,MACzB;AACA,sBAAgB,IAAI;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,CAAC;AAEb,EAAAA,WAAU,MAAM;AACd,UAAM,KAAK,aAAa;AACxB,QAAI,CAAC,GAAI;AACT,aAAS,MAAM,OAAO,IAAI,EAAE,SAAS,CAAC;AACtC,QAAI,MAAM;AACR,eAAS,MAAM,OAAO,IAAI,EAAE,KAAK,CAAC;AAAA,IACpC;AACA,aAAS,cAAc;AAAA,EACzB,GAAG,CAAC,UAAU,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD,MAAI,CAAC,aAAc,QAAO;AAC1B,SAAO,aAAa,UAAU,YAAY;AAC5C;;;AEpDA,SAAS,aAAa,4BAA4B;AAG3C,SAAS,gBAAwB;AACtC,QAAM,WAAW,YAAY;AAE7B,QAAM,YAAY;AAAA,IAChB,CAAC,kBAA8B,SAAS,YAAY,SAAS,MAAM,cAAc,CAAC;AAAA,IAClF,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAc,YAAY,MAAM,SAAS,YAAY,YAAY,QAAQ,IAAI,CAAC,QAAQ,CAAC;AAE7F,SAAO,qBAAqB,WAAW,WAAW;AACpD;;;ACdA,SAAS,eAAAC,cAAa,UAAAC,SAAQ,wBAAAC,6BAA4B;AASnD,SAAS,YAAyB;AACvC,QAAM,WAAW,YAAY;AAC7B,QAAM,YAAYC,QAAoB,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,EAAE,CAAC;AAE7D,QAAM,YAAYC;AAAA,IAChB,CAAC,kBAA8B,SAAS,OAAO,SAAS,aAAa;AAAA,IACrE,CAAC,QAAQ;AAAA,EACX;AAEA,QAAM,cAAcA,aAAY,MAAmB;AACjD,UAAM,EAAE,UAAU,KAAK,IAAI,SAAS;AACpC,UAAM,SAAS,UAAU;AACzB,QAAI,OAAO,MAAM,SAAS,KAAK,OAAO,MAAM,SAAS,KAAK,OAAO,SAAS,MAAM;AAC9E,aAAO;AAAA,IACT;AACA,UAAM,OAAO,EAAE,GAAG,SAAS,GAAG,GAAG,SAAS,GAAG,KAAK;AAClD,cAAU,UAAU;AACpB,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAOC,sBAAqB,WAAW,WAAW;AACpD;","names":["FieldNotesCanvas","useEffect","useRef","useState","useState","useRef","useEffect","useCallback","useRef","useSyncExternalStore","useRef","useCallback","useSyncExternalStore"]}
|
package/package.json
CHANGED
|
@@ -1,42 +1,43 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@fieldnotes/react",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "React wrapper for Field Notes canvas SDK",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/index.cjs",
|
|
7
|
-
"module": "./dist/index.js",
|
|
8
|
-
"types": "./dist/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"types": "./dist/index.d.ts",
|
|
12
|
-
"import": "./dist/index.js",
|
|
13
|
-
"require": "./dist/index.cjs"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"dist"
|
|
18
|
-
],
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsup",
|
|
21
|
-
"test": "vitest run",
|
|
22
|
-
"test:watch": "vitest"
|
|
23
|
-
},
|
|
24
|
-
"license": "MIT",
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@fieldnotes/core": "workspace:*"
|
|
27
|
-
},
|
|
28
|
-
"peerDependencies": {
|
|
29
|
-
"react": ">=18",
|
|
30
|
-
"react-dom": ">=18"
|
|
31
|
-
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@types/react
|
|
35
|
-
"@
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"react
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@fieldnotes/react",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "React wrapper for Field Notes canvas SDK",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@fieldnotes/core": "workspace:*"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"react": ">=18",
|
|
30
|
+
"react-dom": ">=18"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@testing-library/react": "^16.3.2",
|
|
34
|
+
"@types/react": "^19.2.14",
|
|
35
|
+
"@types/react-dom": "^19.2.3",
|
|
36
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
37
|
+
"jsdom": "^29.0.0",
|
|
38
|
+
"react": "^19.2.4",
|
|
39
|
+
"react-dom": "^19.2.4",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"vitest": "^4.1.0"
|
|
42
|
+
}
|
|
43
|
+
}
|